用例驅動實現DDD的方法 - codex

banq發表於2022-01-02

根據UML Distilled(第 9 章),用例是由一個共同的使用者目標聯絡在一起的一組場景(banq:特定角色的各種操作場景上下文)。在這種情況下,我們處理的是一種系統用例,它表示使用者角色與系統的之間的互動。

要表達單個互動操作的概念,請在命名用例時以動詞開頭(例如“列出動作”、“提款”、“刪除我的資料”、“支付購物車”)。在實踐中,用例只是一個函式(一個查詢或一個命令),它應該是純粹的——確定性的並且沒有副作用。(banq注:類似事件風暴中的領域事件中命令動作,注意動作是有限制的上下文BC)

用例應該是最高階別和最可見的架構實體。用例是中心。總是!資料庫和框架都是細節!沒有資料庫

 

用例是您的應用程式的工作單元(即其架構原子裝置)。他們應該是一等公民,以便在瀏覽其程式碼庫時應用程式的目標立即變得清晰。儘管這與程式碼組織有關,但最終會促進領域驅動架構的良好實踐。

正如房屋或圖書館的計劃對這些建築物的用例大肆宣傳一樣,軟體應用程式的架構也應該對應用程式的用例大肆宣傳 - 尖叫架構

用例驅動的方法在後端應用程式中更有意義,因為這是業務邏輯應該存在的地方。

 

讓我們以通過 Web API 提供服務的後端應用程式為例:

在領域驅動架構中,Web API 是由 Web 請求處理程式組成的主要介面卡(介面卡是應用程式入口點,不包含業務邏輯)。

在用例驅動的方法中,每個 Web 處理程式只是一個用例入口點——它們具有一對一的關係。因此,每個 web 處理程式都應該有它的實現檔案,包括驗證、解析、(反)序列化、呼叫域、錯誤處理、響應準備、API 文件(例如OpenAPI)、常量等。沒有其他地方可以尋找。一切盡在一處。

class CreateUserHandler(
    private val createUser: CreateUser,
    private val generateUserId: () -> UserId,
) : Handler {

    override fun handle(ctx: Context) {
        val createUserResult = createUser(
            ctx.bodyAsClass(UserRepresenter::class.java)
                .toUser(generateUserId())
        )
        ctx.status(
            when (createUserResult) {
                NewUser -> HttpStatus.CREATED_201
                UserAlreadyExists -> HttpStatus.CONFLICT_409
            }
        )
    }

    private class UserRepresenter(val email: String, val name: String, val password: String) {
        fun toUser(id: UserId) = User(
            email = email.toEmail(), name = name, password = password.toPassword(), id = id
        )
    }
}

 

領域

域是業務操作發生的地方。充滿域操作的“服務”/“集線器”檔案是一個壞主意。相反,為每個用例建立一個檔案並相應地命名它。具有支援用例的單個公共功能的檔案並不是一件壞事。事實上,這是一個很好的想法,因為它是模組化的和有凝聚力的。它應該是自包含的,並且僅依靠輔助介面卡來滿足其需求(例如儲存庫)。也就是說,除了實際的用例功能(包括語義驗證、編排等),在同一檔案中還包括其請求/響應模型、錯誤/異常和任何私有幫助程式。

  

測試

為每個用例建立一個測試檔案。每個測試檔案都會練習一個用例的所有場景。每個測試只需要注入用例的實際依賴項,而不是一堆。這些因素有助於“將測試作為文件”——自動化測試的一個目標——因為它們有助於將用例描述為使用者/客戶。

 

功能模組化

用例驅動的方法促進了用例之間的解耦。由於它有助於發現它們之間的共享程式碼,因此您犯該錯誤的可能性較小。

按用例組織程式碼庫是識別應用程式存在的原因。用例是應用程式的工作單元。一旦開始利用它,您就可以識別特徵、子域和有界上下文。如果有意義,這有助於建立微服務和/或按團隊拆分工作。

。。。。

點選標題見詳細原文

相關文章