時區換算 - 重新思考 Rails 架構
在整個系統的開發過程中,因為客戶的服務是世界性的,因此還需要考慮到時區問題。然而只是單純的時區換算並不會造成問題過於複雜,而是在時區的呈現上並不像我們平常理解的那樣。
當地時間
大多數時候,我們在時間的呈現上不外乎會選擇以 UTC(Coordinated Universal Time)或者當地時間(UTC+8)等方式「統一呈現」然而客戶的「當地時間」指的是某個欄位的當地時間。
舉例來說,我們從桃園機場 16:00 出發到沖繩,經過 90 分鐘飛行,在 18:30 抵達。在時間的表示上,實際上會是桃園機場的 16:00(UTC+8)經過 90 分鐘飛行,在 17:30(UTC+8)抵達,但在航空的習慣上都會用「當地時間」描述,因此我們要替換成 18:30(UTC+9)表示。
這就讓大部分後端服務的時間套件很難發揮作用,因為同一個畫面上要呈現的時間都是不一樣的。
Value Object
在這類型的問題 Value Object(值物件)就會是一個很不錯的解決方案,如果你對於 Rails 有一定的開發經驗,大概都會聽過。
簡單來看 Value Object 的職責,就是根據系統的需要定義一些無法用整數、字串等基礎型別去表示的資料,那麼在這個狀況下,上述的時間議題就會變成類似這樣的物件。
1class LocalTime
2 SITE_TIMEZONES = {
3 TPE: 'Asia/Taipei'
4 # ...
5 }.freeze
6
7 attr_reader :hour, :minute, :site
8
9 def initialize(hour, minute, site)
10 @hour = hour
11 @minute = minute
12 @site = site
13 end
14
15 def timezone
16 SITE_TIMEZONES[site]
17 end
18
19 def zone
20 @timezone ||=
21 ActiveSupport::TimeZone.new(timezone)
22 end
23
24 def to_time
25 # this example assumption always same day
26 zone.parse("#{hour}:#{minute}")
27 end
28end
搭配上 ActiveRecord 的 composed_of
機制,一定程度上可以讓維護這些資訊變得容易維護。
1class Order
2 composed_of :depature_time, class_name: "LocalTime",
3 mapping: { depature_hour: :hour, ... }
4 composed_of :arrival_time, class_name: "LocalTime",
5 mapping: { arrival_hour: :hour, ... }
6
7 # ...
8end
9
10order = Order.new(
11 depature_hour: 10,
12 # ...
13)
14
15order.depature_time
16# => #<LocalTime hour=10, ...>
17order.depature_time.to_time
18# => Fri, 26 July 2024 10:00:00.000000000 CST +08:00
資料查詢
大部分的系統很常會有需要查詢、篩選的需求,雖然我們利用 Value Object 的方式解決了呈現、運算上的問題,卻無法解決查詢的需求。
假設我們在台北的工作人員希望知道 10:00(UTC+8)出發的列表,那麼一筆當地時間為 11:00(UTC+9)的資料是不能用下面的 SQL 查詢出來的。
1SELECT * FROM orders WHERE departue_hour = 10 " AND depature_site = 'TPE'
因為我們並不能確定不同時區的 depature_site
和 depature_hour
是相同的,假設需要可以查詢,那麼我們還需要在保存到資料庫時統一轉換成 UTC 時間,才能得到相同的篩選條件。
這一連串的需求處理下來,在當時嘗試了各種方法去處理這些情境,仍然很難的把事情乾淨的處理好,造成維護上非常困難。
如果對這篇文章有興趣,可以透過以下連結繼續閱讀這系列的其他文章。
- 軟體架構的挑戰 - 重新思考 Rails 架構
- 資料驅動設計 - 重新思考 Rails 架構
- 複雜的操作 - 重新思考 Rails 架構
- 時區換算 - 重新思考 Rails 架構
- 報表機制 - 重新思考 Rails 架構
- 通用化功能 - 重新思考 Rails 架構
- ActiveRecord 的限制 - 重新思考 Rails 架構
- 領域驅動設計 - 重新思考 Rails 架構
- 從架構到設計 - 重新思考 Rails 架構
- 重復使用的反思 - 重新思考 Rails 架構
- 釐清脈絡 - 重新思考 Rails 架構
- 劃分邊界 - 重新思考 Rails 架構
- 職責劃分 - 重新思考 Rails 架構
- 架構規劃 - 重新思考 Rails 架構
- 驗收測試 - 重新思考 Rails 架構
- Controller - 重新思考 Rails 架構
- Form - 重新思考 Rails 架構
- Use Case - 重新思考 Rails 架構
- Entity - 重新思考 Rails 架構
- Repository - 重新思考 Rails 架構
- Output - 重新思考 Rails 架構
- Query - 重新思考 Rails 架構
- 可能性 - 重新思考 Rails 架構