Slide 1

Slide 1 text

蒼時弦也 看網站開發框架 隠藏的細節 從Domain-Driven Design

Slide 2

Slide 2 text

CONSULTANT × SPEAKER 蒼時弦也 YouTube Coding Live Blog https://blog.aotoki.me Discord Discuss about Ruby Introduce About Me

Slide 3

Slide 3 text

What we talk about Overview The Layers The case study Why Domain-Driven Design Why Domain-Driven Design

Slide 4

Slide 4 text

What we talk about Overview The Layers The Layers The case study Why Domain-Driven Design

Slide 5

Slide 5 text

What we talk about Overview The Layers The case study The case study Why Domain-Driven Design

Slide 6

Slide 6 text

Opportunity Why Domain-Driven Design The Opportunity When I try to improve the code base

Slide 7

Slide 7 text

Framework and Design Why Domain-Driven Design Design Architecture Framework

Slide 8

Slide 8 text

Escape Why Domain-Driven Design Framework Ruby / PHP Framework Node.js / Golang Replace

Slide 9

Slide 9 text

Modeling Why Domain-Driven Design The Modeling From data to information

Slide 10

Slide 10 text

Modeling Why Domain-Driven Design Data State Action Modeling Change Object

Slide 11

Slide 11 text

Layered Architecture The Layers Layered Architecture Define the role of your "Class"

Slide 12

Slide 12 text

Presentation Layer The Layers Presentation Layer Transform between "data" and "object"

Slide 13

Slide 13 text

Presentation Layer The Layers Object @product.name Data

Product

Render Output (View) Rails View

Slide 14

Slide 14 text

Presentation Layer The Layers Object @product.name Data {"name": "Product"} Serialize Output (View) Rails View

Slide 15

Slide 15 text

Presentation Layer The Layers Data {"name":"Product"} Object params[:name] Deserialize Input (Param) Rails Controller

Slide 16

Slide 16 text

Application Layer The Layers Application Layer Define the flow of "action"

Slide 17

Slide 17 text

Application Layer The Layers Input User Flow Order.create! Payment.create! OrderMailer.confirmation

Slide 18

Slide 18 text

Application Layer The Layers Input User Flow Order.create! Payment.create! OrderMailer.confirmation Exception PaymentGatewayUnavailable Raise

Slide 19

Slide 19 text

Application Layer The Layers Input User Flow Order.create! Payment.create! OrderMailer.confirmation Input User Flow Order.find_or_create_by! Payment.create! OrderMailer.confirmation Idempotent

Slide 20

Slide 20 text

Domain Layer The Layers Domain Layer The "logic" related business

Slide 21

Slide 21 text

Domain Layer The Layers Period begin=2022-09-01, end=2022-10-31 Call in? 2022-09-30 Return true Value Object

Slide 22

Slide 22 text

Domain Layer The Layers Data State Action Modeling Change Entity

Slide 23

Slide 23 text

Domain Layer The Layers ShippingService#create_ticket!(order) if response.success? True order.shipping_id = response.id False raise ShippingProviderUnavailable Service Object

Slide 24

Slide 24 text

Infrastructure Layer The Layers Infrastructure Layer The way to interact with "data"

Slide 25

Slide 25 text

Infrastructure Layer The Layers PostgresOrderRepository implement IOrderRepository Write Read Orders Table Repository

Slide 26

Slide 26 text

Infrastructure Layer The Layers ShopifyOrderRepository implement IOrderRepository Call Shopify Order API Write Read Orders Table Repository

Slide 27

Slide 27 text

Testing The Layers Add Tests The reasonable way to test

Slide 28

Slide 28 text

Testing The Layers shipped.json.jbuilder ShippingController Order OrderRepository UserRepository Shipping ShippingRepository TicketRepository DHL::Client E2E Testing

Slide 29

Slide 29 text

Testing The Layers shipped.json.jbuilder ShippingController Order OrderRepository UserRepository Shipping ShippingRepository TicketRepository DHL::Client E2E Testing

Slide 30

Slide 30 text

Testing The Layers shipped.json.jbuilder ShippingController Order OrderRepository UserRepository Shipping ShippingRepository TicketRepository DHL::Client Unit Test

