Appearance
Java 后端分层架构与跨微服务 RPC 调用实战指南
归类:Java 架构 / 分层设计与微服务 RPC
更新时间:2026-09-09
状态:✅ 已沉淀 | 📝 持续维护
一、架构分层概念与前端生态对照表
为了帮助全栈开发者快速建立对 Java 后端与 SCF 微服务架构的直观理解,下表将 Java 后端的核心分层组件与前端 Node.js/TypeScript 生态进行了 1:1 具象化类比:
| Java 后端组件 | 核心职责与设计原则 | 前端 (Node.js/TS) 具象化类比 | 真实项目节点 (fi_scf_fifac & pay_scf_expense) |
|---|---|---|---|
| DTO / VO | 纯数据载体,无业务逻辑。跨层/跨服务传递入参与结果。 | TypeScript Interface / type 类型定义 | ExpenseClosingDetailVO / TeamBuildingVerifyResult |
| BFF Controller | 对外 HTTP API 路由入口。负责参数校验、解析 DTO、调用业务服务、组装 HTTP 响应。 | Express / NestJS Router / Controller | TeamBuildingCheckController.java |
| SCF 契约层 (Contract) | 微服务接口定义与数据结构约束(即 RPC 协议)。纯规范,不跑服务。 | @types 共享包 / gRPC proto / tRPC Router Definition | IPayExpenseQueryService.java (位于 contract 模块) |
| SCF 服务层 (Service Impl) | 核心业务逻辑实现(流程编排、校验、算法、状态匹配)。 | NestJS Service / Koa 业务处理函数 | PayExpenseQueryServiceImpl.java (实现 IPayExpenseQueryService) |
| DAO (Data Access Object) | 封装数据库 CRUD 操作,防 SQL 注入,严禁暴露给 Controller 直连。 | Prisma Client / TypeORM Repository / Knex 查询函数 | FillDao.java / FillTbDetailDao.java |
| Maven 编译构建 | 依赖管理与项目编译打包(编译 .java 源码生成 .class / .jar)。非运行时。 | npm run build / pnpm / Webpack 打包阶段 | pom.xml 配置定义 |
二、真实项目实战案例:团建费计提核对 (TeamBuilding Provision Check)
在 58 业财系统中,fi_scf_fifac(财务总账与凭证核算中心)需要向 pay_scf_expense(费用业务中心)抓取当期(如 202608)指定公司编码下的团建费计提提单数据,并与财务凭证状态进行双向核对。
1. 运行时全链路调流流转
text
[前端 Vue/React 页面]
│
│ 1. HTTP POST /skill/teamBuilding/verify (period=202608, companyCode=58)
▼
[BFF 接入层] TeamBuildingCheckController.java (fi_scf_fifac)
│
│ 2. 参数规范化与 RPC 组装,调用 SCF 客户端
▼
[RPC 契约门面] IPayExpenseQueryService.java (pay_scf_expense contract 规范)
│
│ 3. SCF (TCP/RPC 协议传输),传递请求参数 DTO
▼
[服务逻辑层] PayExpenseQueryServiceImpl.java (pay_scf_expense service)
│
│ 4. 执行团建费计提业务编排,调用 DAO 读库
▼
[数据访问层] FillDao.java / FillTbDetailDao.java (pay_scf_expense)
│
│ 5. SQL: SELECT * FROM tb_fill WHERE fill_type = 'TEAM_BUILDING_PROVISION'
▼
[MySQL 数据库] 存储提单主表 `tb_fill` 与 团建费明细表 `tb_fill_tb_detail`
│
├─► 6. DAO 返回数据库实体 DO (FillDb / FillTbDetailDb)
├─► 7. Service 转换组装为传输对象 List<ExpenseClosingDetailVO>
├─► 8. 通过 SCF 网络 RPC 将 DTO 结果原路回传给 fi_scf_fifac
└─► 9. BFF 组装最终结果 TeamBuildingVerifyResult,以 JSON 格式响应前端三、各层核心代码与职责拆解
1. BFF 接入层 (Controller)
- 源码物理节点:
fi_scf_fifac/fi-fac-service/.../TeamBuildingCheckController.java - 前端类比:相当于 Express/NestJS 中的 API 路由。
- 核心代码逻辑:java
@RestController @RequestMapping("/skill/teamBuilding") public class TeamBuildingCheckController { @Autowired private IExpenseClosingCheckService expenseClosingCheckService; @PostMapping("/verify") public TeamBuildingVerifyResult verifyTeamBuilding(@RequestParam String period, @RequestParam String companyCode) { // 1. 参数校验与格式标准化 (如 2026-08 -> 202608) String normalizedPeriod = DateUtil.normalizePeriod(period); // 2. 调用服务层做团建费计提抓取与核对 Map<String, List<ExpenseClosingDetailVO>> detailVoMap = expenseClosingCheckService.queryExpenseDetails(normalizedPeriod, companyCode); // 3. 组装响应 DTO 返回给前端 return TeamBuildingVerifyResult.builder().data(detailVoMap).build(); } }
2. SCF 契约层与 DTO (Contract & Interface)
- 源码物理节点:
pay_scf_expense/contract/.../IPayExpenseQueryService.java - 前端类比:相当于 Monorepo 中
@shared/types的 TS Interface 定义与 tRPC 服务契约。 - 核心代码逻辑:java
public interface IPayExpenseQueryService { /** * 查询指定期间与公司编码下的团建费计提明细列表 */ List<ExpenseClosingDetailVO> queryTeamBuildingProvisionDetails(String period, String companyCode); } - DTO 数据载体 (
ExpenseClosingDetailVO.java):javapublic class ExpenseClosingDetailVO implements Serializable { private String billNo; // 提单单号 (如 TB2026080001) private String companyCode; // 公司编码 (如 58) private String period; // 会计期间 (如 202608) private BigDecimal amount; // 计提总金额 private Integer status; // 凭证状态 }
3. SCF 服务层 (Service Implementation)
- 源码物理节点:
pay_scf_expense/service/.../PayExpenseQueryServiceImpl.java - 前端类比:相当于 NestJS 的
@Injectable() class ExpenseService业务服务。 - 核心代码逻辑:java
@ServiceBehavior public class PayExpenseQueryServiceImpl implements IPayExpenseQueryService { @Inject private FillDao fillDao; @Inject private FillTbDetailDao fillTbDetailDao; @Override public List<ExpenseClosingDetailVO> queryTeamBuildingProvisionDetails(String period, String companyCode) { // 1. 调用 DAO 查询数据库 List<FillDb> fillList = fillDao.queryByPeriodAndType(period, companyCode, FillTypeEnum.TEAM_BUILDING_PROVISION.getCode()); // 2. 业务逻辑转换:将数据库 DO (FillDb) 转换为对外传输 DTO (ExpenseClosingDetailVO) List<ExpenseClosingDetailVO> result = new ArrayList<>(); for (FillDb db : fillList) { ExpenseClosingDetailVO vo = new ExpenseClosingDetailVO(); vo.setBillNo(db.getBillNo()); vo.setAmount(db.getTotalMoney()); vo.setPeriod(db.getPeriod()); result.add(vo); } return result; } }
4. 数据访问层 (DAO)
- 源码物理节点:
pay_scf_expense/service/.../FillDao.java - 前端类比:相当于 Prisma DB Client / TypeORM Repository。
- 核心代码逻辑:java
public class FillDao { public List<FillDb> queryByPeriodAndType(String period, String companyCode, int fillType) { // 纯粹的数据库 SQL 查询逻辑,只负责与 MySQL 数据库交互,不掺杂任何业务规则 return sqlSession.selectList("FillMapper.queryList", Map.of("period", period, "companyCode", companyCode, "fillType", fillType)); } }
四、架构分层与依赖强约束原则
单向依赖原则(上层依赖下层,下层不感知上层)
Controller➡️ 依赖Service接口与DTOService➡️ 依赖DAODAO➡️ 依赖数据库- 禁止倒挂:DAO 绝对不能反向调用 Service;Service 不能感知 Controller 的存在。
数据隔离原则(DO/PO 物理隔离 DTO/VO)
- DAO 数据库实体 (DO/PO):只在 Service 和 DAO 内部流转,存储数据库的物理字段(如
is_deleted,create_time)。 - 对外传输对象 (DTO/VO):由 Service 组装完成后,向 Controller 或 RPC 外部回传,绝对禁止将 DAO 的 DO 对象直接回传给前端或 RPC 外部客户端!
- DAO 数据库实体 (DO/PO):只在 Service 和 DAO 内部流转,存储数据库的物理字段(如
契约解耦原则 (Contract Separation)
fi_scf_fifac并不直接依赖pay_scf_expense的实现代码,只依赖其contractjar 包。- 这与前端
npm i @types/api-spec解耦的思想完全一致。
五、全景 Mermaid 架构调用图
六、记忆口诀(全栈极速复盘)
- HTTP 进 Controller:前端请求第一站,校验参数解析 JSON。
- RPC 靠 Contract:跨服务调用不硬连,全凭契约接口做约定。
- 业务在 Service:算法、流程、对账逻辑全在 Service 里实现。
- 存取找 DAO:数据库 SQL 封装在 DAO,DO 对象防泄漏。
- 传输用 DTO:数据结构只当载体,不写逻辑只传参。
- Maven 打包用:上线编译 npm build,运行时不参与调用。