Slide 1

Slide 1 text

Clean Architecture in Rails The way to large Rails application

Slide 2

Slide 2 text

蒼時弦也 Software Developer https://blog.aotoki.me @elct9620

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

Single Responsibility Dependency Injection Clean Architecture Clean Architecture in Rails

Slide 6

Slide 6 text

Single Responsibility Dependency Injection Clean Architecture Clean Architecture in Rails

Slide 7

Slide 7 text

Single Responsibility Dependency Injection Clean Architecture Clean Architecture in Rails

Slide 8

Slide 8 text

Single Responsibility Dependency Injection Clean Architecture Clean Architecture in Rails

Slide 9

Slide 9 text

Single Responsibility

Slide 10

Slide 10 text

負責一件事情( ) 關注點分離

Slide 11

Slide 11 text

可能同時有多個功能(計算 + 讀寫)

Slide 12

Slide 12 text

對系統切分的粒度

Slide 13

Slide 13 text

class def end end ; Desk work

Slide 14

Slide 14 text

class def end def = += end def = end def end end ; (amount ) @height amount (amount ); (slot); Desk work up down memory # Standing Desk # ... 1 1

Slide 15

Slide 15 text

class def = end end
 class def = new
 end def = end
 end (amount ); @motor . (amount ) @motor.forward(amount) Motor
 forward Desk
 initialize up 1 Motor 1 # ... # ... # ...

Slide 16

Slide 16 text

class def = end end
 (amount ); Motor
 forward 1 # ... class Desk
 def initialize @motor = Motor.new
 end # ... def up(amount=1) @motor.forward(amount) end
 # ... end

Slide 17

Slide 17 text

class < def end private def end def end end ; ; ; CheckoutController ApplicationController create calculate_discount calculate_amount # ...

Slide 18

Slide 18 text

class end class end class < def end end ; DiscountService CheckoutService CheckoutController ApplicationController create # ...

Slide 19

Slide 19 text

class end class end DiscountService CheckoutService class CheckoutController < ApplicationController def create; end # ... end

Slide 20

Slide 20 text

Dependency Injection

Slide 21

Slide 21 text

由呼叫者提供依賴的物件或函式

Slide 22

Slide 22 text

實踐 ,達到低耦合的程式 關注點分離

Slide 23

Slide 23 text

在 Ruby 可以用 Duck Typing 方式實踐

Slide 24

Slide 24 text

class def = end end
 class def = new
 end def = end
 end (amount ); @motor . (amount ) @motor.forward(amount) Motor
 forward Desk
 initialize up 1 Motor 1 # ... # ... # ...

Slide 25

Slide 25 text

class def = end def = end
 end (motor) @motor motor
 (amount ) @motor.forward(amount) Desk
 initialize up # ... # ... 1

Slide 26

Slide 26 text

class end class end = new new = new new ; ; @standing_desk . ( . ) @electrical_standing_desk . ( . ) Motor ElectricMotor # 手動 # 電動 Desk Motor Desk ElectricMotor

Slide 27

Slide 27 text

class end class end class < def = new end end @discount . @discount.call(@order)
 DiscountService AnniversaryDiscountService CheckoutController ApplicationController create DiscountService # ... # ...

Slide 28

Slide 28 text

class DiscountService end class AnniversaryDiscountService end class CheckoutController < ApplicationController def create @discount.call(@order)
 # ... end # ... end @discount . = new AnniversaryDiscountService

Slide 29

Slide 29 text

class < def = end def = case when then new else new end end end @discount discount_service @discount.call(@order)
 @discount_service .current_store_event . . CheckoutController ApplicationController create discount_service # ... # ... Settings :anniversary AnniversaryDiscountService DiscountService

Slide 30

Slide 30 text

Clean Architecture

Slide 31

Slide 31 text

Architecture Framework Design 粗略 詳細 架構是從宏觀的角度觀察,設計則是從微觀的角度

Slide 32

Slide 32 text

理想狀況 https://www.pinterest.com/pin/205687907955149475/

