---
title: "Controller - 重新思考 Rails 架構"
date: 2024-10-18T00:00:00+08:00
publishDate: 2024-10-18T00: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/18/rethink-rails-architecture-controller/"
language: "zh-tw"
---


有了第一個 Cucumber 撰寫的測試後，我們對於更新運送狀態的行為有一定的概念，那麼就可以針對這個機制來進行 Controller 的實作。

<!--more-->

## 路由{#route}

首先，要讓 `POST /shipments/:id/routes` 這個端點可以被存取，因此需要在 Rails 的路由設定中加入以下的內容。

```ruby
Rails.application.routes.draw do
  # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html

  # Defines the root path route ("/")
  # root "articles#index"
  resources :shipments, module: :shipments do
    resources :routes, only: [:create]
  end
end
```

這樣一來 Rails 就會知道要使用哪個 Controller 來處理，這是大多數開發者很直覺能實踐的事情，然而在 [Clean Architecture](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html) 的角度分析，這是一個低階元件，也就是描述細節的資訊。

因此會被分類在最外圈 Framework & Drivers 這一階段，這類型的物件通常會跟環境有很深的連結，因此當我們要改為使用 gRPC 之類的方式互動時，這些實作就無法繼續使用，就 Rails 的角色，就只能針對 Web（HTTP 協定）的情境。

## 控制器{#controller}

Controller 想必也不需要特別討論，在這個階段我們要先讓 Cucumber 的測試通過，因此可以直接將輸出寫死。

```ruby
module Shipments
  class RoutesController < ApplicationController
    def create
      render json: {
        id: 1,
        state: 'shipping',
        routes: [
          {
            route_id: 'OKA',
            shipment_id: 1,
            delivered_at: Time.utc(2024, 3, 19, 0, 0, 0)
          },
          {
            route_id: 'TPE',
            shipment_id: 1,
            delivered_at: Time.utc(2024, 3, 19, 2, 0, 0)
          }
        ]
      }
    end
  end
end
```

在應用到 Clean Architecture 時，可能就會有一些爭議出現。因為我們以往的經驗，會把處理的行為設計在 Controller 中，那麼這樣算是一種 Use Case（使用案例）的類型嗎？

然而，在 Clean Architecture 由外向內的第二圈舉例中有 Controller 並且歸類為 Interface Adapter（介面轉接器）的類型，這是否表示我們不該在 Controller 中有實際的商業邏輯呢？

## 反思{#reflection}

意識到這個問題後我做了不少思考，大致上來說這件事情並沒有一個絕對正確的答案，或者說他是一個系統發展過程中的一個階段。

在 Clean Architecture 中並沒有限制切分的層級數，書中以四層為例子確實是大部分情境都通用的情況，那麼 Rails 採用的 Model View Controller（MVC）設計，我認為可以看作是只有兩層的情況，也就是指具備 Framework & Drivers 和 Interface Adapter 的情境。

從另一個角度來看，當下的 Interface Adapter 因為跟實際的 Use Case 處理的內容非常一致，因此也沒有特別要做 Adapt（適應） 的必要，只需要直接使用即可。

然而，從職責的角度來看，隨著在 Controller 中實作的邏輯增長，會開始出現需要拆分職責的情況，剛開始可能會用 Fat Model 的模式來做，然而不論怎樣處理最後還是會有職責過多的狀況，進而需要切分出新的物件類型，而 Service Object 這類 Rails 常見的物件也因此而生。

直到這個階段，才開始出現更向內的第三、第四層級，也才開始有明確的 Use Case 和 Entity（實體）的劃分跟界定。

