2766 字
约 9 分钟
34
从单体到微服务:Controller、Service、RPC、Feign、Mapper 到底该怎么写

从单体到微服务:Controller、Service、RPC、Feign、Mapper 到底该怎么写

刚开始从单体项目转到微服务项目时,很容易被这些东西绕晕:

Controller
Service
Mapper
RPC
Feign
Nacos
Gateway
api 模块
biz 模块

其实微服务并没有推翻我们以前熟悉的 Spring Boot 开发方式。

最核心的变化只有一个:

原来一个项目内部的方法调用,现在可能变成两个服务之间的网络调用。

只要把这一点想明白,整个微服务代码结构就会清晰很多。


一、先回忆单体项目怎么写

以前做单体 Spring Boot 项目,一个业务通常就是:

请求
 ↓
Controller
 ↓
Service
 ↓
Mapper
 ↓
MySQL

例如修改用户昵称:

UserController
 ↓
UserService
 ↓
UserMapper
 ↓
user 表

所有代码都运行在:

同一个 Spring Boot
同一个 JVM
同一个进程

所以 Service 之间可以直接调用 Java 方法。


二、微服务其实就是把大项目拆开

原来:

xiaohashu
├── 登录
├── 用户
├── 文件
├── 笔记
└── 评论

现在拆成:

xiaohashu-auth
xiaohashu-user
xiaohashu-oss
xiaohashu-note
xiaohashu-comment

可以把每个微服务理解成:

一个独立运行的小型 Spring Boot 项目。

例如 User 服务内部依然是:

Controller
 ↓
Service
 ↓
Mapper
 ↓
Database

这一部分和以前写单体基本没有区别。

真正不同的是:

如果 User 需要 OSS 帮忙,不能再直接调用 OSS 的 Service。

因为它们已经是两个独立进程。

这时候就需要远程调用。


三、以后拿到需求,第一件事不是写代码

以后拿到一个需求,先问:

这个功能到底属于哪个服务?

比如:

修改昵称

明显属于:

User 服务

再比如:

上传图片

属于:

OSS 服务

再比如:

登录、Token、认证

属于:

Auth 服务

所以以后第一步一定是:

需求
 ↓
判断服务归属

而不是直接开始写 Controller。


四、第二步:当前服务自己能完成吗?

这是微服务开发中特别重要的判断。

假设现在要:

修改昵称

User 服务自己就拥有用户数据。

那么:

UserController
 ↓
UserService
 ↓
UserMapper
 ↓
MySQL

直接完成。

不需要 Feign。


但是如果需求是:

修改头像

User 服务负责:

修改用户资料

但是图片上传属于:

OSS 服务

那么 User 自己做不完。

于是:

User
 ↓
调用 OSS

这时候才需要:

RPC
Feign

所以判断方式非常简单:

当前服务自己能完成?
        ↓
       能
        ↓
Controller → Service → Mapper

如果:

当前服务自己做不了?
        ↓
需要另外一个服务
        ↓
Service → RPC → Feign → 远程服务

五、Controller 到底负责什么?

Controller 可以简单理解成:

负责接请求。

主要做:

接收参数
参数校验
调用 Service
返回结果

例如:

public Response<?> updateAvatar(MultipartFile file) {
    return userService.updateAvatar(file);
}

Controller 不应该塞大量复杂业务逻辑。

所以以后看到 Controller,脑子里就想:

“请求到了,我把它交给 Service。”


六、Service 到底负责什么?

Service 是整个业务里面最重要的一层。

可以把它理解成:

业务流程的总指挥。

例如修改头像:

上传图片
 ↓
拿到图片 URL
 ↓
获取当前 userId
 ↓
更新用户头像

这整个流程应该由:

UserService

负责组织。

但是 Service 不一定亲自完成所有工作。

比如:

上传图片

交给 OSS。

更新数据库

交给 Mapper。

所以:

Controller
负责接请求

Service
负责业务编排

Mapper
负责数据库

Feign
负责调用远程服务

这是以后写微服务最重要的一组职责划分。


七、Mapper 什么时候使用?

以后准备写:

xxxMapper.update(...)

之前,先问一句:

这个数据是不是属于当前微服务?

例如:

UserService
 ↓
UserMapper

合理。

因为用户数据属于 User。

NoteService
 ↓
NoteMapper

也合理。

因为笔记属于 Note。

但是:

AuthService
 ↓
UserMapper

就需要警觉。

因为用户数据应该属于:

User 服务

所以更合理的是:

AuthService
 ↓
UserRpcService
 ↓
UserFeignApi
 ↓
User 服务
 ↓
UserService
 ↓
UserMapper

教程中修改密码的重构就是这种思路:认证服务不再直接更新用户数据库,而是调用用户服务完成密码持久化。

后来认证服务中原来的 Mapper、MyBatis、数据库相关代码也被清理掉了。

这背后的核心思想就是:

谁的数据,尽量由谁自己管理。


八、Feign 到底负责什么?

如果两个 Service 在同一个项目:

Service A
 ↓
Service B

直接:

serviceB.xxx();

就可以。

但是如果:

Auth

和:

User

已经是两个独立微服务,就不能直接调用 Java 对象。

于是:

Auth
 ↓
HTTP
 ↓
User

OpenFeign 就是把这个 HTTP 请求包装成:

userFeignApi.updatePassword();

看起来像普通 Java 方法。

但是底层其实是:

POST /user/password/update

教程中的 UserFeignApi.updatePassword() 就对应着用户服务的密码更新 HTTP 接口。

所以可以记住:

Feign = 把远程 HTTP 调用写成 Java 方法调用。


九、RPC 层又是干嘛的?

项目里可能还有:

UserRpcService

结构变成:

AuthService
 ↓
