微服务边界划分

以 DDD 限界上下文为语义边界,对齐数据所有权、发布节奏与团队协作;让 Agent 产出可评审的服务候选、上下文映射草稿与「暂缓拆分」依据,避免分布式单体与为拆而拆。

SKILL 应要求每个候选服务写明:所属限界上下文、拥有的聚合与存储、对外暴露的查询/命令与事件,以及与其它上下文的协作关系(含防腐层或共享内核等显式决策)。

禁止两个团队各自「写」同一聚合根而不经明确流程:要么单一写入方 + 其它方只读副本或事件投影,要么承认强一致链路并评估是否暂不拆服务。

DDD 与限界上下文

限界上下文(Bounded Context)是语义一致性的边界:在同一上下文内,术语与规则统一(通用语言);跨边界则允许同名不同义,必须通过显式翻译(DTO、ACL、集成契约)衔接。

  • 先画业务能力与子域(核心 / 支撑 / 通用),再落到上下文,而不是按技术层(Controller 一层一个服务)硬切。
  • 上下文比「微服务」更稳定:服务可合并或拆分,但领域边界变化应伴随模型与语言的重议。
  • Agent 输出中应能指出:每个上下文的通用语言词条样例与「禁止外泄」的内部不变量。
# 领域词汇表冲突检测(Python 脚本示例)
# 跨上下文中相同词汇有不同含义 → 边界信号

GLOSSARIES = {
    "ordering": {
        "customer": "下单的用户,含收货地址与支付方式",
        "product":  "商品快照,包含下单时的价格",
        "order":    "用户提交的购买意图,包含多个 OrderItem",
    },
    "inventory": {
        "product":  "库存中的物理商品,含 SKU 与仓库位置",
        "quantity": "可用库存量,不含已预留",
    },
    "billing": {
        "customer": "财务账户持有人,含发票信息",
        "invoice":  "账期内的账单汇总",
    },
}

def detect_term_conflicts(glossaries: dict) -> list[dict]:
    """检测跨上下文中相同词汇的语义差异"""
    term_contexts: dict[str, list] = {}
    for ctx, terms in glossaries.items():
        for term in terms:
            term_contexts.setdefault(term, []).append(ctx)

    conflicts = []
    for term, contexts in term_contexts.items():
        if len(contexts) > 1:
            definitions = {c: glossaries[c][term] for c in contexts}
            conflicts.append({
                "term": term,
                "contexts": contexts,
                "definitions": definitions,
                "recommendation": f"'{term}' 跨上下文语义不同 → 需通过 ACL/DTO 翻译,不共享同一模型类"
            })
    return conflicts

conflicts = detect_term_conflicts(GLOSSARIES)
for c in conflicts:
    print(f"⚠ 冲突词汇: {c['term']}")
    print(f"  出现在: {', '.join(c['contexts'])}")
    for ctx, defn in c['definitions'].items():
        print(f"  [{ctx}] {defn}")
    print(f"  建议: {c['recommendation']}")

微服务与上下文的对应关系

常见起点是一个限界上下文对应一个部署单元,但并非教条:过小会导致运维与发布碎片化;过大则易成「分布式单体」。对齐标准是变更频率、团队归属与事务一致性需求,而非类包或目录结构。

倾向拆成独立服务

  • 不同发布节奏与扩展特征(读多写少 vs 批处理峰值)
  • 清晰的数据所有权与单一写入路径
  • 团队可端到端负责该上下文内的交付

倾向保留同进程 / 同仓库

  • 强一致事务跨多个聚合且无法接受最终一致窗口
  • 高频、细粒度同步调用链(需先编排、BFF 或合并边界)
  • 尚未澄清的领域模型,仅有「按表拆服务」的冲动

数据所有权与聚合边界

表或集合应归属单一服务;其它服务通过 API、只读副本、CDC 或领域事件获取数据,而不是直连对方库。聚合是事务与不变量边界:一个事务内修改的实体集合应能由单一聚合根串起来。

  • 跨聚合用最终一致(事件、Outbox、Saga);需要分布式事务时先在 SKILL 中标注成本与替代方案。
  • 共享只读维表可抽「通用子域」或复制到各上下文并约定更新源,避免隐式共享写。

Agent 检查清单:每个聚合根是否只有一个写入服务?跨服务「更新同一业务事实」是否已改为事件或明确的主数据归属?

上下文映射与协作模式

