Bounded Context(限界上下文)

是什麼?

Bounded Context 是 DDD 戰略設計的核心概念。它定義了一個語意邊界,在這個邊界內,所有的術語(Ubiquitous Language)都有明確且一致的定義。不同的 Bounded Context 可以對同一個現實世界概念有不同的模型。

ℹ️DDD 戰略設計

Bounded Context 是 DDD 戰略設計(Strategic Design)的基礎。如果說 Entity、Value Object 是「怎麼寫程式」,Bounded Context 就是「怎麼切系統」。

核心觀念

常見誤區

⚠️常見誤區

誤區一:試圖建立一個「統一的 Domain Model」跨越整個系統。這會導致模型越來越臃腫,充滿 if-else 來處理不同場景。每個 BC 有自己的模型才對。

誤區二:把 Bounded Context 等同於微服務。BC 是邏輯邊界,微服務是部署邊界。一個 BC 可以拆成多個微服務,也可以多個 BC 部署在同一個服務中。

誤區三:BC 之間共享資料庫表。每個 BC 應該擁有自己的資料儲存,避免透過資料庫造成隱性耦合。

劃分流程

1

識別子領域

分析業務,找出核心域、支撐域、通用域

2

發現語意分歧

找出同名但不同義的概念(如 Product 在銷售和倉儲的差異)

3

劃定邊界

把語意一致的概念劃入同一個 Bounded Context

4

定義上下文地圖

畫出各 BC 之間的關係(Context Map)

5

設計整合方式

決定 BC 之間用 API、Events、Shared Kernel 等方式溝通

程式碼範例

C# 版本

csharp
// === 銷售 BC (Sales Context) ===
namespace Sales.Domain
{
    // 銷售 BC 的 Customer:有購買紀錄和聯絡方式
    public class Customer : Entity<Guid>
    {
        public string Name { get; private set; }
        public Email Email { get; private set; }
        public CustomerTier Tier { get; private set; }   // VIP、一般
        private readonly List<OrderId> _orderHistory = new();
 
        public Customer(Guid id, string name, Email email) : base(id)
        {
            Name = name;
            Email = email;
            Tier = CustomerTier.Regular;
        }
 
        public void UpgradeToVip()
        {
            if (_orderHistory.Count >= 10)
                Tier = CustomerTier.VIP;
        }
    }
}
 
// === 物流 BC (Shipping Context) ===
namespace Shipping.Domain
{
    // 物流 BC 的 Customer:只關心收件資訊
    public class Customer : Entity<Guid>
    {
        public string RecipientName { get; private set; }
        public Address ShippingAddress { get; private set; }
        public PhoneNumber Phone { get; private set; }
 
        public Customer(Guid id, string recipientName, Address address, PhoneNumber phone)
            : base(id)
        {
            RecipientName = recipientName;
            ShippingAddress = address;
            Phone = phone;
        }
    }
}
 
// === Anti-Corruption Layer:轉換不同 BC 的模型 ===
namespace Shipping.Infrastructure
{
    public class SalesCustomerAdapter
    {
        public Shipping.Domain.Customer ToShippingCustomer(
            Sales.Contracts.CustomerDto salesCustomer,
            Address shippingAddress)
        {
            return new Shipping.Domain.Customer(
                id: salesCustomer.Id,
                recipientName: salesCustomer.Name,
                address: shippingAddress,
                phone: new PhoneNumber(salesCustomer.Phone)
            );
        }
    }
}

TypeScript 版本

typescript
// === 銷售 BC ===
// sales/domain/customer.ts
class SalesCustomer {
  constructor(
    public readonly id: string,
    public name: string,
    public email: Email,
    public tier: CustomerTier = CustomerTier.Regular,
    private orderHistory: string[] = [],
  ) {}
 
  upgradeToVip(): void {
    if (this.orderHistory.length >= 10) {
      this.tier = CustomerTier.VIP;
    }
  }
}
 
// === 物流 BC ===
// shipping/domain/customer.ts
class ShippingCustomer {
  constructor(
    public readonly id: string,
    public recipientName: string,
    public shippingAddress: Address,
    public phone: PhoneNumber,
  ) {}
}
 