UserRpcService
 ↓
UserFeignApi

其实不是必须有这一层。

它主要是为了:

把远程调用相关代码和真正的业务代码分开。

例如:

AuthService

只想表达:

更新用户密码

于是写:

userRpcService.updatePassword(password);

而 RPC 层内部负责:

创建 DTO
 ↓
调用 Feign
 ↓
处理返回值
 ↓
处理远程异常

教程中的 UserRpcService 就是先构造更新密码 DTO,然后调用 UserFeignApi。

所以可以理解成:

Service
负责业务

RPC
负责和其他微服务打交道

Feign
负责真正发送 HTTP 请求

十、api 模块又是什么?

例如:

xiaohashu-user-api

它不是一个需要启动的微服务。

可以把它理解成:

User 服务对外公布的接口说明书。

里面通常放:

FeignClient
DTO
服务名称常量

例如:

UserFeignApi
UpdateUserPasswordReqDTO
FindUserByPhoneReqDTO

相当于 User 服务告诉其他服务:

我提供哪些接口
你应该传什么数据
我会返回什么数据

而真正处理业务的:

xiaohashu-user-biz

才会启动。

所以:

user-api
= 对外协议

user-biz
= 真正业务实现

十一、完整走一遍:修改头像

需求:

用户修改头像。

首先判断:

用户资料
 ↓
属于 User 服务

所以入口:

UserController

流程:

客户端
 ↓
Gateway
 ↓
UserController
 ↓
UserService

UserService 发现:

我要修改头像
 ↓
必须先上传图片

但是上传文件属于:

OSS 服务

所以:

UserService
 ↓
OssRpcService
 ↓
FileFeignApi
 ↓
OSS 服务

OSS 收到请求以后,又回到了我们熟悉的模式:

FileController
 ↓
FileService
 ↓
FileStrategy
 ↓
Minio / Aliyun

上传成功得到:

avatarUrl

然后返回 User:

OSS
 ↓
Feign
 ↓
RpcService
 ↓
UserService

UserService 再:

获取 userId
 ↓
UserMapper
 ↓
保存 avatarUrl

完整链路:

客户端
   ↓
Gateway
   ↓
UserController
   ↓
UserService
   ↓
OssRpcService
   ↓
FileFeignApi
   ↓
OSS 服务
   ↓
FileController
   ↓
FileService
   ↓
Minio / Aliyun
   ↓
返回图片 URL
   ↓
UserService
   ↓
UserMapper
   ↓
MySQL

十二、再走一遍:修改密码

修改密码可能先进入:

Auth 服务

因为 Auth 负责密码处理和认证。

流程:

AuthController
 ↓
AuthService
 ↓
BCrypt 加密密码

但是保存用户密码属于:

User 服务

所以:

AuthService
 ↓
UserRpcService
 ↓
UserFeignApi
 ↓
User 服务

到了 User 服务之后:

UserController
 ↓
UserService
 ↓
UserMapper
 ↓
MySQL

最终:

客户端
 ↓
Gateway
 ↓
Auth
 ↓
密码加密
 ↓
Feign
 ↓
User
 ↓
Mapper
 ↓
数据库

所以修改头像和修改密码看起来业务不同,其实代码结构完全是同一个套路。


十三、以后开发新功能,可以固定使用这套流程

以后拿到需求,先在脑子里跑:

第一步:
这个业务属于哪个服务?

第二步:
当前服务自己能不能完成?

第三步:
如果自己能完成:
Controller
 ↓
Service
 ↓
Mapper

如果需要其他服务:

Controller
 ↓
Service
 ↓
RPC
 ↓
Feign
 ↓
远程微服务
 ↓
Controller
 ↓
Service
 ↓
Mapper

最后再考虑:

userId 怎么传?
Token 怎么校验?
事务怎么处理?
Redis 要不要用?
异常怎么返回?

不要一上来就想着这些组件。

先把:

业务属于谁、数据属于谁、谁调用谁

弄明白。


十四、微服务开发的核心心法

以后看到这些类,可以这样理解:

Controller
有人来找我了
Service
这个业务具体怎么完成
Mapper
我要操作自己的数据库
RPC
我要找另一个微服务帮忙
Feign
我要真正发送 HTTP 请求
api 模块
这是我的对外调用协议
Nacos
我要调用的服务在哪里
Gateway
外部请求从哪里进入系统

十五、最终整体结构

以后写微服务,脑子里可以一直保留这张图:

                   客户端
                     ↓
                  Gateway
                     ↓
             当前服务 Controller
                     ↓
                  Service
               ↙           ↘
         自己能完成          需要别的服务
             ↓                  ↓
           Mapper              RPC
             ↓                  ↓
         自己的数据库         Feign
                                ↓
                              Nacos
                                ↓
                            目标微服务
                                ↓
                            Controller
                                ↓
                             Service
                                ↓
                              Mapper
                                ↓
                              数据库

所以微服务并没有把以前的开发方式推翻。

每一个微服务内部依然是:

Controller
 ↓
Service
 ↓
Mapper

只是当业务跨越服务边界的时候,中间多出了:

RPC
 ↓
Feign
 ↓
HTTP

最终可以用一句话总结:

先判断业务属于哪个服务;本服务能做就 Service → Mapper,本服务做不了就 Service → RPC → Feign → 目标服务;谁的数据,尽量由谁自己操作。

只要这套思维形成,以后再写微服务功能,就不会先被 Nacos、Feign、RPC 这些组件绕进去,而是先从业务流程出发,再自然决定代码应该放在哪一层。

从单体到微服务:Controller、Service、RPC、Feign、Mapper 到底该怎么写
https://clxhxhhr.top/posts/2944/
作者
clxstart
发布于
2026-09-20
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。