DDD-rail your Monorail

46a19926f5dff95126e78b7393019c9e?s=47 Andrew Hao
October 21, 2015

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

46a19926f5dff95126e78b7393019c9e?s=128

Andrew Hao

October 21, 2015
Tweet

Transcript

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

    driven design
  2. Hi, I’m Andrew

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

  5. The situation

  6. %

  7. What happened?

  8. In the beginning, there was an app.

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

    to year B. ⌛
  10. None
  11. None
  12. None
  13. The business decides… • Drivers deliver passengers from year A

    to year B. ⌛ • Passengers choose the type of Delorean service they want.
  14. None
  15. None
  16. None
  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
  18. None
  19. None
  20. None
  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!
  22. None
  23. None
  24. None
  25. Hm.

  26. None
  27. Regressions

  28. The Payments team regresses Trip while refactoring ServiceTier

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

    rideshare pricing
  30. Responsibilities?

  31. The Mobile team requests a new mobile login API, but

    which team should implement it?
  32. Business won’t stop

  33. Outsourced dev shop is rebuilding the marketing home page and

    needs a pricing API.
  34. CEO wants to launch "Airbnb for time travel” feature in,

    let’s say, 2 months!
  35. Does this sound familiar?

  36. There are deeper insights to be had

  37. What is DDD?

  38. A set of techniques to arrive at a flexible design

    that cleanly maps to the business model
  39. Strong, expressive domain models

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

    model
  41. Embrace the chaos

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

  43. Let’s get designing!

  44. Step 1: Visualize your domain models

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

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

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

  49. Print it out! ✏

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

  51. Concept: Core Domain

  52. What a business does

  53. Transportation Core Domain

  54. Concept: Subdomains

  55. A supporting unit within the business

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

    Access Subdomain
  57. Ridesharing Food Delivery Financial Transactions Identity/ Access

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

  59. Concept: Ubiquitous Language

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

    technical contexts
  61. Bring in the domain experts

  62. A Driver picks up a Passenger

  63. A Passenger requests a Pickup

  64. An Owner drives a Vehicle

  65. An Operator? drives a Vehicle

  66. A User logs in and changes her Password

  67. An Invoice is delivered to the Passenger

  68. A Customer orders an item from a Menu, which is

    picked up and delivered by the Delivery Agent
  69. Step 4: Make a glossary

  70. Ridesharing • Passenger: “…” • Driver: “…” • Trip: “…”

    • Pickup: “…” • Vehicle: “…” • Vehicle Owner: “…”
  71. Financial Transaction • Invoice: “…” • Order: “…” • Payment:

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

    • Role: “…”
  73. Print ‘em out! ✏

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

  75. Concept: Bounded Contexts

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

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

    “destination”
  78. Software systems are natural boundaries for these linguistic terms

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

    you have a smell
  80. Ridesharing Financial Transactions Identity/ Access Food Delivery DeloreanMonolith StripeAPI

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

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

    Service (etc)
  83. DeloreanMonolith Ridesharing Financial Transactions Identity/ Access Stripe API Food Delivery

  84. Step 6: Now add directional dependencies

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

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

    Service (etc) U U D U D U D
  87. This helps you see dependencies between teams, where communication will

    be most important.
  88. Context Map: A tool to visualize how your software systems

    relate to each other and the domain(s)
  89. Our goal is to map our bounded contexts directly to

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

  91. Show me the code!

  92. Tactic 1: Get the names right

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

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

    new order.calculate_billing_total
  95. Tactic 2: Namespace and modulize your domains

  96. Break Rails’ folder structure conventions

  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
  98. class Menu < ActiveRecord::Base belongs_to :restaurant end

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

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

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

    end end
  102. Domain code stays together

  103. Tactic 3: Design with Aggregate Roots

  104. Aggregate: A collection of domain objects that can be treated

    as a single unit
  105. Use aggregates to model real-world entities that belong together

  106. Aggregate Root: An entity at the “top” of the collection

    that can represent the whole
  107. FoodDelivery aggregate root: Order

  108. Food Delivery

  109. A user adds a menu item to their cart

  110. A User adds a MenuItem into their ShoppingCart

  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
  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
  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
  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
  115. The root items of these aggregates are the only entities

    that external callers may fetch
  116. Ridesharing Financial Transactions Identity/Access Food Delivery

  117. Building good interfaces for a service oriented future!

  118. Tactic 4: Break database joins between domains

  119. `has_many`-itis!

  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
  121. These tend to happen in your God Objects

  122. None
  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
  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
  125. module Rideshare class FoodDeliveryAdapter def order_from_trip(trip) FoodDelivery::Order.find_by(trip_id: trip.id) end end

    end
  126. Decoupling domains now will ease your architectural transitions later

  127. You can do this all in small steps!

  128. Toward a distributed architecture

  129. Namespaced, Isolated Modules to Rails Engines

  130. Component-Based Rails Engines (Stephan Hagemann)

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

  132. Good architecture goes a very long way.

  133. To recap

  134. Drew things on a wall

  135. Came up with a language

  136. Moved code around

  137. Team is on the same page

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

    domain experts
  139. Big idea: Bounded contexts are separators for linguistic drivers. Keep

    your language consistent in one and only one system
  140. Big idea: Domain code should live together

  141. Big idea: Adapters between domains enforce domain purity

  142. Big idea: Incremental changes

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

    8