土法炼钢兴趣小组的算法知识备份

【系统架构设计百科】防腐层与开放主机服务:系统集成的 DDD 方案

文章导航

分类入口
architecture
标签入口
#anti-corruption-layer#open-host-service#DDD#legacy-integration#adapter-pattern

目录

某金融科技公司正在构建新一代交易系统。新系统使用领域驱动设计,模型清晰、代码整洁。然而它必须对接一套运行了 15 年的核心银行系统(Core Banking System)——这套系统的接口返回 COBOL 风格的定长字段,状态码用两位数字表示(“01”=正常、“02”=冻结、“99”=未知),金额用”分”而非”元”为单位。如果新系统直接使用这些数据结构,精心设计的领域模型将被老系统的”语言”侵蚀。防腐层(Anti-Corruption Layer)正是为解决这类问题而存在的。

本文深入讨论两种互补的上下文映射模式——防腐层(Anti-Corruption Layer,ACL)和开放主机服务(Open Host Service,OHS),分析其实现架构、适用场景和工程实践。

上一篇:领域事件与事件风暴 | 下一篇:CQRS + Event Sourcing 完整实战


一、为什么需要防腐层

1.1 直接集成的代价

当系统 A 直接调用系统 B 的接口并使用系统 B 的数据结构时,会发生以下问题:

问题 说明
模型污染 系统 B 的概念侵入系统 A 的领域模型
语义扭曲 系统 B 的字段命名和类型定义影响系统 A 的通用语言
变更耦合 系统 B 的接口变更直接导致系统 A 的代码修改
测试困难 测试系统 A 必须依赖系统 B 的运行环境
认知负载 开发者需要同时理解两套模型

1.2 一个具体的例子

假设新系统的领域模型中,账户(Account)是这样定义的:

// 新系统的领域模型——清晰、语义明确
public class Account {
    private AccountId id;
    private AccountHolder holder;
    private Money balance;
    private AccountStatus status; // ACTIVE, FROZEN, CLOSED
    private Currency currency;
}

而遗留核心银行系统的接口返回的是这样的数据:

{
  "ACCT_NO": "00123456789012",
  "CUST_CD": "C0001",
  "BAL_AMT": 1234500,
  "BAL_CCY": "156",
  "STAT_CD": "01",
  "ACCT_TYP": "SA",
  "LST_TXN_DT": "20250101",
  "BRN_CD": "001"
}

如果直接在领域模型中使用 STAT_CDBAL_AMT(以分为单位)、BAL_CCY(ISO 数字码而非字母码)这些概念,领域模型将变得面目全非。


二、防腐层的架构

2.1 三层结构

Eric Evans 在原著中定义的防腐层由三个组件构成:

graph LR
    subgraph "我的限界上下文"
        DS["领域服务<br/>Domain Service"]
    end

    subgraph "防腐层 ACL"
        F["外观<br/>Facade"]
        A["适配器<br/>Adapter"]
        T["转换器<br/>Translator"]
    end

    subgraph "外部系统"
        ES["遗留系统<br/>Legacy API"]
    end

    DS -->|"使用领域语言"| F
    F -->|"简化接口"| A
    A -->|"协议适配"| ES
    T -.->|"模型转换"| F
    T -.->|"模型转换"| A

2.2 每个组件的职责

组件 输入 输出 职责
Facade 领域对象 领域对象 提供符合本域语言的接口
Translator 外部 DTO ↔︎ 领域对象 领域对象 ↔︎ 外部 DTO 模型映射和数据转换
Adapter 外部 DTO 外部 DTO 网络通信、序列化、错误处理

2.3 数据流示意

本域代码                                 外部系统
   │                                      │
   │  调用 Facade                          │
   │  (领域语言)                          │
   ▼                                      │
Facade                                    │
   │  调用 Translator                      │
   │  (领域对象 → 外部 DTO)               │
   ▼                                      │
Translator                                │
   │  调用 Adapter                         │
   │  (外部 DTO)                         │
   ▼                                      │
