Upgrade to Pro — share decks privately, control downloads, hide ads and more …

DDD-rail your Monorail

DDD-rail your Monorail

The scene cuts to the offices of Delorean - the hottest time-transport startup in town (“it’s like Uber for time travel!”). Beneath the glitzy face of the fast-growing company, we find a defeated engineering team struggling with a large Rails monolith, complete with the battle scars of anemic domain models, spaghetti code, and cut corners. The team is shipping fewer features while battling increasing rates of unexpected regressions. The team wants to break up the monolith to combat complexity, but where do they begin? Join the team as they turn to three Domain-Driven Design (DDD) techniques to discover key domain insights and attempt to design their way out of the monolith mess into a well-defined, cleanly segregated, and service-oriented future. Will they succeed, or will the inexorable tides of time and entropy forever trap this team in the past?

Presentation: https://www.youtube.com/watch?v=TljmIXW2fwM

Don't forget to check out the companion sample app: https://github.com/andrewhao/delorean

Andrew Hao

October 21, 2015
Tweet

More Decks by Andrew Hao

Other Decks in Programming

Transcript

  1. DDD-rail Your Monorail
    Breaking up the Rails monolith
    with domain driven design

    View Slide

  2. Hi, I’m Andrew

    View Slide

  3. View Slide

  4. Meet Delorean
    “It’s like Uber, for time travel!”

    View Slide

  5. The situation

    View Slide

  6. %

    View Slide

  7. What happened?

    View Slide

  8. In the beginning, there
    was an app.

    View Slide

  9. The business decides…
    • Drivers deliver passengers from year A to
    year B. ⌛

    View Slide

  10. View Slide

  11. View Slide

  12. View Slide

  13. The business decides…
    • Drivers deliver passengers from year A to
    year B. ⌛
    • Passengers choose the type of Delorean
    service they want.

    View Slide

  14. View Slide

  15. View Slide

  16. View Slide

  17. The business decides…
    • Drivers deliver passengers from year A to
    year B. ⌛
    • Passengers choose the type of Delorean
    service they want.
    • Time travel carpools! 01

    View Slide

  18. View Slide

  19. View Slide

  20. View Slide

  21. The business decides…
    • Drivers deliver passengers from year A to year B. ⌛
    • Passengers choose the type of Delorean service they
    want.
    • Time travel carpools! 01
    • DeloreanEATS: Customers order food, drivers pick
    up from time period and deliver to customer time
    period!

    View Slide

  22. View Slide

  23. View Slide

  24. View Slide

  25. Hm.

    View Slide

  26. View Slide

  27. Regressions

    View Slide

  28. The Payments team regresses
    Trip while refactoring ServiceTier

    View Slide

  29. The Restaurants team deploys a
    new pricing algorithm that
    regresses rideshare pricing

    View Slide

  30. Responsibilities?

    View Slide

  31. The Mobile team requests a new
    mobile login API, but which team
    should implement it?

    View Slide

  32. Business won’t stop

    View Slide

  33. Outsourced dev shop is rebuilding
    the marketing home page and
    needs a pricing API.

    View Slide

  34. CEO wants to launch "Airbnb for
    time travel” feature in, let’s say, 2
    months!

    View Slide

  35. Does this sound familiar?

    View Slide

  36. There are deeper insights to be had

    View Slide

  37. What is DDD?

    View Slide

  38. A set of techniques to arrive at a
    flexible design that cleanly maps
    to the business model

    View Slide

  39. Strong, expressive domain
    models

    View Slide

  40. There is no such thing as a
    One True Perfect model

    View Slide

  41. Embrace the chaos

    View Slide

  42. Domain-Driven
    Design: Eric Evans
    Implementing Domain-
    Driven Design: Vaughn
    Vernon

    View Slide

  43. Let’s get designing!

    View Slide

  44. Step 1: Visualize your domain models

    View Slide

  45. Rails ERD
    https://github.com/voormedia/rails-erd

    View Slide

  46. Ruby gem to generate UML diagrams from
    your ActiveRecord models

    View Slide

  47. View Slide

  48. This helps you get the entire system into
    your mind.

    View Slide

  49. Print it out!

    View Slide

  50. Step 2: Find your core- and sub-domains

    View Slide

  51. Concept: Core Domain

    View Slide

  52. What a business does

    View Slide

  53. Transportation Core Domain

    View Slide

  54. Concept: Subdomains

    View Slide

  55. A supporting unit within the
    business

    View Slide

  56. Rideshare Subdomain
    Food Delivery Subdomain
    Financial Transaction Subdomain
    Identity and Access Subdomain

    View Slide

  57. Ridesharing
    Food Delivery
    Financial
    Transactions
    Identity/
    Access

    View Slide

  58. Step 3: Get the team talking in the same
    language

    View Slide

  59. Concept: Ubiquitous Language

    View Slide

  60. A defined language that is used
    consistently across business and
    technical contexts

    View Slide

  61. Bring in the domain experts

    View Slide

  62. A Driver picks up a Passenger

    View Slide

  63. A Passenger requests a Pickup

    View Slide

  64. An Owner drives a Vehicle

    View Slide

  65. An Operator? drives a Vehicle

    View Slide

  66. A User logs in and changes her Password

    View Slide

  67. An Invoice is delivered to the Passenger

    View Slide

  68. A Customer orders an item from a Menu,
    which is picked up and delivered by the
    Delivery Agent

    View Slide

  69. Step 4: Make a glossary

    View Slide

  70. Ridesharing
    • Passenger: “…”
    • Driver: “…”
    • Trip: “…”
    • Pickup: “…”
    • Vehicle: “…”
    • Vehicle Owner: “…”

    View Slide

  71. Financial Transaction
    • Invoice: “…”
    • Order: “…”
    • Payment: “…”
    • Royalty: “…”
    • Salary: “…”

    View Slide

  72. Identity and Access
    • User: “…”
    • Secure password: “…”
    • Role: “…”

    View Slide

  73. Print ‘em out!

    View Slide

  74. Step 5: Draw out your software systems
    (bounded contexts)

    View Slide

  75. Concept: Bounded Contexts

    View Slide

  76. A software system that defines the
    applicability of a ubiquitous
    language

    View Slide

  77. FoodDeliveryService TripRoutingEngine
    “menu” “trip”
    “restaurant” “plan”
    “delivery” “pickup”
    “customer date” “destination”

    View Slide

  78. Software systems are natural boundaries for
    these linguistic terms

    View Slide

  79. If a term leaks into a different software
    system/bounded context, you have a smell

    View Slide

  80. Ridesharing
    Financial
    Transactions
    Identity/
    Access
    Food Delivery
    DeloreanMonolith
    StripeAPI

    View Slide

  81. DeloreanMonolith
    Ridesharing
    Financial
    Transactions
    Identity/
    Access
    Stripe API
    Food Delivery

    View Slide

  82. DeloreanMonolith
    Identity/
    Access
    Food Delivery
    Driver
    Routing
    Service
    Fraud
    Detection
    Service (etc)

    View Slide

  83. DeloreanMonolith
    Ridesharing
    Financial
    Transactions
    Identity/
    Access
    Stripe API
    Food Delivery

    View Slide

  84. Step 6: Now add directional dependencies

    View Slide

  85. DeloreanMonolith
    Ridesharing
    Financial
    Transactions
    Identity/
    Access
    Stripe API
    Food Delivery
    U
    D

    View Slide

  86. DeloreanMonolith
    Identity/
    Access
    Food Delivery
    Driver
    Routing
    Service
    Fraud
    Detection
    Service (etc)
    U
    U
    D
    U
    D U
    D

    View Slide

  87. This helps you see dependencies between
    teams, where communication will be most
    important.

    View Slide

  88. Context Map: A tool to visualize how your
    software systems relate to each other and
    the domain(s)

    View Slide

  89. Our goal is to map our bounded contexts
    directly to our subdomains.

    View Slide

  90. Ridesharing
    Financial
    Transactions
    Identity/
    Access
    Stripe API
    Food Delivery

    View Slide

  91. Show me the code!

    View Slide

  92. Tactic 1: Get the names right

    View Slide

  93. Adjust your class and method names to
    reflect the ubiquitous language

    View Slide

  94. # old
    User.without_drivers
    # new
    Passenger.hailing_drivers
    # old
    order.calculate_cost
    # new
    order.calculate_billing_total

    View Slide

  95. Tactic 2: Namespace and
    modulize your domains

    View Slide

  96. Break Rails’ folder structure conventions

    View Slide

  97. app/domains/financial
    app/domains/financial/inflation_adjustment.rb
    app/domains/financial/invoice.rb
    app/domains/food_delivery
    app/domains/food_delivery/menu.rb
    app/domains/food_delivery/menu_item.rb
    app/domains/food_delivery/menu_items_controller.rb
    app/domains/food_delivery/menus_controller.rb
    app/domains/identity
    app/domains/identity/user.rb
    app/domains/identity/users_controller.rb
    app/domains/rideshare
    app/domains/rideshare/driver.rb
    app/domains/rideshare/passenger.rb
    app/domains/rideshare/service_tier.rb

    View Slide

  98. class Menu < ActiveRecord::Base
    belongs_to :restaurant
    end

    View Slide

  99. module FoodDelivery
    class Menu < ActiveRecord::Base
    belongs_to :restaurant
    end
    end

    View Slide

  100. class Trip < ActiveRecord::Base
    belongs_to :service_tier
    belongs_to :vehicle
    end

    View Slide

  101. module Rideshare
    class Trip < ActiveRecord::Base
    belongs_to :service_tier
    belongs_to :vehicle
    end
    end

    View Slide

  102. Domain code stays together

    View Slide

  103. Tactic 3: Design with
    Aggregate Roots

    View Slide

  104. Aggregate: A collection of domain objects
    that can be treated as a single unit

    View Slide

  105. Use aggregates to model real-world entities
    that belong together

    View Slide

  106. Aggregate Root: An entity at the “top” of the
    collection that can represent the whole

    View Slide

  107. FoodDelivery aggregate root: Order

    View Slide

  108. Food Delivery

    View Slide

  109. A user adds a menu item to their cart

    View Slide

  110. A User adds a MenuItem into their
    ShoppingCart

    View Slide

  111. module Financial
    class ShoppingCart
    def order
    @order ||= FoodDelivery::Order.new
    end
    def amount
    @order.menu_items.sum(&:cost) +
    @order.menu_items.sum(&:tax_amount)
    end
    def add(item)
    order.menu_items << item
    end
    def remove(item)
    order.menu_items.delete(item); order.save
    end
    end
    end

    View Slide

  112. module FoodDelivery
    class Order < ActiveRecord::Base
    belongs_to :user
    has_many :order_menu_items
    has_many :menu_items, through: :order_menu_items
    end
    end

    View Slide

  113. module FoodDelivery
    class Order < ActiveRecord::Base
    belongs_to :user
    has_many :order_menu_items
    has_many :menu_items, through: :order_menu_items
    def total_cost
    item_cost + tax_cost
    end
    def item_cost
    menu_items.sum(&:cost)
    end
    def tax_cost
    menu_items.sum(&:tax_amount)
    end
    def add_item!(menu_item)
    menu_items << menu_item
    end
    def remove_item!(menu_item)
    menu_items.delete(menu_item); save
    end
    end
    end

    View Slide

  114. module Financial
    class ShoppingCart
    def order
    @order ||= FoodDelivery::Order.new
    end
    def amount
    @order.total_cost
    end
    def add(item)
    order.add_item!(item)
    end
    def remove(item)
    order.remove_item!(item)
    end
    end
    end
    Calculation logic kept in
    FoodDelivery domain
    Implementation-
    agnostic

    View Slide

  115. The root items of these aggregates are the
    only entities that external callers may fetch

    View Slide

  116. Ridesharing
    Financial Transactions
    Identity/Access
    Food Delivery

    View Slide

  117. Building good interfaces for a service
    oriented future!

    View Slide

  118. Tactic 4: Break database
    joins between domains

    View Slide

  119. `has_many`-itis!

    View Slide

  120. module Rideshare
    class Trip < ActiveRecord::Base
    belongs_to :service_tier
    has_many :trip_pool_trips
    has_one :trip_pool, through: :trip_pool_trips
    belongs_to :driver, foreign_key: :driver_id, class_name: I
    belongs_to :passenger, foreign_key: :passenger_id, class_n
    belongs_to :vehicle
    belongs_to :order, class_name: FoodDelivery::Order
    has_one :payment
    end

    View Slide

  121. These tend to happen in your God Objects

    View Slide

  122. View Slide

  123. module Rideshare
    class Trip < ActiveRecord::Base
    belongs_to :service_tier
    has_many :trip_pool_trips
    has_one :trip_pool, through: :trip_pool_trips
    belongs_to :driver, foreign_key: :driver_id, class_name: I
    belongs_to :passenger, foreign_key: :passenger_id, class_n
    belongs_to :vehicle
    belongs_to :order, class_name: FoodDelivery::Order
    has_one :payment
    end

    View Slide

  124. module Rideshare
    class Trip < ActiveRecord::Base
    # belongs_to :order, class_name: FoodDelivery::Order
    def order
    FoodDeliveryAdapter.new.order_from_trip(self)
    end
    end
    end

    View Slide

  125. module Rideshare
    class FoodDeliveryAdapter
    def order_from_trip(trip)
    FoodDelivery::Order.find_by(trip_id: trip.id)
    end
    end
    end

    View Slide

  126. Decoupling domains now will ease your
    architectural transitions later

    View Slide

  127. You can do this all in small
    steps!

    View Slide

  128. Toward a distributed
    architecture

    View Slide

  129. Namespaced, Isolated Modules to
    Rails Engines

    View Slide

  130. Component-Based Rails
    Engines (Stephan Hagemann)

    View Slide

  131. Rails engines to a remote (micro-)service

    View Slide

  132. Good architecture goes a very long way.

    View Slide

  133. To recap

    View Slide

  134. Drew things on a wall

    View Slide

  135. Came up with a language

    View Slide

  136. Moved code around

    View Slide

  137. Team is on the same page

    View Slide

  138. Big idea: Your software speaks the
    same language as your domain
    experts

    View Slide

  139. Big idea: Bounded contexts are separators
    for linguistic drivers. Keep your language
    consistent in one and only one system

    View Slide

  140. Big idea: Domain code
    should live together

    View Slide

  141. Big idea: Adapters between
    domains enforce domain purity

    View Slide

  142. Big idea: Incremental
    changes

    View Slide

  143. Thanks!
    Sample code: https://github.com/andrewhao/delorean
    Slides: https://github.com/andrewhao/dddrail-talk
    Twitter @andrewhao
    Github @andrewhao
    8

    View Slide