从分类维度冲突到领域驱动设计(DDD)

在分类问题中,无论是开发还是生活种,通常都习惯是单维树状结构表达的,但多数情况下,事物是多维属性的。

比如:一个关于web的博客,和一个关于web的项目,可以都归为web类,也可以分为博客类,项目类各一个。 再比如在开发中文件归属的问题,如瀑布流搜索这个类,可以归于瀑布流文件夹,也可以归于搜索。

那在工程上这种问题有什么好的思考方案吗?有的兄弟,有的。

问题本质

例子:

  • web博客
  • web项目

维度其实有两个:

  1. 领域(Domain) :web
  2. 类型(Type) :博客 / 项目

但目录树只能选一个主维度。

同理:

  • 瀑布流搜索
    • 属于「瀑布流」能力?
    • 属于「搜索」能力?

这是典型的 正交维度冲突


工程上的解决方案

核心原则:

目录按“主责”划分,而不是按“属性”划分,谁承担生命周期,谁是“主责”

目录表达主责,代码表达能力,组合表达场景。

谁是“核心”,谁做一级目录,思考:

  1. 谁依赖谁?
  2. 谁调用谁?
  3. 谁变更更频繁?
  4. 删除谁会影响谁?

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