Adapter ─────── HTTP / MQ / SOAP ─────────▶│
   │                                      │
   │◀──────── 响应数据 ───────────────────│
   ▼                                      │
Translator                                │
   │  (外部 DTO → 领域对象)               │
   ▼                                      │
Facade                                    │
   │  (返回领域对象)                      │
   ▼                                      │
本域代码                                   │

三、防腐层的实现

3.1 Java 实现

// ============ Adapter 层:负责网络通信 ============
public class CoreBankingAdapter {
    private final HttpClient httpClient;
    private final String baseUrl;

    public CoreBankingAdapter(HttpClient httpClient, String baseUrl) {
        this.httpClient = httpClient;
        this.baseUrl = baseUrl;
    }

    public CoreBankingAccountDto fetchAccount(String accountNumber) {
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(baseUrl + "/accounts/" + accountNumber))
            .header("Content-Type", "application/json")
            .GET()
            .build();

        try {
            HttpResponse<String> response = httpClient.send(
                request, HttpResponse.BodyHandlers.ofString());

            if (response.statusCode() != 200) {
                throw new ExternalSystemException(
                    "核心银行系统返回错误: " + response.statusCode());
            }

            return objectMapper.readValue(
                response.body(), CoreBankingAccountDto.class);
        } catch (IOException | InterruptedException e) {
            throw new ExternalSystemException(
                "调用核心银行系统失败", e);
        }
    }
}

// ============ 外部系统的 DTO ============
public class CoreBankingAccountDto {
    @JsonProperty("ACCT_NO")
    private String accountNumber;
    @JsonProperty("CUST_CD")
    private String customerCode;
    @JsonProperty("BAL_AMT")
    private long balanceAmount; // 以分为单位
    @JsonProperty("BAL_CCY")
    private String balanceCurrency; // ISO 数字码
    @JsonProperty("STAT_CD")
    private String statusCode;
    @JsonProperty("LST_TXN_DT")
    private String lastTransactionDate; // yyyyMMdd
    // getter/setter 省略
}

// ============ Translator 层:负责模型转换 ============
public class AccountTranslator {

    private static final Map<String, AccountStatus> STATUS_MAP = Map.of(
        "01", AccountStatus.ACTIVE,
        "02", AccountStatus.FROZEN,
        "03", AccountStatus.CLOSED
    );

    private static final Map<String, Currency> CURRENCY_MAP = Map.of(
        "156", Currency.getInstance("CNY"),
        "840", Currency.getInstance("USD"),
        "978", Currency.getInstance("EUR")
    );

    public Account toDomain(CoreBankingAccountDto dto) {
        AccountStatus status = STATUS_MAP.get(dto.getStatusCode());
        if (status == null) {
            throw new TranslationException(
                "未知的账户状态码: " + dto.getStatusCode());
        }

        Currency currency = CURRENCY_MAP.get(dto.getBalanceCurrency());
        if (currency == null) {
            throw new TranslationException(
                "未知的币种代码: " + dto.getBalanceCurrency());
        }

        // 将"分"转换为"元"
        BigDecimal amount = BigDecimal.valueOf(dto.getBalanceAmount())
            .divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);

        return Account.reconstitute(
            new AccountId(dto.getAccountNumber()),
            new AccountHolder(dto.getCustomerCode()),
            new Money(amount, currency),
            status
        );
    }
}

// ============ Facade 层:提供领域语言接口 ============
public class ExternalAccountService {
    private final CoreBankingAdapter adapter;
    private final AccountTranslator translator;

    public ExternalAccountService(CoreBankingAdapter adapter,
                                   AccountTranslator translator) {
        this.adapter = adapter;
        this.translator = translator;
    }

    /**
     * 查询外部账户信息,返回本域的 Account 领域对象。
     * 本域代码只与 ExternalAccountService 交互,
     * 不需要知道核心银行系统的存在。
     */
    public Account findAccount(AccountId accountId) {
        CoreBankingAccountDto dto = adapter.fetchAccount(
            accountId.value());
        return translator.toDomain(dto);
    }