Slide 31

Slide 31 text

Testing The Layers shipped.json.jbuilder ShippingController Order OrderRepository UserRepository Shipping ShippingRepository TicketRepository DHL::Client Unit Test

Slide 32

Slide 32 text

Testing The Layers shipped.json.jbuilder ShippingController Order OrderRepository UserRepository Shipping ShippingRepository TicketRepository DHL::Client Workable Feature

Slide 33

Slide 33 text

Input Object Case Study Input Object How to "validate" the inputs

Slide 34

Slide 34 text

Input Object Case Study Input Profile @user.assign_attributes(params) @user.valid! @user.save!

Slide 35

Slide 35 text

Input Object Case Study Input Register @user = User.new(params) @user.valid! @user.save!

Slide 36

Slide 36 text

Input Object Case Study Input Register @form.valid! @user = User.new(@form.attributes) @user.save!

Slide 37

Slide 37 text

Input Object Case Study # Ruby on Rails validates :email, :password, presence: true validates :email, format: /\A[^@\s]+@[^@\s]+\z/ class RegisterForm include ActiveModel::API include ActiveModel::Validation attribute :email, :string attribute :password, :string end

Slide 38

Slide 38 text

Input Object Case Study # Ruby on Rails validates :email, :password, presence: true validates :email, format: /\A[^@\s]+@[^@\s]+\z/ class RegisterForm include ActiveModel::API include ActiveModel::Validation attribute :email, :string attribute :password, :string end validate :email_uniqueness

Slide 39

Slide 39 text

Input Object Case Study // NestJS class RegisterDto { email: string; password: string; } @IsEmail() @IsNotEmpty() @Post() @UsePipes(new ValidationPipe({ transform: true })) async create(@Body() registerDto: RegisterDto) { this.registrationService.create(registerDto); }

Slide 40

Slide 40 text

Input Object Case Study // NestJS @UsePipes(new ValidationPipe({ transform: true })) class RegisterDto { @IsEmail() email: string; @IsNotEmpty() password: string; } @Post() async create(@Body() registerDto: RegisterDto) { this.registrationService.create(registerDto); }

Slide 41

Slide 41 text

Input Object Case Study Input Steps Parse Request Validate DTO Dispatch Controller

Slide 42

Slide 42 text

Input Object Case Study // Pseudo Code function dispatch(request: HttpRequest) { controller, action = this.router.lookup(request.path) input = meta.inputs[action].build(request.params) controller[action].call(input) } meta = Reflect.getMeta(controller) validator = meta.validator[action] if (validator) { validator.validate(input) }

Slide 43

Slide 43 text

Error Handling Case Study Error Handling Where to fix the error?

Slide 44

Slide 44 text

Error Handling Case Study Input Order.create! Payment.create! OrderMailer.confirmation Exception PaymentGatewayUnavailable Raise

Slide 45

Slide 45 text

Error Handling Case Study # Ruby on Rails rescue PaymentGateWayUnavailable render :new class OrderController < ApplicationController def create @order = Order.create!(params) @payment = Payment.create!(order: @order) OrderMailer.confirmation(@order) end end

Slide 46

Slide 46 text

Error Handling Case Study # Ruby on Rails rescue PaymentGateWayUnavailable render :new class OrderController < ApplicationController def create @order = Order.create!(params) @payment = Payment.create!(order: @order) OrderMailer.confirmation(@order) end end rescue SomeErrorA # ... rescue SomeErrorB # ...

Slide 47

Slide 47 text

Error Handling Case Study # Ruby on Rails rescue_from ActiveRecord::ActiveRecordError, with: :record_error rescue_from PaymentService::PaymentError, with: :payment_error class OrderController < ApplicationController def create @order = Order.create!(params) @payment = Payment.create!(order: @order) OrderMailer.confirmation(@order) end end

Slide 48

Slide 48 text

Error Handling Case Study # Ruby on Rails @form = CreaetOrderForm.new(params) def payment_error(exc) @form.errors.add(:base, exc.message) render :new end class OrderController < ApplicationController rescue_from ActiveRecord::ActiveRecordError, with: :record_error rescue_from PaymentService::PaymentError, with: :payment_error def create # ... end end

Slide 49