// === ACL:模型轉換 ===
// shipping/infrastructure/sales-customer-adapter.ts
function toShippingCustomer(
  salesCustomer: SalesCustomerDto,
  address: Address,
): ShippingCustomer {
  return new ShippingCustomer(
    salesCustomer.id,
    salesCustomer.name,
    address,
    new PhoneNumber(salesCustomer.phone),
  );
}

Python 版本

python
# === 銷售 BC ===
# sales/domain/customer.py
class SalesCustomer:
    def __init__(self, id: str, name: str, email: Email):
        self.id = id
        self.name = name
        self.email = email
        self.tier = CustomerTier.REGULAR
        self._order_history: list[str] = []
 
    def upgrade_to_vip(self) -> None:
        if len(self._order_history) >= 10:
            self.tier = CustomerTier.VIP
 
# === 物流 BC ===
# shipping/domain/customer.py
class ShippingCustomer:
    def __init__(self, id: str, recipient_name: str,
                 shipping_address: Address, phone: PhoneNumber):
        self.id = id
        self.recipient_name = recipient_name
        self.shipping_address = shipping_address
        self.phone = phone
 
# === ACL ===
# shipping/infrastructure/sales_customer_adapter.py
def to_shipping_customer(
    sales_customer: SalesCustomerDto, address: Address
) -> ShippingCustomer:
    return ShippingCustomer(
        id=sales_customer.id,
        recipient_name=sales_customer.name,
        shipping_address=address,
        phone=PhoneNumber(sales_customer.phone),
    )

概念圖

Sales BC
定義
Shipping BC
定義
Billing BC
Customer(銷售模型)
Customer(物流模型)
Anti-Corruption Layer

此圖展示三個 BC 如何各自定義「Customer」的不同模型,以及 BC 之間的溝通方式。Sales 和 Shipping 之間透過 ACL 做模型轉換,Sales 和 Billing 之間透過 Domain Event 非同步通訊。這就是 Bounded Context 的核心價值——同名概念各自建模,互不污染。

BC 和微服務的對應策略

| 階段 | 策略 | 說明 | |------|------|------| | 早期/新專案 | 單體 + 模組隔離 | 每個 BC 是一個 namespace/project,共用一個部署單位。先把邊界劃對,以後再拆 | | 中期/需求穩定 | 逐步拆分 | 把最獨立、最常變動的 BC 先拆成獨立服務,其餘留在單體 | | 成熟期/大團隊 | 一個 BC 一個服務 | 每個團隊負責一個 BC,獨立部署、獨立資料庫 |

核心原則:先劃對 BC 邊界,再決定部署方式。先拆微服務但 BC 邊界劃錯,比沒拆更糟——你會得到「分散式單體」(Distributed Monolith)。

實戰補充

💡資深開發者筆記

在 C# 單體應用中,用 namespaceProject 來隔離 Bounded Context 是最實用的做法。例如 Sales.DomainShipping.DomainBilling.Domain 各自是獨立的專案,共用同一個 Solution。

劃分 BC 的一個實用技巧:當你發現一個 Entity 的屬性和行為在不同場景中差異很大時(如 Customer 在銷售和物流),就是需要拆分成不同 BC 的訊號。

BC 之間的資料同步用 Domain Events 或 Integration Events。千萬不要讓兩個 BC 共用同一張資料表 — 這會讓兩邊的模型演化被綁死在一起。

理解測驗

🤔 為什麼「Customer」在銷售 BC 和物流 BC 可以是不同的模型?

🤔 Bounded Context 和微服務的關係是?

🤔 以下哪個做法違反了 Bounded Context 的原則?

重點整理

💡一句話記住

口訣:「BC 是語意的牆、不是伺服器的牆」—— 先劃對邊界再談部署,順序不能反。

| 概念 | 說明 | |------|------| | 語意邊界 | 同名概念在不同 BC 有不同模型 | | 獨立模型 | 每個 BC 有自己的 Entity/VO/Aggregate | | 獨立儲存 | 每個 BC 有自己的資料庫或 Schema | | BC vs 微服務 | 邏輯邊界 vs 部署邊界,可以不對齊 | | ACL | 跨 BC 溝通時用 Anti-Corruption Layer 轉換模型 |

你可能也想看

Domain EventsContext Mapping

按 ← → 鍵切換課程