☕ NEW! 完成新手任務即可參加抽獎!LINE 星巴克禮券等你拿,名額有限!        🎉 推廣活動:邀請好友註冊 DevLearn,累積推薦抽 LINE 星巴克禮券! 活動詳情 →        🔥 活動期間 2026/4/1 - 5/31 |已有 0 人參加       
microservices 初學

📐 微服務設計原則:DDD 與邊界劃分

📌 Domain-Driven Design (DDD) 基礎概念

DDD 是一種軟體設計方法,以業務領域為核心來組織程式碼。在微服務架構中,DDD 幫助我們找到正確的服務邊界。

為什麼 DDD 對微服務這麼重要? 因為微服務拆分的依據不是技術層(Controller、Service、Repository),而是業務邊界


📌 核心概念:通用語言 (Ubiquitous Language)

開發團隊與業務專家使用同一套語言來描述系統:

// ❌ 技術導向的命名
public class DataProcessor
{
    public void ProcessRecord(int recordId) { }
}

// ✅ 業務導向的命名(通用語言)
public class OrderService
{
    public void PlaceOrder(PlaceOrderCommand command) { }
    public void CancelOrder(Guid orderId, string reason) { }
    public void ShipOrder(Guid orderId, ShippingInfo shipping) { }
}

📌 Bounded Context 限界上下文

限界上下文是 DDD 中最重要的概念之一。同一個名詞在不同的上下文中可能有不同的含義。

例子:"產品" 在不同上下文的意義

// ── 商品目錄上下文 (Catalog Context) ──
public class Product
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public decimal ListPrice { get; set; }
    public List<string> Images { get; set; }
    public Category Category { get; set; }
}

// ── 訂單上下文 (Order Context) ──
// 同樣叫 "Product",但只需要訂單相關的資訊
public class OrderItem
{
    public Guid ProductId { get; set; }
    public string ProductName { get; set; }  // 快照,不是引用
    public decimal UnitPrice { get; set; }   // 下單時的價格
    public int Quantity { get; set; }
}

// ── 庫存上下文 (Inventory Context) ──
public class StockItem
{
    public Guid ProductId { get; set; }
    public string Sku { get; set; }
    public int QuantityOnHand { get; set; }
    public int ReorderThreshold { get; set; }
    public string WarehouseLocation { get; set; }
}

重點: 每個限界上下文有自己的 "Product" 模型,只包含該上下文需要的屬性。


📌 聚合根、實體與值物件

聚合根 (Aggregate Root)

聚合根是外部存取聚合的唯一入口,確保業務規則的一致性。

// Order 是聚合根
public class Order
{
    public Guid Id { get; private set; }
    public Guid CustomerId { get; private set; }
    public OrderStatus Status { get; private set; }
    private readonly List<OrderLine> _lines = new();
    public IReadOnlyList<OrderLine> Lines => _lines.AsReadOnly();

    // 業務邏輯都透過聚合根來操作
    public void AddItem(Guid productId, string productName,
                        decimal unitPrice, int quantity)
    {
        if (Status != OrderStatus.Draft)
            throw new InvalidOperationException("只有草稿狀態的訂單可以加入商品");

        var existing = _lines.FirstOrDefault(l => l.ProductId == productId);
        if (existing != null)
        {
            existing.IncreaseQuantity(quantity);
        }
        else
        {
            _lines.Add(new OrderLine(productId, productName, unitPrice, quantity));
        }
    }

    public void Submit()
    {
        if (!_lines.Any())
            throw new InvalidOperationException("訂單至少要有一個商品");

        Status = OrderStatus.Submitted;
        // 發出領域事件
        AddDomainEvent(new OrderSubmittedEvent(Id, CustomerId, GetTotal()));
    }

    public decimal GetTotal() => _lines.Sum(l => l.SubTotal);
}

實體 (Entity) — 有唯一識別的物件

// OrderLine 是實體(有 Id),屬於 Order 聚合
public class OrderLine
{
    public Guid Id { get; private set; }
    public Guid ProductId { get; private set; }
    public string ProductName { get; private set; }
    public decimal UnitPrice { get; private set; }
    public int Quantity { get; private set; }
    public decimal SubTotal => UnitPrice * Quantity;

