---
title: "複雜的操作 - 重新思考 Rails 架構"
date: 2024-07-19T00:00:00+08:00
publishDate: 2024-07-19T00: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/07/19/rethink-rails-architecture-complex-operation/"
language: "zh-tw"
---


現代大部分的軟體都會考慮到使用者體驗（User Experience）因此都會盡量設計成簡單易懂的操作，然而仍有一些例外的狀況，像是有高度專業需求的系統，或者一些傳統產業長久以來的習慣，當時客戶就屬於這一類型。

<!--more-->

## 難以使用{#hard-to-use}

當我們得知畫面上必須一次性的處理四到五種資訊時，就明顯的感受到操作是非常困難的。這是因為在舊系統中，客戶會在同一個畫面中處理出發、抵達時間、運送的貨物等等資訊。

正常狀況下，我們可能會很直覺的拆分成數個小步驟來進行處理，這樣每一個步驟就會變得相對簡單使用起來也會比較容易，然而就當時的狀況而言，因為每個站點的業務量基本上是相當龐大的，比起簡單花時間的操作，能夠一次性完成處理更加適合。

也因此，這就造成了在一個動作中我們需要有大量的檢查確認使用者的行為，裡面還需要根據使用者當地的時區做出不同的呈現，讓整個行為變得非常複雜。

## Form Object

在 Rails 中，只靠 Controller 以及 Model 上的一些驗證機制，是很難解決這樣的問題，因此 Form Object 這一個技巧很常會被 Rails 的開發團隊使用。

比較常見的應用案例，通常會是一些資料上的檢查在不同情境下無法適用。

```ruby
class CreditCard < ApplicationRecord
  validate :sufficient_balance

  private

  def sufficient_balance
    return if balance.positive?

    errors.add(:balance, :insufficient_balance)
  end
end
```

以上面的「餘額」情境，假設我們要增加一個「預借」功能時，這個檢查就會在另一個功能造成問題，因此會用兩個不同的 Form Object 處理。

```ruby
class PayWithCashForm < ApplicationForm
  validate :sufficient_balance

  # ...
end

class PayWithCreditForm < ApplicationForm
  # no balance check
end
```

這個情境也適用於上述的複雜表單，因為同樣的資料在不同畫面的檢驗條件可能是不一樣的（如：角色不同）使用 Form Object 就能很好的做出區分。

## 自動化{#automation}

設計一個系統應該是要有更多的「自動化」在大部分的時候只需要提供足夠的資訊，系統就能夠完成適當的判斷並把事情做完。

也因此，雖然上述的情境我們可以用 Form Object 把使用者輸入的檢查自動化，最後還是仰賴使用者完成大部分的資料輸入，假設驗證發生錯誤時，提示訊息不完整還是會卡住使用者。

因此，延伸上一篇提到的「脈絡」概念，在這階段的設計上，我們也沒有去考慮到整個系統的運作流程，因此錯失了許多可以改善、簡化的地方，也讓「檢查」這件事情變得複雜。

舉例來說，在 Rails 中提供了一種 `accepts_nested_attributes_for` 的機制，可以讓我們針對多層 Model 同時賦予數值，然而不少文章都不推薦使用。更恰當的方式，應該是在操作的對象身上，提供一些方法根據設計好的行為修改這些相關的物件。

這樣一來，一部分的「驗證」也就不需要再去處理，因為這些檢查是根據行為判斷是否能被執行，另一方面根據這些可用的行為，我們也能設計更多輔助使用者處理的機制，來讓工作更輕鬆，而不是用複雜的表單畫面操作以及以輸入的資料做檢查。

