Application Service(應用服務)

是什麼?

Application Service 是 DDD 分層架構中**應用層(Application Layer)**的核心元件。它負責接收外部請求、協調 Domain Model 和 Infrastructure 完成一個完整的 Use Case,但不包含業務規則。

ℹ️薄服務原則

Application Service 必須是「薄」的——如果你發現 Service 裡有 if-else 在判斷業務規則,那些邏輯應該搬進 Domain Model。

核心觀念

Use Case 導向

每個公開方法對應一個用例。方法名稱反映業務操作,例如 PlaceOrderCancelOrder,而不是 UpdateOrderStatus

協調者,不是執行者

Application Service 的工作模式是固定的:

  1. 從 Repository 取出 Aggregate
  2. 呼叫 Aggregate 上的業務方法
  3. 把結果存回 Repository
  4. 發送 Domain Event(如果有的話)

交易邊界

Application Service 通常是交易(Transaction)的起點和終點,確保一個 Use Case 的所有操作要嘛全成功、要嘛全失敗。

常見誤區

⚠️Fat Service 壞味道

把業務邏輯塞進 Application Service 是最常見的錯誤。當你看到 Service 裡有「如果訂單金額大於 1000 就打九折」這種邏輯,它應該在 Domain Model 裡,不是在 Service 裡。

協調流程

1

接收請求

從 Controller/Handler 接收 Command 或 DTO

2

載入 Aggregate

透過 Repository 取出需要操作的領域物件

3

執行業務操作

呼叫 Aggregate 上的方法(邏輯在 Domain 裡)

4

持久化結果

透過 Repository 儲存變更後的 Aggregate

5

發布事件

發送 Domain Event 通知其他 Bounded Context

程式碼範例

C#

csharp
// Application Service — 薄,只做協調
public class OrderApplicationService
{
    private readonly IOrderRepository _orderRepo;
    private readonly IProductRepository _productRepo;
    private readonly IDomainEventPublisher _eventPublisher;
 
    public OrderApplicationService(
        IOrderRepository orderRepo,
        IProductRepository productRepo,
        IDomainEventPublisher eventPublisher)
    {
        _orderRepo = orderRepo;
        _productRepo = productRepo;
        _eventPublisher = eventPublisher;
    }
 
    // 一個方法 = 一個 Use Case
    public void PlaceOrder(PlaceOrderCommand command)
    {
        // 1. 載入需要的領域物件
        var product = _productRepo.GetById(command.ProductId);
 
        // 2. 呼叫 Domain Model 的業務方法(邏輯在 Order 裡)
        var order = Order.Create(
            customerId: command.CustomerId,
            product: product,
            quantity: command.Quantity
        );
 
        // 3. 持久化
        _orderRepo.Save(order);
 
        // 4. 發布事件
        _eventPublisher.Publish(new OrderPlacedEvent(order.Id));
    }
 
    public void CancelOrder(Guid orderId, string reason)
    {
        var order = _orderRepo.GetById(orderId);
        order.Cancel(reason); // 業務邏輯在 Aggregate 裡
        _orderRepo.Save(order);
        _eventPublisher.Publish(new OrderCancelledEvent(orderId, reason));
    }
}
 
// Domain Model — 業務邏輯在這裡
public class Order
{
    public static Order Create(Guid customerId, Product product, int quantity)
    {
        if (quantity <= 0)
            throw new DomainException("Quantity must be positive");
        if (!product.IsAvailable)
            throw new DomainException("Product is not available");
 
        var total = product.Price * quantity;
        if (total > 1000) total *= 0.9m; // 折扣邏輯在 Domain 裡
 
        return new Order(customerId, product.Id, quantity, total);
    }
}

TypeScript

typescript
// Application Service
class OrderApplicationService {
  constructor(
    private orderRepo: OrderRepository,
    private productRepo: ProductRepository,
    private eventPublisher: DomainEventPublisher
  ) {}
 
  async placeOrder(command: PlaceOrderCommand): Promise<void> {
    const product = await this.productRepo.getById(command.productId);
 
    // 業務邏輯在 Order.create 裡
    const order = Order.create(
      command.customerId,
      product,
      command.quantity
    );
 
    await this.orderRepo.save(order);
    await this.eventPublisher.publish(new OrderPlacedEvent(order.id));
  }
 