Slide 49 text

Error Handling Case Study # Ruby on Rails module WithPaymentFlow # ... included do rescue_from PaymentService::PaymentError, with: :payment_flow_error end def payment_flow_error(exc) @form.errors.add(:base, exc.message) end end # response_with(@form)

Slide 50

Slide 50 text

Error Handling Case Study ActiveSupport::Rescuable ActionController ActionMailer ActionCable ActiveJob

Slide 51

Slide 51 text

Side Effect Case Study Side Effect Who changes my data?

Slide 52

Slide 52 text

Side Effect Case Study # Ruby on Rails class ShippingService # ... def refresh_shipping_state!(order, ticket) end end order.update( shipping_state: ticket.shipping_state, # ... )

Slide 53

Slide 53 text

Side Effect Case Study # Ruby on Rails class Merchant::ShippingController < ApplicationController # ... def update ticket = ShippingTicket.from_order!(@order) shipping_service.refresh_shipping_state!(@order, ticket) # ... @order.save! end end tracking_service.attach_event!(@order) #=> TrackingUnavailableError

Slide 54

Slide 54 text

Side Effect Case Study # Ruby on Rails class ShippingService # ... end def refresh_shipping_state(order, ticket) order.assign_attributes( shipping_state: ticket.shipping_state, # ... ) end

Slide 55

Slide 55 text

Side Effect Case Study # Ruby on Rails class Merchant::ShippingController < ApplicationController # ... def update # User Flow ticket = ShippingTicket.from_order!(@order) shipping_service.refresh_shipping_state(@order, ticket) tracking_service.attach_event!(@order) # Commit @order.save! end end # Input # ...

Slide 56

Slide 56 text

Side Effect Case Study # Ruby on Rails class Merchant::ShippingController < ApplicationController # ... def update # Input # ... # Commit @order.save! end end # User Flow ticket = ShippingTicket.from_order!(@order) shipping_service.refresh_shipping_state(@order, ticket) tracking_service.attach_event!(@order)

Slide 57

Slide 57 text

Side Effect Case Study # Ruby on Rails class Merchant::ShippingController < ApplicationController # ... def update # Input # ... # User Flow ticket = ShippingTicket.from_order!(@order) shipping_service.refresh_shipping_state(@order, ticket) tracking_service.attach_event!(@order) end end # Commit @order.save!

Slide 58

Slide 58 text

Value Object Case Study Value Object Manage logic in the "Domain Model"

Slide 59

Slide 59 text

Value Object Case Study # Ruby on Rails class Wallets::DepositController < ApplicationController def create # ... @wallet.save! end end @wallet.deposit(@form[‘amount’], @form[‘currency’])

Slide 60

Slide 60 text

Value Object Case Study # Ruby on Rails class Wallet < ApplicationRecord # ... def deposit(amount, currency) self.balance += amount end end raise CurrencyMismatch unless self.currency == currency

Slide 61

Slide 61 text

Value Object Case Study # Ruby on Rails class Wallet < ApplicationRecord # ... def deposit(amount) end end sig {params(amount: Money).returns(Money)} self.balance += amount

Slide 62

Slide 62 text

Value Object Case Study # Ruby on Rails class Money # ... end def +(other) raise ArgumentError, ‘...’ unless other.currency == currency Money.new(amount + other.amount, currency) end raise ArgumentError, ‘...’ unless other.is_a?(Money)

Slide 63

Slide 63 text

Value Object Case Study # Ruby on Rails class Wallet < ApplicationRecord # ... sig {params(amount: Money).returns(Money)} def deposit(amount) end end composed_of :balance, class_name: ‘Money’, mapping: [%i[balance amount], %i[currency currency]] self.balance += amount

Slide 64

Slide 64 text

Value Object Case Study # Ruby on Rails class Wallets::DepositController < ApplicationController def create # ... @wallet.save! end end @wallet.deposit(@form.amount)

Slide 65

Slide 65 text

Value Object Case Study # Ruby on Rails class Wallets::DepositController < ApplicationController def create # ... @wallet.save! end end @wallet.balance += @form.amount

Slide 66

Slide 66 text

Survey Feedback Survey https://www.surveycake.com/s/lboZ9