从分类维度冲突到领域驱动设计(DDD)
在分类问题中,无论是开发还是生活种,通常都习惯是单维树状结构表达的,但多数情况下,事物是多维属性的。
比如:一个关于web的博客,和一个关于web的项目,可以都归为web类,也可以分为博客类,项目类各一个。
再比如在开发中文件归属的问题,如瀑布流搜索这个类,可以归于瀑布流文件夹,也可以归于搜索。
那在工程上这种问题有什么好的思考方案吗?有的兄弟,有的。
问题本质
例子:
维度其实有两个:
- 领域(Domain) :web
- 类型(Type) :博客 / 项目
但目录树只能选一个主维度。
同理:
这是典型的 正交维度冲突 。
工程上的解决方案
核心原则:
目录按“主责”划分,而不是按“属性”划分,谁承担生命周期,谁是“主责”
目录表达主责,代码表达能力,组合表达场景。
谁是“核心”,谁做一级目录,思考:
- 谁依赖谁?
- 谁调用谁?
- 谁变更更频繁?
- 删除谁会影响谁?
1️⃣ 以“核心职责”作为一级目录
比如:
如果瀑布流搜索是为瀑布流页面服务
→ 主责是“瀑布流能力”
如果是搜索系统内部的一个召回策略
→ 主责是“搜索能力”
谁拥有生命周期,就放在哪。
2️⃣ 不用目录表达所有维度
不要用文件夹表达:
目录只表达 一个核心维度 。
其他维度用:
- package命名
- interface抽象
- 领域模型
- 标签(逻辑概念)
3️⃣ 常见的工程划分法
A. 按业务域分(推荐)
/waterfall
search.go
/search
recall.go
如果瀑布流是产品功能 → 用这种。
B. 按能力分(中台型系统)
/search
waterfall_strategy.go
category_strategy.go
如果是搜索中台 → 用这种。
瀑布流搜索的最佳实践
电商架构里
因此常见设计:
/search
service/
strategy/
waterfall.go
category.go
瀑布流只是一个策略,不是一个系统。
一个更高级的解决方案:避免“物理分类强绑定”
大型系统会:
例如:
/application
waterfall_feed_app.go
/domain
search/
recommend/
瀑布流只是一个 application 层组合。
更优的设计模式:领域驱动 + 组合层
那么如果一个系统经常出现,既有多个业务域,又有多个能力维度,能不能从一开始就设计成既能表达领域,又能表达能力的结构呢?有的兄弟,有的。
DDD(领域驱动设计)提供了一个很好的思路
领域驱动设计(DDD) 强调以业务领域为核心,将复杂的业务逻辑封装在领域层中。组合层(也叫应用层/编排层)则负责协调多个领域服务,完成复杂的业务流程。
还是以"瀑布流搜索"为例:
用户在电商APP首页下拉加载商品瀑布流,需要:
- 根据用户偏好推荐商品
- 搜索匹配的商品
- 过滤库存、上下架状态
- 返回分页数据
分层设计
┌─────────────────────────────────────────────────────┐
│ 接口层 (API) │
│ 接收请求、参数校验、返回响应 │
└─────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ 组合层 (Composer) │
│ 协调多个领域服务,编排业务流程 │
│ │
│ 瀑布流组合器: │
│ 1. 调用【用户领域】→ 获取用户偏好标签 │
│ 2. 调用【搜索领域】→ 根据关键词+标签搜索商品 │
│ 3. 调用【商品领域】→ 批量查询商品详情 │
│ 4. 调用【库存领域】→ 过滤无货商品 │
│ 5. 调用【推荐领域】→ 对结果排序 │
│ 6. 组装最终数据返回 │
└─────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ 领域层 (Domain) │
│ 每个领域独立,只关心自己的业务逻辑 │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 用户领域 │ │ 搜索领域 │ │ 商品领域 │ │
│ │ │ │ │ │ │ │
│ │·用户偏好 │ │·ES搜索 │ │·商品详情 │ │
│ │·用户画像 │ │·分词处理 │ │·上下架 │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │
│ ┌──────────┐ ┌──────────┐ │
│ │ 库存领域 │ │ 推荐领域 │ │
│ │ │ │ │ │
│ │·库存查询 │ │·个性化排序│ │
│ │·库存预占 │ │·热度计算 │ │
│ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ 基础设施层 │
│ 数据库、缓存、ES、消息队列等 │
└─────────────────────────────────────────────────────┘
- 接口层:接收HTTP/RPC请求,只做参数校验和响应封装
- 组合层:编排多个领域,不含业务逻辑,只做"胶水"工作
- 领域层:核心业务逻辑。高内聚、可独立测试、可复用
- 基础设施层:数据库,缓存、外部服务调用
实际应用场景
通过组合层编排,同样的领域服务,通过不同的组合方式,满足不同的业务场景。这就是 领域驱动 + 组合层 的核心价值:
首页瀑布流
用户偏好 → 搜索 → 商品 → 库存 → 推荐排序
搜索结果页
关键词解析 → 搜索 → 商品 → 筛选条件
分类页瀑布流
分类 → 搜索 → 商品 → 库存
店铺商品流
店铺 → 商品列表 → 库存 → 店铺排序规则
DDD和微服务的关系
域和微服务是两个不同的概念,领域之间通过内部函数调用,不是接口,也不是 RPC。
通常的项目历程是:早期项目、团队较小、业务简单,使用单体 + DDD,所有领域在一个服务内,通过包/模块隔离;随后中等规模、逐步拆分,模块化单体,领域独立模块,共享进程,可独立演进;最后独立扩缩需求,才需要微服务每个领域/聚合根独立部署。
以下场景可以考虑微服务化:
- 团队扩大 → 不同团队负责不同领域
- 独立扩缩容 → 搜索流量大,需单独扩容
- 技术栈差异 → 某领域需要用 Python 做 AI
DDD目录结构示例
app/product/
├── api/ # 接口层(Interface Adapters)
│ ├── http/
│ │ ├── spu_handler.go
│ │ ├── sku_handler.go
│ │ └── audit_handler.go
│ │
│ └── rpc/
│ └── product_rpc.go
│
├── handler/ # 应用层/组合层(Use Case / App Service)
│ ├── waterfall_feed.go
│ ├── spu_detail.go
│ ├── spu_audit.go
│ ├── related_recommend.go
│ └── terminal_push.go
│
├── server/ # 领域层(核心模型)
│
│ ├── product/ # 商品限界上下文(核心域)
│ │ ├── aggregate.go # Product 聚合根(含 SPU / SKU)
│ │ ├── entity.go
│ │ ├── value_object.go
│ │ ├── repository.go
│ │ └── domain_service.go
│ │
│ ├── audit/ # 审核子域
│ │ ├── aggregate.go
│ │ ├── repository.go
│ │ └── domain_service.go
│ │
│ ├── inventory/ # 库存子域
│ │ ├── aggregate.go
│ │ ├── repository.go
│ │ └── domain_service.go
│ │
│ ├── search/ # 搜索子域(如果是本系统的一部分)
│ │ ├── repository.go
│ │ └── domain_service.go
│ │
│ └── recommendation/ # 推荐子域
│ ├── repository.go
│ └── domain_service.go
│
├── infrastructure/ # 基础设施层
│
│ ├── persistence/
│ │ ├── mysql/
│ │ ├── es/
│ │ └── redis/
│ │
│ ├── external/
│ │ ├── user_client.go
│ │ └── merchant_client.go
│ │
│ └── repository_impl/ # 所有仓储实现
│ ├── product_repo_mysql.go
│ ├── audit_repo_mysql.go
│ ├── search_repo_es.go
│ └── inventory_repo_mysql.go
│
└── bootstrap/ # 依赖注入 / wiring
└── wire.go