  async cancelOrder(orderId: string, reason: string): Promise<void> {
    const order = await this.orderRepo.getById(orderId);
    order.cancel(reason); // 業務邏輯在 Aggregate 裡
    await this.orderRepo.save(order);
    await this.eventPublisher.publish(new OrderCancelledEvent(orderId, reason));
  }
}

Python

python
class OrderApplicationService:
    def __init__(
        self,
        order_repo: OrderRepository,
        product_repo: ProductRepository,
        event_publisher: DomainEventPublisher,
    ):
        self._order_repo = order_repo
        self._product_repo = product_repo
        self._event_publisher = event_publisher
 
    def place_order(self, command: PlaceOrderCommand) -> None:
        product = self._product_repo.get_by_id(command.product_id)
 
        # 業務邏輯在 Order.create 裡
        order = Order.create(
            customer_id=command.customer_id,
            product=product,
            quantity=command.quantity,
        )
 
        self._order_repo.save(order)
        self._event_publisher.publish(OrderPlacedEvent(order.id))
 
    def cancel_order(self, order_id: str, reason: str) -> None:
        order = self._order_repo.get_by_id(order_id)
        order.cancel(reason)  # 業務邏輯在 Aggregate 裡
        self._order_repo.save(order)
        self._event_publisher.publish(OrderCancelledEvent(order_id, reason))

結構圖

Controller / Handler
呼叫 Use Case
Application Service
載入 / 儲存
Repository
持久化
Aggregate (Domain)
Domain Event Publisher
Database

此圖展示 Application Service 的「導演」角色:它接收 Controller 的請求,從 Repository 載入 Aggregate,呼叫 Aggregate 的業務方法,儲存結果,發布事件。注意 Application Service 不直接碰 Database,也不包含業務邏輯——它只做串接和協調。

Application Service vs Domain Service vs Infrastructure Service

| 類型 | 職責 | 放在哪一層 | 範例 | |------|------|-----------|------| | Application Service | 協調用例流程:取出→操作→儲存→發事件 | Application 層 | PlaceOrderHandler | | Domain Service | 封裝跨 Aggregate 的業務規則,不屬於任何單一 Entity | Domain 層 | TransferMoneyService(從帳戶 A 轉到帳戶 B,涉及兩個 Aggregate) | | Infrastructure Service | 封裝基礎設施操作 | Infrastructure 層 | SmtpEmailServiceS3FileStorage |

判斷邏輯是否該在 Application Service 裡的方法:拿掉所有 if-else 後,Service 的方法是否還能正常運作?如果可以,那些 if-else 就是業務邏輯,搬到 Domain Model 或 Domain Service。

實戰補充

ℹ️資深開發者心得

判斷 Application Service 是否太胖的簡單方法:如果拿掉所有 if-else 後 Service 還能運作,那些 if-else 就應該在 Domain 裡。 Application Service 的流程應該是線性的:取出、操作、儲存、發事件。

💡與 Domain Service 的區別

Application Service 協調用例流程,Domain Service 封裝跨 Aggregate 的業務規則。如果邏輯需要多個 Aggregate 協作且包含業務判斷,那是 Domain Service 的職責,不是 Application Service。

理解測驗

🤔 Application Service 的核心職責是什麼?

🤔 如果你在 Application Service 裡看到「如果 VIP 客戶則免運費」這段邏輯,正確的做法是?

🤔 Application Service 和 Domain Service 的關鍵差異是?

重點整理

💡一句話記住

口訣:「取出、操作、儲存、發事件」—— Application Service 的四步萬能公式,多一步就是越權。

| 概念 | 說明 | |------|------| | 薄服務 | Service 只做協調,業務邏輯在 Domain Model | | Use Case 導向 | 每個公開方法對應一個業務操作 | | 固定模式 | 取出 → 操作 → 儲存 → 發事件 | | 交易邊界 | Service 是 Transaction 的起點和終點 | | 判斷標準 | 拿掉 if-else 後 Service 還能運作 → 那些邏輯應搬到 Domain |

你可能也想看

Repository PatternCQRS

按 ← → 鍵切換課程