蒼時弦也
蒼時弦也
資深軟體工程師
發表於

架構規劃 - 重新思考 Rails 架構

這篇文章是 重新思考 Rails 架構 系列的一部分。

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

過度設計

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

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

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

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

領域劃分

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

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

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

  • 訂單(Order)
  • 運送(Shipment)
  • 集裝(Container)

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

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

對 Ruby 開發者來說,Private(私有)的概念可能不是那麼常見,然而要保護系統內部資訊的一致,就需要透過私有方法來限制外部存取可用的手段。

元件劃分

元件(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(函式庫)的型態存在,也是相當合理的。