    public boolean isAccountActive(AccountId accountId) {
        Account account = findAccount(accountId);
        return account.isActive();
    }
}

3.2 Go 实现

// adapter.go —— 网络通信层
type CoreBankingClient struct {
    baseURL    string
    httpClient *http.Client
}

type coreBankingAccountDTO struct {
    AccountNo   string `json:"ACCT_NO"`
    CustomerCode string `json:"CUST_CD"`
    BalanceAmt  int64  `json:"BAL_AMT"`
    BalanceCcy  string `json:"BAL_CCY"`
    StatusCode  string `json:"STAT_CD"`
}

func (c *CoreBankingClient) FetchAccount(ctx context.Context,
    accountNo string) (*coreBankingAccountDTO, error) {

    url := fmt.Sprintf("%s/accounts/%s", c.baseURL, accountNo)
    req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
    if err != nil {
        return nil, fmt.Errorf("构建请求失败: %w", err)
    }

    resp, err := c.httpClient.Do(req)
    if err != nil {
        return nil, fmt.Errorf("调用核心银行系统失败: %w", err)
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusOK {
        return nil, fmt.Errorf("核心银行系统返回错误: %d", resp.StatusCode)
    }

    var dto coreBankingAccountDTO
    if err := json.NewDecoder(resp.Body).Decode(&dto); err != nil {
        return nil, fmt.Errorf("解析响应失败: %w", err)
    }
    return &dto, nil
}

// translator.go —— 模型转换层
var statusMap = map[string]AccountStatus{
    "01": AccountStatusActive,
    "02": AccountStatusFrozen,
    "03": AccountStatusClosed,
}

var currencyMap = map[string]string{
    "156": "CNY",
    "840": "USD",
    "978": "EUR",
}

func translateAccount(dto *coreBankingAccountDTO) (*Account, error) {
    status, ok := statusMap[dto.StatusCode]
    if !ok {
        return nil, fmt.Errorf("未知的账户状态码: %s", dto.StatusCode)
    }

    currencyCode, ok := currencyMap[dto.BalanceCcy]
    if !ok {
        return nil, fmt.Errorf("未知的币种代码: %s", dto.BalanceCcy)
    }

    // 分 → 元
    amount := decimal.NewFromInt(dto.BalanceAmt).
        Div(decimal.NewFromInt(100))

    return &Account{
        ID:      AccountID(dto.AccountNo),
        Holder:  AccountHolder(dto.CustomerCode),
        Balance: NewMoney(amount, currencyCode),
        Status:  status,
    }, nil
}

// facade.go —— 领域语言接口
type ExternalAccountService struct {
    client     *CoreBankingClient
}

func (s *ExternalAccountService) FindAccount(ctx context.Context,
    id AccountID) (*Account, error) {

    dto, err := s.client.FetchAccount(ctx, string(id))
    if err != nil {
        return nil, fmt.Errorf("查询外部账户失败: %w", err)
    }
    return translateAccount(dto)
}

3.3 防腐层的测试策略

防腐层的每一层都需要独立测试:

测试方法 验证内容
Adapter WireMock / httptest 模拟外部 API 网络通信、错误处理、超时
Translator 单元测试 所有已知的映射正确、未知值的错误处理
Facade 集成测试(Mock Adapter) 端到端流程、领域对象构建正确
// translator 的单元测试
func TestTranslateAccount_Success(t *testing.T) {
    dto := &coreBankingAccountDTO{
        AccountNo:    "00123456789012",
        CustomerCode: "C0001",
        BalanceAmt:   1234500,
        BalanceCcy:   "156",
        StatusCode:   "01",
    }

    account, err := translateAccount(dto)
    assert.NoError(t, err)
    assert.Equal(t, AccountID("00123456789012"), account.ID)
    assert.Equal(t, AccountStatusActive, account.Status)
    assert.Equal(t, "12345.00", account.Balance.Amount().String())
    assert.Equal(t, "CNY", account.Balance.Currency())
}

func TestTranslateAccount_UnknownStatus(t *testing.T) {
    dto := &coreBankingAccountDTO{
        StatusCode: "99",
        BalanceCcy: "156",
    }

    _, err := translateAccount(dto)
    assert.Error(t, err)
    assert.Contains(t, err.Error(), "未知的账户状态码")
}

四、开放主机服务

4.1 定义

开放主机服务(Open Host Service,OHS)是上游上下文提供的一套标准化、版本化的公开协议,供多个下游上下文使用。

如果说防腐层是下游的自我保护机制,那么开放主机服务就是上游的主动服务机制。两者经常配合使用。

4.2 OHS 的设计原则

原则 说明
接口稳定 公开接口一旦发布,尽量不做破坏性变更
版本管理 支持多版本并存,给下游迁移时间
文档完善 提供完整的 API 文档和使用指南
独立于内部模型 接口数据结构与内部领域模型解耦
向后兼容 新版本应该兼容旧版本的客户端

4.3 OHS 的实现形式

              ┌────────────────────────────────┐
              │       开放主机服务 (OHS)         │
              │                                │
              │  ┌──────────────────────────┐   │
              │  │  API 层(接口契约)        │   │
              │  │  - REST / gRPC / GraphQL  │   │
              │  │  - 版本管理: /v1, /v2    │   │
              │  └──────────┬───────────────┘   │
              │             │                   │
              │  ┌──────────▼───────────────┐   │
              │  │  转换层                    │   │
              │  │  领域模型 → API 响应模型   │   │
              │  └──────────┬───────────────┘   │
              │             │                   │
              │  ┌──────────▼───────────────┐   │
              │  │  领域层                    │   │
              │  │  内部领域模型              │   │
              │  └──────────────────────────┘   │
              └────────────────────────────────┘

4.4 代码示例:OHS 的 REST API

// OHS 的 API 数据传输对象——独立于内部领域模型
public record AccountResponse(
    String accountId,
    String holderName,
    String balance,
    String currency,
    String status,
    String lastUpdated
) {}

// OHS 控制器
@RestController
@RequestMapping("/api/v1/accounts")
public class AccountApiV1Controller {
    private final AccountQueryService queryService;
    private final AccountApiTranslator translator;

    @GetMapping("/{accountId}")
    public ResponseEntity<AccountResponse> getAccount(
            @PathVariable String accountId) {
        Account account = queryService.findById(new AccountId(accountId));
        AccountResponse response = translator.toApiResponse(account);
        return ResponseEntity.ok(response);
    }
}

// OHS 的转换器:领域模型 → API 响应
public class AccountApiTranslator {
    public AccountResponse toApiResponse(Account account) {
        return new AccountResponse(
            account.id().value(),
            account.holder().name(),
            account.balance().amount().toPlainString(),
            account.balance().currency().getCurrencyCode(),
            account.status().name().toLowerCase(),
            account.lastUpdated().toString()
        );
    }
}

五、发布语言

5.1 定义

发布语言(Published Language,PL)是一种文档化的、版本化的数据交换格式,供多方使用。它通常与开放主机服务配合——OHS 定义”如何通信”,PL 定义”通信的内容格式”。

5.2 常见形式

形式 优点 缺点 适用场景
JSON Schema 可读性好、生态丰富 类型系统较弱 REST API
Protocol Buffers 强类型、高性能、向后兼容 可读性差 gRPC、消息队列
Avro Schema 支持 schema 演进 需要 schema 注册中心 Kafka 生态
OpenAPI (Swagger) 文档和代码生成一体 规范复杂 REST API 文档
GraphQL Schema 灵活查询 服务端实现复杂 前后端交互

5.3 Protocol Buffers 示例

syntax = "proto3";
package account.api.v1;

option go_package = "github.com/example/account/api/v1";
option java_package = "com.example.account.api.v1";

// 发布语言:账户服务的公开数据格式
service AccountService {
    rpc GetAccount(GetAccountRequest) returns (AccountResponse);
    rpc ListAccounts(ListAccountsRequest) returns (ListAccountsResponse);
}

message GetAccountRequest {
    string account_id = 1;
}

message AccountResponse {
    string account_id = 1;
    string holder_name = 2;
    MoneyValue balance = 3;
    AccountStatus status = 4;
    google.protobuf.Timestamp last_updated = 5;
}

message MoneyValue {
    string amount = 1; // 使用字符串避免浮点精度问题
    string currency = 2; // ISO 4217 字母码
}

enum AccountStatus {
    ACCOUNT_STATUS_UNSPECIFIED = 0;
    ACCOUNT_STATUS_ACTIVE = 1;
    ACCOUNT_STATUS_FROZEN = 2;
    ACCOUNT_STATUS_CLOSED = 3;
}

六、ACL vs Conformist vs Separate Ways 的选择

6.1 决策框架

flowchart TD
    START["需要对接外部系统"] --> Q1{"外部系统的模型<br/>是否可以接受?"}
    Q1 -->|"完全可以接受"| CF["遵从者<br/>Conformist"]
    Q1 -->|"部分可接受"| Q2{"集成价值是否<br/>大于 ACL 成本?"}
    Q1 -->|"完全不可接受"| Q2

    Q2 -->|是| Q3{"外部系统是否<br/>提供 OHS?"}
    Q2 -->|否| SW["各行其道<br/>Separate Ways"]

    Q3 -->|是| ACL_OHS["ACL + 对接 OHS"]
    Q3 -->|否| ACL_RAW["ACL + 直接适配"]

    CF --> CF_NOTE["风险:模型污染<br/>适合:标准化系统"]
    SW --> SW_NOTE["风险:数据孤岛<br/>适合:集成价值低"]
    ACL_OHS --> ACL_NOTE["风险:维护成本<br/>适合:遗留系统对接"]
    ACL_RAW --> ACL_NOTE

6.2 对比总结

维度 ACL Conformist Separate Ways
实现成本 高(需要建设翻译层) 低(直接使用)
维护成本 中(翻译层需跟随外部变更)
模型纯净度 高(完全隔离) 低(被外部模型污染) 高(无集成)
适用场景 遗留系统、外部 API 强势第三方标准 集成价值低
风险 翻译层可能不完整 领域模型退化 数据不一致

七、工程案例:对接遗留 ERP 系统

7.1 背景

某制造企业的新一代供应链管理系统需要对接运行了 12 年的 SAP ERP 系统。ERP 系统通过 RFC(Remote Function Call)提供接口,数据格式为定长的 BAPI 结构。

7.2 挑战

挑战 具体表现
数据格式 BAPI 结构使用定长字段,字段名缩写晦涩
编码体系 物料编码、工厂编码、供应商编码使用内部编号
业务语义 “移动类型”(Movement Type)用三位数字编码
性能 RFC 调用延迟高(200-500ms)
可用性 ERP 系统有维护窗口,每月停机一次

7.3 ACL 架构设计

┌─────────────────────────────────────────────────────┐
│                  新供应链系统                          │
│                                                     │
│  ┌─────────────┐                                    │
│  │ 采购域       │                                    │
│  │ 领域服务     │──── 使用领域语言 ────┐              │
│  └─────────────┘                     │              │
│                                      ▼              │
│  ┌───────────────────────────────────────────────┐  │
│  │              防腐层 (ACL)                       │  │
│  │                                               │  │
│  │  ┌───────────┐  ┌───────────┐  ┌───────────┐  │  │
│  │  │  Facade   │  │ Translator│  │  Adapter  │  │  │
│  │  │           │  │           │  │           │  │  │
│  │  │ 提供领域  │  │ BAPI ↔    │  │ JCo/RFC  │  │  │
│  │  │ 语言接口  │  │ 领域模型  │  │ 通信     │  │  │
│  │  └───────────┘  └───────────┘  └───────────┘  │  │
│  │                                               │  │
│  │  ┌───────────┐                                │  │
│  │  │  Cache    │ ← 缓解 RFC 延迟               │  │
│  │  └───────────┘                                │  │
│  └───────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────┘
                          │
                    RFC / BAPI
                          │
                          ▼
              ┌──────────────────┐
              │    SAP ERP       │
              │  (遗留系统)       │
              └──────────────────┘

7.4 关键实现

// ERP Adapter:封装 JCo/RFC 调用
public class SapMaterialAdapter {
    private final JCoDestination destination;

    public BapiMaterialDto getMaterial(String materialNumber) {
        JCoFunction function = destination
            .getRepository()
            .getFunction("BAPI_MATERIAL_GET_DETAIL");

        function.getImportParameterList()
            .setValue("MATERIAL", materialNumber);

        function.execute(destination);

        JCoStructure header = function.getExportParameterList()
            .getStructure("MATERIAL_GENERAL_DATA");

        return new BapiMaterialDto(
            header.getString("MATERIAL"),
            header.getString("MATL_DESC"),
            header.getString("BASE_UOM"),
            header.getString("MATL_GROUP"),
            header.getString("MATL_TYPE")
        );
    }
}

// Translator:BAPI 结构 → 领域对象
public class MaterialTranslator {
    public Material toDomain(BapiMaterialDto bapi) {
        return Material.reconstitute(
            new MaterialId(bapi.materialNumber().trim()),
            bapi.description().trim(),
            mapUnitOfMeasure(bapi.baseUom()),
            mapMaterialGroup(bapi.materialGroup()),
            mapMaterialType(bapi.materialType())
        );
    }

    private UnitOfMeasure mapUnitOfMeasure(String sapUom) {
        return switch (sapUom.trim()) {
            case "EA" -> UnitOfMeasure.EACH;
            case "KG" -> UnitOfMeasure.KILOGRAM;
            case "M"  -> UnitOfMeasure.METER;
            case "L"  -> UnitOfMeasure.LITER;
            default -> throw new TranslationException(
                "未知的 SAP 计量单位: " + sapUom);
        };
    }
}

// Facade:领域语言接口 + 缓存
public class ExternalMaterialService {
    private final SapMaterialAdapter adapter;
    private final MaterialTranslator translator;
    private final Cache<String, Material> cache;

    public Material findMaterial(MaterialId id) {
        return cache.get(id.value(), key -> {
            BapiMaterialDto dto = adapter.getMaterial(key);
            return translator.toDomain(dto);
        });
    }
}

7.5 效果

指标 无 ACL 有 ACL
领域模型可读性 差(充斥 SAP 概念) 好(纯领域语言)
外部变更影响范围 全系统 仅 ACL
单元测试覆盖率 30%(依赖 SAP 环境) 85%(可 Mock)
新人上手时间 3 周(需学习 SAP) 1 周(只需学领域)
RFC 调用延迟 直接承受 300ms 缓存后 < 5ms

八、防腐层的演进模式

8.1 渐进式引入

不需要一次性为所有外部集成建立防腐层。推荐的演进路径:

阶段 1:识别最痛的集成点
  ├── 哪些外部模型对领域污染最严重?
  └── 哪些集成点变更最频繁?

阶段 2:为最痛点建立 ACL
  ├── 先建 Translator,确保模型转换正确
  ├── 再建 Adapter,封装通信细节
  └── 最后建 Facade,提供领域语言接口

阶段 3:逐步扩展到其他集成点

阶段 4:定期清理和优化
  ├── 外部系统升级时更新 Translator
  └── 下线不再使用的映射逻辑

8.2 与 Strangler Fig 模式结合

当目标是逐步替换遗留系统时,防腐层可以与绞杀者模式(Strangler Fig Pattern)结合使用:

graph TB
    subgraph "阶段 1:ACL 隔离"
        NEW1["新系统"] -->|"ACL"| OLD1["遗留系统<br/>(全部功能)"]
    end

    subgraph "阶段 2:部分迁移"
        NEW2["新系统<br/>(部分功能)"] -->|"ACL"| OLD2["遗留系统<br/>(部分功能)"]
    end

    subgraph "阶段 3:完全替换"
        NEW3["新系统<br/>(全部功能)"]
        OLD3["遗留系统<br/>(已下线)"]
    end

    OLD1 -.->|"演进"| OLD2
    OLD2 -.->|"演进"| OLD3

九、综合权衡

维度 ACL(重型) ACL(轻型) 直接集成
实现成本 高(三层完整实现) 中(简化的转换层)
维护成本 高(变更扩散)
隔离效果 完全隔离 部分隔离 无隔离
适用场景 核心域对接遗留系统 支撑域对接标准 API 通用域、低风险
测试友好
性能影响 有转换开销 较小
团队理解成本 中(需理解 ACL 模式) 低(但后期高)
维度 OHS + PL 定制 API 无 API
实现成本 高(需要文档和版本管理)
下游适配成本 低(标准化) 高(每个下游定制) 最高
可扩展性 高(新下游直接接入)
适用场景 一对多服务 一对一定制 内部模块

十、总结与下一步

防腐层和开放主机服务是 DDD 上下文映射中最具工程实践价值的两种模式。ACL 保护下游不被上游的”烂模型”污染,OHS 让上游以标准化的方式服务多个下游。两者经常组合使用:上游提供 OHS,下游通过 ACL 消费。

在下一篇文章中,我们将深入 CQRS + Event Sourcing 的完整实现,探讨事件存储设计、投影重建和事件版本化等工程细节。

下一篇:CQRS + Event Sourcing 完整实战


参考资料

  1. Evans, Eric. Domain-Driven Design: Tackling Complexity in the Heart of Software. Addison-Wesley, 2003.
  2. Vernon, Vaughn. Implementing Domain-Driven Design. Addison-Wesley, 2013.
  3. Fowler, Martin. “Anti-Corruption Layer.” martinfowler.com.
  4. Hohpe, Gregor; Woolf, Bobby. Enterprise Integration Patterns. Addison-Wesley, 2003.
  5. Newman, Sam. Building Microservices. 2nd ed., O’Reilly, 2021.
  6. 张逸.《解构领域驱动设计》. 人民邮电出版社, 2021.
  7. Richardson, Chris. Microservices Patterns. Manning, 2018.
  8. Gamma, Erich 等. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley, 1994.

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2026-04-13 · architecture

【系统架构设计百科】复杂性管理:架构的核心战场

系统复杂性是架构腐化的根源——本文从 Brooks 的本质复杂性与偶然复杂性划分出发,结合认知负荷理论与 Parnas 的信息隐藏原则,系统阐述复杂性的来源、度量与控制手段,并给出可操作的架构策略

2026-04-13 · architecture

【系统架构设计百科】DDD 战略设计:限界上下文与上下文映射

一个中型电商系统里,"订单"在交易团队意味着"待支付的购物车快照",在物流团队意味着"等待拣货的配送单",在财务团队意味着"一条应收账款记录"。三个团队共用同一张 torder 表、同一个 OrderService 类,每次迭代都互相踩脚。这种混乱的根源不是代码质量,而是缺少一项最基本的架构决策——限界上下文(Boun…

2026-04-13 · architecture

【系统架构设计百科】DDD 战术模式:聚合、实体与值对象

某团队在实施领域驱动设计时,把整个"订单"建模为一个聚合根(Aggregate Root),其中包含订单基本信息、所有订单行、配送信息、支付记录、物流轨迹、评价数据。结果这个聚合加载一次需要从 7 张表联查,保存一次需要锁定整个订单树。并发下单高峰期,数据库锁等待飙升至秒级。这就是典型的"大聚合"反模式——聚合的边界画…

2026-04-13 · architecture

【系统架构设计百科】DDD 与微服务:用领域模型划分服务边界

某电商团队按数据库表拆分微服务——用户服务管 tuser,商品服务管 tproduct,订单服务管 torder。看起来边界清晰,实际运行中却发现:下单需要同步调用商品服务查价格、调用库存服务检查库存、调用优惠服务算折扣、调用用户服务查地址,一个下单请求扇出 4 次 RPC,任意一个服务超时整条链路就失败。这种"一实体…


By .