目标:帮助模板使用者用“高内聚、低耦合、可演进”的方式组织业务代码。
这份 Guide 不追求教条式 DDD。你可以把它理解为:一套更贴近日常开发的分层约定 + 一些实战决策规则。
六边形架构(Ports & Adapters)的直觉是:把业务核心放在中间,把 HTTP/MQ/DB/第三方等都当成可替换的适配器。
- 高内聚:同一个用例相关的代码尽量放在一起
- 低耦合:把容易变化的点(协议、存储、三方)隔离在端口与实现之后
越往内越“纯业务”(模型与规则),越往外越“技术/协议”(HTTP、MQ、Redis、DB、三方调用等)。
- 对外 req/resp、错误码、OpenAPI/Proto:放
tml-api - HTTP/RPC/MQ/Job 接入、协议解析、统一异常映射、Req/Resp 转换:放
tml-adapter - 用例编排(port.in/port.out)、场景流程(process)、内部业务模型(model):放
tml-service - 业务核心模型与规则(聚合、值对象、领域服务、领域事件):放
tml-domain - DB/Redis/第三方接口/消息生产者等技术实现:放
tml-infrastructure - 给其它服务用的 SDK:放
tml-client
一句话:越往内越“纯业务”,越往外越“技术/协议”。
本项目最外层 Maven 有两个模块:
tml-app:应用启动入口(Bootstrap)tml-standard:一个完整服务(或一个限界上下文)的实现骨架
tml-standard 下的模块结构(一个服务的标准形状):
tml-api:对外服务契约(req/resp、错误码、OpenAPI/Proto)tml-adapter:适配层(HTTP/RPC/MQ/Job),负责接入协议与数据转换tml-service:应用层(UseCase),负责用例/流程编排与端口定义tml-domain:领域层(业务核心),放领域模型与规则tml-infrastructure:基础设施实现(DB/缓存/第三方/消息生产者等)tml-client:对外 SDK(给其它服务或调用方用)
依赖方向(简化):
tml-app
→ tml-adapter
→ tml-service → tml-domain
→ tml-api
tml-infrastructure → tml-service, tml-domain
tml-client → tml-api
你会看到一个刻意的设计:
tml-service(应用层)不依赖tml-infrastructure(技术实现)- 需要访问 DB/Redis/第三方时,通过
port.out接口隔离变化
这套模板建议你把数据模型分成两套:
- 对外契约(API Contract):给调用方看的,放
tml-apiPayOrderReq/PayOrderRespErrorCode/ApiErrorResponse
- 服务内契约(Internal Contract):给应用层用例看的,放
tml-servicePayOrderModelCreateOrderModel
这样做的目的很简单:
- 对外契约经常受网关、前端、协议格式影响,变化更频繁
- 服务内模型更贴近业务语义,应该更稳定
典型链路(推荐):
HTTP/Proto/MQ
→ tml-adapter(协议解析)
→ tml-api req/resp(对外契约)
→ tml-adapter delegate(req→model)
→ tml-service port.in(用例)
这个模块干什么?
- 放应用启动入口(main / Spring Boot 启动类)
- 放服务级装配(把 adapter/service/domain/infrastructure 组装成可运行的服务)
- 放启动时初始化入口(比如启动后做一次预热),但不要在这里写业务规则
适合放哪些类?
Application/App(启动类)StartupRunner(启动后执行的 runner,内部调用应用层用例)
不要放什么?
- Controller、Repository、领域模型
- 复杂业务逻辑
定位:给“服务调用者”(前端、网关、其它微服务)提供稳定的调用契约。
当前模板已补齐推荐包:
io.github.timemachinelab.api
├─ req
├─ resp
├─ enums
├─ error
└─ contract
├─ openapi
└─ proto
包说明 + 放什么
req:对外请求- 例:
PayOrderReq、CreateOrderReq
- 例:
resp:对外响应- 例:
PayOrderResp、OrderDetailResp
- 例:
enums:公共枚举- 例:
OrderStatus、PayChannel
- 例:
error:错误码与统一错误响应- 例:
ErrorCode、ApiErrorResponse
- 例:
contract/openapi:OpenAPI 文件(可选)- 例:
payment-api.yaml
- 例:
contract/proto:Proto 文件(可选)- 例:
payment-service.proto
- 例:
一个直观例子
当你要新增一个接口“支付订单”:
- 在
req/resp新增:PayOrderReq/PayOrderResp - 在
error补充:PAYMENT_ORDER_NOT_FOUND这类错误码 - 如果你用 OpenAPI:在
contract/openapi增量维护接口文档
不要放什么
- 任何 Repository、ApplicationService、Domain 对象
- 与框架强绑定的类(Controller、Filter 等)
定位:处理“世界如何调用你”。把外部协议翻译成内部用例调用。
当前模板已补齐推荐包:
io.github.timemachinelab.adapter
├─ web
│ ├─ controller
│ ├─ delegate
│ ├─ interceptor
│ └─ handler
├─ rpc
├─ mq
│ ├─ listener
│ └─ delegate
└─ scheduler
└─ job
包说明 + 放什么
web/controller:HTTP Controller(只做协议接入)- 例:
PaymentController、OrderController
- 例:
web/delegate:API req/resp → 内部业务模型(model)的转换与调用- 例:
PaymentWebDelegate
- 例:
web/interceptor:HTTP 拦截器/Filter- 例:鉴权、traceId 透传、日志上下文
web/handler:统一异常映射- 例:
GlobalExceptionHandler:把内部异常映射为ApiErrorResponse
- 例:
mq/listener:MQ 消费入口(反序列化、幂等入口、调用用例)- 例:
PaymentTimeoutListener
- 例:
mq/delegate:消息 req/resp → 内部业务模型(model)的转换与调用- 例:
PaymentMessageDelegate
- 例:
scheduler/job:定时任务入口(只负责触发)- 例:
CloseExpiredOrdersJob
- 例:
rpc:gRPC/Dubbo 等入口适配(可选)
适配层最重要的两段“翻译”
- 协议 → API req(
tml-api)
- HTTP JSON →
PayOrderReq - MQ 二进制 →
PayOrderReq - Protobuf →
PayOrderReq
- API req → 内部模型(
tml-service)
PayOrderReq→PayOrderModelPayOrderResp← (由内部结果映射而来)
你之前担心的点(应用层被迫使用 adapter 的数据契约),在这里会被彻底规避:
- 应用层
port.in的入参永远是*Model这类内部模型 - req/resp 的存在感只停留在 adapter 和 api
定位:用例编排层。
- 定义系统“能做什么”:
port.in - 定义对外依赖“需要什么能力”:
port.out - 编排领域对象 + 调用
port.out完成一个用例
当前模板已补齐推荐包:
io.github.timemachinelab.service
├─ port
│ ├─ in
│ └─ out
├─ application
├─ process
└─ model
包说明 + 放什么
port/in:入站端口(用例接口)- 例:
PayOrderUseCase#pay(PayOrderModel)
- 例:
port/out:出站端口(抽象外部能力)- 例:
PaymentGatewayPort、OrderRepositoryPort、PaymentRecordRepositoryPort
- 例:
application:用例实现(ApplicationService),实现port.in- 例:
PaymentApplicationService implements PayOrderUseCase
- 例:
process:场景编排(跨用例/跨子域),不直接做底层技术- 例:
PayOrderProcessService:编排“创建订单 → 决策 → 支付 → 记流水 → 调三方”
- 例:
model:服务内业务模型(用例入参/中间态等),不是对外契约- 例:
PayOrderModel、CreateOrderModel
- 例:
port.in 什么时候写?
- 你只要确认“有一个用例要对外提供”,就优先写
port.in接口,并在application里实现 - adapter 只依赖
port.in,避免出现 Controller 直接依赖实现类
定位:放业务核心概念与规则。
当前模板已补齐按子域拆分的包结构(并保留一个 common 做共享):
io.github.timemachinelab.domain
├─ common
├─ order
│ ├─ model
│ ├─ service
│ └─ event
└─ payment
├─ model
├─ service
└─ event
包说明 + 放什么
order/model:订单聚合、实体、值对象- 例:
Order、OrderItem、Money
- 例:
order/service:订单领域服务(纯业务规则)- 例:
OrderDomainService
- 例:
order/event:领域事件(可选)- 例:
OrderCreatedEvent
- 例:
payment/*:同理common:跨子域共享但稳定的概念- 例:通用值对象
Money、通用异常基类(如果你选择放领域异常)
- 例:通用值对象
不要放什么
- HTTP、MQ、JSON、DB、Redis 等技术细节
定位:所有技术实现都放这里,专门去实现应用层定义的 port.out。
当前模板已补齐推荐包:
io.github.timemachinelab.infrastructure
├─ persistence
│ ├─ entity
│ └─ repository
├─ cache
├─ external
├─ mq
│ └─ producer
└─ config
包说明 + 放什么
persistence/entity:数据库映射对象- 例:
OrderEntity、PaymentRecordEntity
- 例:
persistence/repository:仓储实现- 例:
JpaOrderRepository implements OrderRepositoryPort
- 例:
cache:缓存实现(Redis/本地缓存)- 例:
RedisOrderCache implements OrderCachePort
- 例:
external:第三方接口调用实现- 例:
HttpPaymentGatewayAdapter implements PaymentGatewayPort
- 例:
mq/producer:消息生产者实现- 例:
KafkaPaymentEventPublisher implements PaymentEventPublisherPort
- 例:
config:数据源、事务、Redis/MQ 等技术配置
小框架(消费者/生产者框架、缓存框架)放哪?
- 如果只服务于当前服务:放
tml-infrastructure里(比如infrastructure/cache下) - 如果要复用给多个服务:建议抽成独立 Maven 模块,然后各服务的
*-infrastructure依赖它
定位:给其它服务/调用方提供“像本地方法一样调用”的 SDK。
当前模板已补齐推荐包:
io.github.timemachinelab.client
├─ api
└─ internal
包说明 + 放什么
api:对外暴露的 Client- 例:
PaymentServiceClient、OrderServiceClient
- 例:
internal:内部实现(HTTP/gRPC 调用、鉴权、重试、熔断等)
建议 SDK 的方法签名直接复用 tml-api 的 req/resp:
PaymentServiceClient.pay(PayOrderReq): PayOrderResp
假设你的业务流程是:
- 创建订单(订单子域)
- 基于订单信息决定是否继续支付
- 执行支付业务(支付子域)
- 存储支付流水
- 调用第三方接口进行支付
推荐的落层方式:
tml-apireq.PayOrderReq、resp.PayOrderResp
tml-adapterweb/controller.PaymentController:接 HTTPweb/delegate.PaymentWebDelegate:PayOrderReq -> PayOrderModel,调用PayOrderUseCase
tml-serviceprocess.PayOrderProcessService:编排“创建订单 → 决策 → 支付”port/in.CreateOrderUseCase、port/in.PayOrderUseCaseapplication.OrderApplicationService、application.PaymentApplicationServiceport/out.OrderRepositoryPort、port/out.PaymentRecordRepositoryPort、port/out.PaymentGatewayPort
tml-domainorder/model.Order、payment/model.Payment、payment/model.PaymentRecord
tml-infrastructurepersistence/repository.JpaOrderRepository(实现OrderRepositoryPort)persistence/repository.JpaPaymentRecordRepository(实现PaymentRecordRepositoryPort)external.HttpPaymentGatewayAdapter(实现PaymentGatewayPort)
这样拆的好处是:
- 用例编排在应用层,领域规则在领域层,技术实现留在基础设施层
- 将来订单/支付拆成两个微服务时,变化主要集中在
port.out的实现(本地调用 → 远程调用)
- 优先做到“模块边界清晰”,再谈聚合根、领域事件这些进阶概念
- 复杂流程(跨订单/支付)优先放
process(应用层编排),不要把两个子域的逻辑硬塞进一个领域对象 - 所有可能变化的外部依赖(DB/Redis/第三方/MQ)都通过
port.out隔离 - 当业务还很简单时:
- 允许 domain 很薄、process 很少
- 允许先用最小必要结构跑起来,复杂了再演进
