---
title: "論 Use Case 和 Domain Service 的差異"
date: 2024-09-11T00:00:00+08:00
publishDate: 2024-09-11T00:00:00+08:00
lastmod: 2024-09-08T16:11:51+08:00
tags: ["MVC","Clean Architecture","Domain-Driven Design","經驗"]
toc: true
permalink: "https://blog.aotoki.me/posts/2024/09/11/discuss-about-usecase-and-domain-service/"
language: "zh-tw"
---


這個問題我個人目前還沒有一個邏輯足夠完整的答案，然而最近思考後認為蠻適合拋出來討論。

會回過頭來思考這個問題，也是因為對 Clean Architecture 的使用足夠熟練，要開始準備來深入了解經常一起應用的 Domain-Driven Design，那就一定會遇到這個問題。

<!--more-->

## MVC 到 Clean Architecture{#mvc-to-clean-architecture}

在進入到 Domain-Driven Design 前，我們從大多數框架都會採用的 MVC 架構發展到 Clean Architecture 開始討論，會相對的容易理解脈絡。

首先，如何實作一個軟體，可以看作是各種類型的條件限制下造成的結果，因此從架構層到設計，都是我們套用各種限制的影響（如：商業需求、系統需求、設計需求）因此，第一步就是將最常使用的 MVC 框架基礎，加入 Clean Architecture 的限制來進行設計。

我們可以先來觀察 MVC 框架，通常在結構上非常單純，基本上就是 Controller 耦合 Model 和 View 來完成輸入到輸出一系列的處理，以單體式（Monolithic）的架構來看，可以算是相當容易開發跟上手的結構。

然而，當系統的功能逐漸增加後，維護上就不那麼容易。因為大部分的功能都在 Controller 上，並且直接耦合在 Model 和 View 而不容易抽換或者調整，此時就迎來如何維護的問題，那麼 Clean Architecture 就是個不錯的方式。

