---
title: "架構規劃 - 重新思考 Rails 架構"
date: 2024-10-04T00:00:00+08:00
publishDate: 2024-10-04T00:00:00+08:00
lastmod: 2024-06-02T17:03:47+08:00
tags: ["Rails","Domain-Driven Design","設計","Clean Architecture"]
series: "rethink-rails-architecture"
toc: true
permalink: "https://blog.aotoki.me/posts/2024/10/04/rethink-rails-architecture-planning-architecture/"
language: "zh-tw"
---


經過這段時間的分析，我們對於以往在 Rails 開發系統時會遭遇的問題有了近一步理解。接下來，我們要再一次的分析系統的架構，來對原本的設計進行改善。

<!--more-->

## 過度設計{#over-design}

使用像是 Clean Architecture（清楚架構）這類方法來規劃時，我們仍需要考慮過度設計的問題。假設系統本身並不複雜，也沒有立即性的擴充需求，並不一定要像 Clean Architecture 的例子中分為四個層級，只有一個或者兩個也沒問題。

以 Rails 為例子，在沒有特別區分的狀況下 MVC 框架可以看做只有 Framework（框架）跟 Use Case（使用案例）兩個層級。

然而，在系統逐漸複雜的狀況下，單一層級的物件無法負擔不斷擴展的職責，那麼就需要進一步的切分出新的層級來確保職責足夠單純。

> 根據經驗，Clean Architecture 例子中分為四個層級對大多數系統不會太過於複雜，也不會太少的狀態。

## 領域劃分{#domain}

若要將系統規劃清晰，我們可以從「領域（Domain）」和「元件（Component）」兩種不同面向的職責劃分來進行思考。

> 這裡選擇不用「功能」來描述是因為容易混淆，因為可以表示某個領域提供給使用者的功能（Feature）或者在系統運作上扮演的功能（Function）

以物流系統來看，我們大致上會需要處理幾種不同領域的問題

* 訂單（Order）
* 運送（Shipment）
* 集裝（Container）

除此之外還要能夠提供報表相關的機制，因此還會有像是運送報告（ShipmentReport）之類的領域劃分出來。

有了領域的劃分，才能意識到邊界（Boundary）的存在，每個領域都無法直接互相干涉，需要透過特定的物件協調（如：UseCase）來確保不會有預期外的修改操作發生。

> 對 Ruby 開發者來說，Private（私有）的概念可能不是那麼常見，然而要保護系統內部資訊的一致，就需要透過私有方法來限制外部存取可用的手段。
## 元件劃分{#component}

元件（Component）可以看作是一系列讓系統可以運作的零件，每個領域中都會有類似的元件，然而透過不同的元件搭配，就能夠得到不一樣的效果。

我們以「運送」這個領域作為例子，以 Clean Architecture 的角度來看該如何劃分出對應的元件（Framework 就是 Rails 本身，因此不特別列出）

* Interface Adapter
	* ActionController
		* `app/controllers/shipments_controller.rb`
		* ...
	* ActionView
		* `app/views/shipments/new.html.erb`
		* ...
	* ActiveRecord
		* `app/model/shipment.rb`
		* ...
	* ActiveJob
		* `app/jobs/refresh_shipment_state_job.rb`
		* ...
	* Form
		* `app/forms/create_shipment_form.rb`
		* ...
* Use Case
	* Command
		* `app/commands/create_shipment_command.rb`
		* ...
	* Query
		* `app/queries/user_shipment_query.rb`
		* ...
* Entities
	* Shipment
		* `lib/shipment/shipment.rb`
		* `lib/shipment/item.rb`
		* ...
	* Order
		* ...
	* Container
		* ...

若要能夠將 Clean Architecture 的實踐套用到 Rails 上，同時也不違背 Domain-Driven Design 的設計，上述的架構會是相對合理的。即使我們通常會認為 Rails 中的 Model 應該是 Entity 的角色，然而從 ActiveRecord 的設計來看，因為具有 SQL 資料庫的 Adapter 性質，更可能是 Interface Adapter 的一種類型。

另一方面，這也對應到在一開始所提出「系統初期不會過於複雜」這樣的論點，當我們系統複雜度增長的時候，是逐步抽離出高階元件（High-Level Component）來抽象化，而演變出了 Use Case 和 Entity 的類型。

另一方面，高階元件因為抽象程度足夠，也不應該受到框架的限制跟綁定，那麼以 Library（函式庫）的型態存在，也是相當合理的。