    public OrderLine(Guid productId, string productName,
                     decimal unitPrice, int quantity)
    {
        Id = Guid.NewGuid();
        ProductId = productId;
        ProductName = productName;
        UnitPrice = unitPrice;
        Quantity = quantity;
    }

    public void IncreaseQuantity(int amount)
    {
        if (amount <= 0) throw new ArgumentException("數量必須大於 0");
        Quantity += amount;
    }
}

值物件 (Value Object) — 用值來比較的物件

// Address 是值物件:沒有 Id,用值來比較
public record Address(
    string Street,
    string City,
    string State,
    string ZipCode,
    string Country)
{
    // C# record 自動實作值比較
    // new Address("信義路", "台北", ...) == new Address("信義路", "台北", ...)
}

// Money 也是值物件
public record Money(decimal Amount, string Currency)
{
    public static Money operator +(Money a, Money b)
    {
        if (a.Currency != b.Currency)
            throw new InvalidOperationException("不同幣別無法直接相加");
        return new Money(a.Amount + b.Amount, a.Currency);
    }
}

📌 如何劃分微服務的邊界

步驟 1:識別業務領域

電商系統的業務領域:
├── 用戶管理(註冊、登入、個人資料)
├── 商品目錄(瀏覽、搜尋、分類)
├── 訂單管理(下單、取消、查詢)
├── 庫存管理(庫存數量、進出貨)
├── 支付處理(付款、退款)
├── 物流配送(出貨、追蹤)
└── 通知服務(Email、SMS、推播)

步驟 2:畫出上下文映射圖 (Context Map)

┌──────────┐    同步呼叫    ┌──────────┐
│ 訂單服務  │ ───────────→ │ 庫存服務  │
│          │              │          │
└────┬─────┘              └──────────┘
     │ 發布事件
     ↓
┌──────────┐              ┌──────────┐
│ 支付服務  │              │ 通知服務  │
│          │              │ (訂閱事件)│
└──────────┘              └──────────┘

📌 資料庫分離策略:Database per Service

// 每個微服務有自己的 DbContext 和資料庫
// ── 訂單服務 ──
public class OrderDbContext : DbContext
{
    public DbSet<Order> Orders { get; set; }
    public DbSet<OrderLine> OrderLines { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder options)
        => options.UseNpgsql("Host=order-db;Database=OrderDb");
}

// ── 庫存服務 ──
public class InventoryDbContext : DbContext
{
    public DbSet<StockItem> StockItems { get; set; }
    public DbSet<Warehouse> Warehouses { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder options)
        => options.UseNpgsql("Host=inventory-db;Database=InventoryDb");
}

黃金法則: 服務之間絕不直接存取對方的資料庫,只透過 API 或事件溝通。


📌 範例:電商系統的服務拆分

服務 職責 資料庫 主要 API
UserService 用戶註冊、登入、Profile PostgreSQL POST /api/users, GET /api/users/{id}
CatalogService 商品 CRUD、搜尋 PostgreSQL + Elasticsearch GET /api/products, GET /api/products/{id}
OrderService 下單、訂單狀態管理 PostgreSQL POST /api/orders, GET /api/orders/{id}
InventoryService 庫存管理、扣減 PostgreSQL POST /api/inventory/reserve, PUT /api/inventory/release
PaymentService 支付、退款 PostgreSQL POST /api/payments, POST /api/refunds
NotificationService 發送通知 MongoDB 透過訊息佇列觸發

下一章: 我們將動手用 ASP.NET Core 建立第一個微服務 API。

💡 大家的想法 · 0

載入中...
💬 即時聊天室 🟢 0 人在線
😀 😎 🤓 💻 🎮 🎸 🔥
➕ 新問題
📋 我的工單
💬 LINE 社群
🔒
需要註冊才能使用此功能
註冊帳號即可解鎖測驗、遊戲、簽到、筆記下載等所有功能,完全免費!
免費註冊