Slide 33

Slide 33 text

現實狀況 https://knowyourmeme.com/photos/1624064-unfinished-horse-drawing-flaming-horse-rating

Slide 34

Slide 34 text

區分出高階(抽象)跟低階(具體)元件

Slide 35

Slide 35 text

# Which is highest?

 # ... # ... class def end end
 class def end end class < def = end end (amount, ); (order); @discount discount_service
 Order add_discount GeneralDiscountService call CheckoutController ApplicationController create reason:

Slide 36

Slide 36 text

Controller Checkout Service Discount Entity Order 低階 高階 被越多物件引用的物件通常越高階(抽象)

Slide 37

Slide 37 text

https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html

Slide 38

Slide 38 text

越低階的物件越容易跟其他東西耦合

Slide 39

Slide 39 text

class def end end class < def = end end class < def end end (text); res commander.call(...) client.reply(res) TextCommander call LineBotController ApplicationController callback MessengerBotController ApplicationController callback # ... # ...

Slide 40

Slide 40 text

Clean Architecture 嘗試找出不同元件之間的邊界

Slide 41

Slide 41 text

釐清邊界後,如何設計依賴、劃分模組就會有更清晰的方向

Slide 42

Slide 42 text

Clean Architecture in Rails

Slide 43

Slide 43 text

https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html

Slide 44

Slide 44 text

Rails 的 Model、View、Controller 分別屬於哪一層?

Slide 45

Slide 45 text

Controller Presenter Model Entity View Presenter Business Logic

Slide 46

Slide 46 text

https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html Frameworks

Slide 47

Slide 47 text

Model 適合當作 Business Logic 分類成 UseCase、Entity 嗎?

Slide 48

Slide 48 text

Controller Controller Model Gateway View Presenter Rails 的 MVC 更可能是同層級的物件

Slide 49

Slide 49 text

Framework Adapter + Business Logic MVC 框架的設計很容易把框架跟商業邏輯耦合在一起

Slide 50

Slide 50 text

Service Object、Value Object 的出現是嘗試提煉商業邏輯

Slide 51

Slide 51 text

Controller Controller Service UseCase View Presenter Business Logic

Slide 52

Slide 52 text

我們將 Model 視為 Service 的依賴,注入給 Service 使用

Slide 53

Slide 53 text

然而 Entity 不等於 Model,因為 ActiveRecord 跟底層細節耦合

Slide 54

Slide 54 text

Controller Model View UseCase Entity

Slide 55

Slide 55 text

class < def case in in end end end resolve( ).call(connect_params) => result render result.value, => result render result.failure, ConnectController ApplicationController create 'notify.connect' Success json: status: :created Failure json: status: :unprocessable_entity # ...

Slide 56

Slide 56 text

module class < include private def end def ** = new end end end [ ] step try , :: :: (input) ( , , ) notify :: :: . ( token, type) notifies.save(notify) Notify Connect BaseOperation validate create Deps notifies: :validate :create catch: Notify Errors NotifyAlreadyExists token: type: Notify Entities Notify token: type: 'notify_repository' # ...

Slide 57

Slide 57 text

module module class attr_reader def = = end end end end , ( , ) @token token @type type Notify Entities Notify initialize :token :type token: type: # ...

Slide 58

Slide 58 text

# ActiveRecord model # API client class def = new end end class def end end (entity) notify . notify.assign_attributes(entity.to_h) notify.save! (entity) .upsert(entity.to_h) NotifyRepository save NotifyRepository save NotifyConnection NotifierAPI

Slide 59

Slide 59 text

基於這樣的調整,除了擴充更容易外,我們也不再綁定框架

Slide 60

Slide 60 text

以往難以判斷如何拆分物件、將商業邏輯提煉的線索更加清晰

Slide 61

Slide 61 text

這是 Clean Architecture 應用的一小部分,還有許多面向可以發掘

Slide 62

Slide 62 text

Question & Answer https://bit.ly/3YnmmQJ