上下文映射描述上下游依赖与权力关系:谁定义模型、谁适配、是否共享内核。选错模式会导致隐性耦合或重复建设;SKILL 中应写出模式名与落地方式(例如防腐层所在服务、契约测试责任方)。

  • 客户 / 供应商(Customer-Supplier):下游需求进入上游排期;适合有内部「产品化」上游模型的场景。
  • 遵奉者(Conformist):下游完全采纳上游模型,成本是失去独立演进空间。
  • 防腐层(ACL):下游隔离外来模型,适合外部系统或遗留大包 API。
  • 共享内核(Shared Kernel):小范围共享代码/模型,需严格门禁与版本纪律。
  • 开放主机服务(OHS)+ 发布语言(PL):上游提供稳定集成面与文档化契约。
  • 各行其道(Separate Ways):集成成本高于重复时,允许有限重复并记录理由。
# 反腐层(ACL)实现:翻译外部 API 响应为内部领域模型
# 场景:Billing 上下文调用外部支付网关,用 ACL 隔离外部模型泄漏

from dataclasses import dataclass
from decimal import Decimal

# ===== 外部支付网关响应(上游模型,不可控)=====
class StripePaymentIntent:
    def __init__(self, data: dict):
        self.id = data["id"]
        self.amount = data["amount"]          # Stripe: 分为单位
        self.currency = data["currency"]      # Stripe: 小写 "cny"
        self.status = data["status"]          # Stripe: "succeeded"/"requires_payment_method"
        self.customer = data.get("customer")  # Stripe: customer ID

# ===== 内部领域模型(Billing BC)=====
@dataclass
class PaymentResult:
    payment_id: str
    amount: Decimal                           # 内部: 元为单位
    currency: str                             # 内部: 大写 "CNY"
    status: str                               # 内部: "SUCCESS"/"FAILED"/"PENDING"
    customer_id: str | None

# ===== 防腐层:翻译器 =====
class StripePaymentACL:
    STATUS_MAP = {
        "succeeded": "SUCCESS",
        "requires_payment_method": "FAILED",
        "processing": "PENDING",
        "requires_action": "PENDING",
    }

    def translate(self, stripe_intent: StripePaymentIntent) -> PaymentResult:
        """将 Stripe 外部模型翻译为 Billing 内部 PaymentResult"""
        return PaymentResult(
            payment_id=stripe_intent.id,
            amount=Decimal(stripe_intent.amount) / 100,  # 分→元
            currency=stripe_intent.currency.upper(),      # cny→CNY
            status=self.STATUS_MAP.get(stripe_intent.status, "UNKNOWN"),
            customer_id=stripe_intent.customer,
        )

# 使用:外部模型只在 ACL 层出现,不泄漏到 Billing 内部逻辑
acl = StripePaymentACL()
external_data = {"id": "pi_abc", "amount": 9950, "currency": "cny",
                 "status": "succeeded", "customer": "cus_123"}
internal = acl.translate(StripePaymentIntent(external_data))
# → PaymentResult(payment_id='pi_abc', amount=Decimal('99.50'),
#                 currency='CNY', status='SUCCESS', customer_id='cus_123')
# Pact 契约测试:Consumer 端 Provider test 示例(Python pact-python)
# 场景:OrderService(consumer) 调用 InventoryService(provider)

# 1. Consumer 端:定义期望(生成 pact 文件)
from pact import Consumer, Provider

pact = Consumer("OrderService").has_pact_with(Provider("InventoryService"))

def test_reserve_inventory_consumer():
    (pact
     .given("SKU sku-001 has 100 units available")
     .upon_receiving("a reserve inventory request")
     .with_request("POST", "/inventory/reserve",
                   body={"skuId": "sku-001", "quantity": 2, "orderId": "order-88421"})
     .will_respond_with(200, body={
         "reservationId": pact.like("res-uuid-abc"),
         "skuId": "sku-001",
         "quantity": 2,
         "expiresAt": pact.like("2024-03-15T11:00:00Z"),
     }))

    with pact:
        # 实际调用 consumer 代码,pact 启动 mock server
        result = inventory_client.reserve("sku-001", 2, "order-88421")
        assert result["quantity"] == 2

# 2. Provider 端:验证 pact 文件(在 CI 中运行)
# pact-verifier --provider-base-url=http://localhost:8081 \
#               --pact-broker-url=https://pact.acme.com \
#               --provider=InventoryService \
#               --publish-verification-results \
#               --provider-version=$(git rev-parse HEAD)

从领域到服务候选(工作流)

  [ 事件风暴 / 领域叙事 ]
        │
        ▼
  ┌─────────────────┐     产出:动词命令、名词、热点与争议术语
  │ 识别子域与核心域 │
  └─────────────────┘
        │
        ▼
  ┌─────────────────┐     每个 BC:语言表、聚合草图、对外能力
  │ 划定限界上下文   │
  └─────────────────┘
        │
        ▼
  ┌─────────────────┐     标注:CS / ACL / OHS / Shared Kernel …
  │  上下文映射     │
  └─────────────────┘
        │
        ▼
  ┌─────────────────┐     表:服务 | 拥有数据 | API / 事件 | 风险
  │ 服务候选与暂缓   │──── 暂缓:强一致链、批处理、技术债、联调地狱
  └─────────────────┘