Clean Architecture 透過 [SOLID](https://zh.wikipedia.org/zh-tw/SOLID_(%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E8%AE%BE%E8%AE%A1)) 這類原則，對系統中的分級，讓我們更好區分每種物件所負責的範圍，大多數情況依照作者的案例區分為 Framework、Adapter、Application Business Rule（Use Case）、Enterprise Business Rule（Entity）四個層級就非常足夠。

此時，我會將 MVC 框架中提供的 Model、View、Controller 都視為 Adapter 層級，也就是銜接底層（如：框架、HTTP 伺服器）和商業邏輯（Use Case 和 Entity）的角色。

> 關於這樣區分的因，可以參考我在 2024 年 COSCUP 的演講 [Clean Architecture in Rails](https://www.youtube.com/watch?v=IRM7nPHo_Hk&list=PL0a15hor_DySzSteDdErhwisDekfzPJYQ&index=3&pp=iAQB) 所提及的判斷基準

簡單來說，為了對應逐漸變複雜的情境，我們將 Model、View、Controller 這三個物件的職責做切割，進而出現了 Model 分離出 Entity 和 Presenter、Controller 分離出 Use Case 的情況，在 Rails 的文章，就會看到像是 Service Object 這類概念的出現。

> 這個做法我們可以從「電視遙控器」來看到合理醒，作為 Controller 的遙控器，只有將按鈕轉換成紅外線訊號的功能（Adapter）實際上進行處理的是電視本身設計的機制（Use Case）

## Clean Architecture 到 Domain-Driven Design{#clean-architecture-to-domain-driven-design}

原版的 Domain-Driven Design 是以 Layered Architecture 的方式來區分系統的架構，這個架構基本上和 MVC 架構是相對接近（或者說 MVC 有使用相關的概念）然而在現代系統比較複雜的狀況下，大多會把 Domain-Driven Design 和 Clean Architecture 放在一起討論。

從上一個段落我們可以觀察到，MVC 加入 Clean Architecture 的限制後，為了讓物件的職責和層級明確，可以透過從 Controller 分割出 Use Caes 的這種方式來處理。

那麼在 Domain-Driven Design 中的 Domain Model 範圍中，有一半以上跟 Clean Architecture 所提到的物件是差不多交疊的，我們是否可以直接代換呢？

以 Entity 和衍伸的 Aggregate Root 來看，這樣的解讀並無太大問題。而 Repository 劃分為 Adapter 的方式，也可以歸類成呼叫外部服務的抽象概念。至於 Domain Service 和 Use Case 的對應看起來也相當合理，然而我們又有另一個限制條件在這裡出現了一個可能矛盾的狀況。

Domain-Driven Design 的角度來看，Domain Service 不該知道其他 Domain 的物件，這樣可以極大的減少耦合問題，也有助於在未來轉換成 Microservice 等架構。

但是，我們從 MVC 到 Clean Architecture 的過程中，我們是沒有考慮 Domain 問題的，因此會出現像這樣的情況。

```ruby
class BuildNotificationUseCase
  # ...

  def execute(user_id:, channel_id:, event:)
    user = users.find(user_id) # Users::Entities::User
    channel = channels.find(channel_id) # Notifications::Entities::Channel

	channel.add_notify(user: user, event: event)
    channels.save(channel)
  end
end
```

上述的案例中，我們的 Use Case 同時使用了 `Users` 和 `Notifications` 兩個命名空間（或者視為 Domain）的物件，那麼就違背了 Domain Service 的預期，也就是說事情沒有表面上那麼單純。

> Golang 這類語言有避免循環引用（Circular Dependency）的設計，因此在 `clear_notify(user: user)` 這類例子不會直接傳入 User Entity 更可能是一個 Struct 作為 DTO（Data Transfer Object）來處理

假設要讓 Use Case 與 Domain Service 是相同的條件，我們就需要讓不同領域之間的調度由更前一個階段的 Controller 來負責，此時我們還需要考慮作為 Adapter 呼叫多個 Use Case 是否恰當的問題。

## Bounded Context

我認為比較有可能處理這個問題的方法，可能釐清 Bounded Context 的界線在哪裡。舉例來說，我在一些 Domain-Driven Design 的專案樣板中，看過各種不同的目錄結構，我比較常看到把 Use Case、Repository 切分出來的做法。

以我目前常用的結構，通常像是這樣：

* `app/entities`
	* `app/entities/users`
	* `app/entities/notifications`
* `app/usecases`
* `app/repositories`

因為 Clean Architecture 的思考方式，Repository 是實作 Use Case 的介面，通常會回傳某個 Domain 的 Entity 回去供 Use Case 使用。

以這個方式作為前提思考，我很可能會把 `UserRepository` 理所當然視為 User Domain 的物件，然而 Notification Domain 下其實也有一個被通知對象 User Entity 才對，此時該如何命名這個 Repository 呢？

假設要處理這樣的問題，目錄結構是否會轉變成像是這樣。

* `app/domains/user`
	* `app/domains/user/entity`
	* `app/domains/user/service`
	* `app/domains/user/repository`
* `app/domains/notification`
	* `app/domains/notification/entity`
	* `app/domains/notification/service`
	* `app/domains/notification/repository`

此時每個 Domain 下都會有滿足所有行為的 Entity 以及只針對該 Domain 的使用 Use Case（這邊定義為 Domain Service）還有做為 Adapter 的 Repository，那麼 Notification Domain 下就可以有 `UserRepository` 並且產生該 Domain 之下的 User Entity 出來。

同時 Domain Service 等於 Use Case 的問題可能就可以被滿足，因為在上述的案例中，這個 User 並不是 User Domain 的 User 而是 Notification Domain 下面的「被通知對象」

> 印象中這個區別在一些討論 Domain-Driven Design 的文章也會被提及，然而在實務上還是蠻高的機率會忽略這樣的細節

此時我們可以理解為，一個 API 呼叫會由某個 Controller 作為 Adapter 將外部的格式（JSON、XML、Protobuf）轉換成內部的格式（如：DTO）交給 Domain Service 來處理，再將回傳的結果轉換後回傳。

然而，這是建立在前後端分離的概念上進行的思考。假設要從後端輸出一個完整的畫面，那麼 Controller 所需的資訊就不只一個 Domain Service 呼叫可以提供，也會有跨越不同 Domain 的需求，此時該由 Controller 來協調，還是 Use Case（這邊視為和 Domain Service 是不同的東西）就可能需要再做討論。

也許這就是 Clean Architecture 說「不一定只有四層」的情境，假設對不同 Domain 的調度是一個經常出現的動作，那麼就可能會有一個新的 Layer 出現在架構中。

> 這篇文章主要是紀錄思考跟推理的過程，雖然不是第一次思考，但是每次都有一些新的發現。希望不久後自己能找到一個組夠完整的推導，並且足以在正式的專案中應用。