输出物建议包含:上下文列表、映射图(文字或 Mermaid)、服务候选表、以及「不拆」或「合并」的条目与触发条件(例如调用深度阈值、团队拓扑变化)。

对外 API 面与同步链

每个服务对外暴露的应是稳定、版本化的契约(REST/GraphQL/gRPC/消息契约);内部实现细节不穿越边界。深度同步调用栈是边界过细或职责错位的信号:优先考虑编排、异步消息或 BFF 聚合读。

  • 读模型可经 BFF 或 API Gateway 组合,写路径保持单一归属以避免双写。
  • 契约测试与消费者驱动契约(CDC)责任方应在映射中写清,支撑独立部署。

反模式与康威定律

分布式单体:许多小服务但部署、发布、数据库变更仍强耦合,联调成本不低于单体。按层切服务(纯 DAO 服务、纯网关服务)往往放大扇出与延迟。

康威定律:系统结构趋向组织沟通结构。边界与团队拓扑不一致时,要么调整团队(特性团队对齐上下文),要么承认边界将被人为穿透并在 SKILL 中记录治理措施(接口人、评审门槛)。

暂缓拆分清单

SKILL 应显式列出暂不拆或应合并的信号,避免「为了微服务而微服务」。

  • 无法在业务上接受最终一致的跨聚合流程,且尚无可靠 Saga / 补偿设计。
  • 批处理、报表、迁移作业与在线路径强绑在同一事务或同一锁粒度。
  • 两个候选服务由同一小组每日联调,且无独立发布诉求。
  • 缺少契约测试、特性开关、可观测性与回滚策略时的贸然切分。
# Istio VirtualService 流量规则示例(服务网格)
# 场景:payment-service v2 灰度发布,5% 流量切新版本
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: payment-service
  namespace: production
spec:
  hosts:
    - payment-service
  http:
    # 金丝雀:标头路由(内测用户走 v2)
    - match:
        - headers:
            x-canary-user:
              exact: "true"
      route:
        - destination:
            host: payment-service
            subset: v2
    # 流量分割:5% 走 v2,95% 走稳定版
    - route:
        - destination:
            host: payment-service
            subset: v1
          weight: 95
        - destination:
            host: payment-service
            subset: v2
          weight: 5
      retries:
        attempts: 3
        perTryTimeout: 2s
        retryOn: "5xx,connect-failure,reset"
      timeout: 10s
---
# Saga vs 2PC 选择标准:
# 2PC(两阶段提交):
#   适合:同一数据库/支持 XA 的资源管理器;延迟可接受
#   代价:协调者是单点;锁持有时间长;不跨异构系统
# Saga:
#   适合:跨微服务/异构数据库;可接受最终一致
#   代价:需设计补偿逻辑;可能出现部分提交中间状态
# 决策规则:跨服务 → Saga;同数据库强一致 → 2PC 或本地事务

上下文映射草稿实验室

选择经典上下文映射关系,填写上下游上下文名称与可选说明;可多次「追加一行」累积到下方文本框,便于粘贴进设计文档或 SKILL 附录。

箭头约定:上游 → 下游(依赖方向)。正式制图可用 Mermaid flowchart 或团队统一的上下文映射图例;本实验室仅生成文字草稿。

---
name: microservice-boundaries
description: 从 DDD 限界上下文推导服务边界、ACL 实现与契约测试
---
# 边界识别
词汇冲突检测: 同一术语在两个上下文含义不同 → 需 ACL 翻译
子域分类: 核心域(竞争力)/支撑域/通用域 → 决定自研还是外购
每个 BC: 通用语言词条样例 + 禁止外泄的内部不变量列表
# 上下文映射
CS: 下游需求进入上游排期(内部产品化上游)
ACL: 下游边界内翻译(外部系统/遗留 API)→ 实现见示例代码
OHS+PL: 上游提供稳定集成面 + schema 文档(对外公开 API)
SK: 小范围共享,需双方评审 + 同步发布
# 契约测试(Pact)
Consumer 侧: 定义期望 → 生成 pact 文件 → 本地 mock 验证
Provider 侧: CI 中 pact-verifier 验证 + 发布验证结果到 Broker
# 分布式事务
Saga: 跨服务/异构DB → 补偿顺序与正向相反,语义化
2PC: 同DB/XA资源 → 同一事务但协调者单点,不跨异构
# 服务网格(Istio)
VirtualService: 金丝雀/权重分流/超时重试配置
subset: 通过 DestinationRule 定义版本标签

返回技能库 更多技能入口