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

Highly Cohesive, Loosely Coupled (& Very Awesome)

Andrew Hao
February 15, 2017

Highly Cohesive, Loosely Coupled (& Very Awesome)

Designing Rails apps at scale sometimes hurts. Codebases grow out of control, and we lack the architectural patterns to control the chaos. Luckily, we have the powers Domain-Driven Design at our disposal.

With the use of tools like Context Mapping, we'll learn how to map the problem space of our large Rails apps, learn where to draw boundaries in our apps, and do a very simple Cool Trick to organize our apps for higher cohesion.

Given at Fog City Ruby, February 2017

Andrew Hao

February 15, 2017
Tweet

More Decks by Andrew Hao

Other Decks in Programming

Transcript

  1. Highly Cohesive,
    Loosely Coupled
    (& Very Awesome)
    A domain-driven approach to beautiful systems

    View Slide

  2. Let's imagine tonight
    We work at a hot new startup: Delorean. The Uber for
    time-travel!

    View Slide

  3. OMG it's making so much
    money!
    We're releasing features right and left!

    View Slide

  4. But the code is a mess!
    As systems grow, they naturally want to fall into disarray...

    View Slide

  5. The evolution of a feature
    Feature: As a passenger, I want to hail a Delorean
    So I can travel in time!

    View Slide

  6. class TripsController
    def create
    passenger = Passenger.find(params[:passenger_id])
    driver = Driver.where(
    available: true,
    longitude: params[:longitude],
    latitude: params[:latitude]
    ).first
    driver.send_to!(passenger)
    end
    end

    View Slide

  7. The evolution of a feature
    Feature: As a passenger, I want to hail a Delorean
    So I can travel in time!
    ...and my credit card will be charged

    View Slide

  8. class TripsController
    def create
    passenger = Passenger.find(params[:passenger_id])
    driver = Driver.where(
    available: true,
    longitude: params[:longitude],
    latitude: params[:latitude]
    ).first
    BraintreeService.charge(passenger)
    driver.send_to!(passenger)
    end
    end

    View Slide

  9. The evolution of a feature
    Feature: As a passenger, I want to hail a Delorean
    So I can travel in time!
    ...and my credit card will be charged
    ...and the system should log the event to Google Analytics

    View Slide

  10. class TripsController
    def create
    passenger = Passenger.find(params[:passenger_id])
    driver = Driver.where(
    available: true,
    longitude: params[:longitude],
    latitude: params[:latitude]
    ).first
    BraintreeService.charge(passenger)
    AnalyticsService.log_ride_created!
    driver.send_to!(passenger)
    end
    end

    View Slide

  11. The evolution of a feature
    Feature: As a passenger, I want to hail a Delorean
    So I can travel in time!
    ...and my credit card will be charged
    ...and the system should log the event to Google Analytics
    ...and we should totally also do food delivery

    View Slide

  12. class TripsController
    def create
    passenger = Passenger.find(params[:passenger_id])
    is_food = params[:ride_type] == 'food'
    driver = Driver.where(
    can_food_delivery: is_food,
    # ...
    ).first
    restaurant = Restaurant.find_by(meal_type: params[:m
    BraintreeService.charge(passenger)
    AnalyticsService.log_ride_created!
    driver.itinerary.add(restaurant)
    driver.itinerary.add(passenger)
    end
    end

    View Slide

  13. Code clutter in Rails
    As the monolith grows, feature code is scattered across
    the app.
    app/
    controllers/trips_controller.rb
    models/trip.rb
    helpers/trip_helper.rb
    services/calculate_trip_cost.rb

    View Slide

  14. Code clutter in Rails
    As the monolith grows, feature code is scattered across
    the app.
    app/
    controllers/restaurants_controller.rb
    models/restaurant.rb
    models/meal.rb
    helpers/restaurant_helper.rb
    services/calculate_meal_cost.rb

    View Slide

  15. Code clutter in Rails
    As the monolith grows, feature code is scattered across
    the app.
    app/
    controllers/puppy_deliveries_controller.rb
    models/puppy_delivery.rb
    models/animal_shelter.rb

    View Slide

  16. So here we are...
    The code is tangled & dif cult to change
    Regressions are common
    Features take forever to build & release

    View Slide

  17. View Slide

  18. Hi, I'm Andrew
    Friendly neighborhood programmer at Carbon Five

    View Slide

  19. View Slide

  20. Beautiful systems

    View Slide

  21. Beautiful systems are highly cohesive

    View Slide

  22. Beautiful systems are loosely coupled

    View Slide

  23. Highly cohesive
    Elements of a module are strongly related to each other
    Near each other, are easily accessible.

    View Slide

  24. View Slide

  25. Loosely coupled
    Modules minimize their dependencies so that they are
    easily modi able

    View Slide

  26. Loosely coupled
    Modules minimize their dependencies so that they are
    easily modi able
    Can evolve independently of other modules in the system

    View Slide

  27. View Slide

  28. Introducing Domain-Driven
    Design
    DDD is both a set of high-level design activities and
    speci c software patterns

    View Slide

  29. Our goals tonight
    Visualize our system from a domain perspective.

    View Slide

  30. Our goals tonight
    Visualize our system from a domain perspective.
    ✏ Learn insights to draw boundaries in our code!

    View Slide

  31. Our goals tonight
    Visualize our system from a domain perspective.
    ✏ Learn insights to draw boundaries in our code!
    Do a little bit of refactoring.

    View Slide

  32. Strategic Design
    Through an exercise called Context Mapping

    View Slide

  33. Apply It! ⚡
    Step 1: Visualize Your System
    Let's generate an ERD diagram!
    I like to generate mine with a gem like railroady or
    rails-erd
    If you have multiple systems, do this for each system.

    View Slide

  34. View Slide

  35. Yikes.

    View Slide

  36. Core domain
    The Core Domain is the thing that your business does that
    makes it unique.

    View Slide

  37. Core domain
    The Core Domain is the thing that your business does that
    makes it unique.
    Delorean Core Domain: Transportation

    View Slide

  38. Supporting domains
    A Supporting Domain (or Subdomain) are the areas of
    the business that play roles in making the Core Domain
    happen.

    View Slide

  39. Supporting domains
    A Supporting Domain (or Subdomain) are the areas of
    the business that play roles in making the Core Domain
    happen.
    Delorean Supporting Domains:
    Driver Routing (route me from X to Y)

    View Slide

  40. Supporting domains
    A Supporting Domain (or Subdomain) are the areas of
    the business that play roles in making the Core Domain
    happen.
    Delorean Supporting Domains:
    Driver Routing (route me from X to Y)
    Noti cations (push noti cations)

    View Slide

  41. Supporting domains
    A Supporting Domain (or Subdomain) are the areas of
    the business that play roles in making the Core Domain
    happen.
    Delorean Supporting Domains:
    Driver Routing (route me from X to Y)
    Noti cations (push noti cations)
    Financial Transactions (charge the card)

    View Slide

  42. Supporting domains
    A Supporting Domain (or Subdomain) are the areas of
    the business that play roles in making the Core Domain
    happen.
    Delorean Supporting Domains:
    Driver Routing (route me from X to Y)
    Noti cations (push noti cations)
    Financial Transactions (charge the card)
    Product Analytics (track business metrics)

    View Slide

  43. Supporting domains
    A Supporting Domain (or Subdomain) are the areas of
    the business that play roles in making the Core Domain
    happen.
    Delorean Supporting Domains:
    Driver Routing (route me from X to Y)
    Noti cations (push noti cations)
    Financial Transactions (charge the card)
    Product Analytics (track business metrics)
    Customer Support (keep people happy)

    View Slide

  44. Apply It! ⚡
    Step 2: Draw domains on your
    diagram
    Overlay your domains on top of the ERD diagram
    You might discover some domains you never even thought
    you had!

    View Slide

  45. View Slide

  46. View Slide

  47. Bounded Contexts
    A Bounded Context is:
    Concretely: a software system (like a codebase)
    Linguistically: a delineation in your domain where concepts are
    "bounded", or contained

    View Slide

  48. Apply It! ⚡
    Step 3: Overlay your bounded
    contexts
    Next up - with a different color pen or marker, draw lines
    around system boundaries / bounded contexts.

    View Slide

  49. Apply It! ⚡
    Step 3: Overlay your bounded
    contexts
    Next up - with a different color pen or marker, draw lines
    around system boundaries / bounded contexts.
    You may also nd other system boundaries like:
    External cloud providers
    Other teams' services or systems

    View Slide

  50. View Slide

  51. View Slide

  52. View Slide

  53. Congrats! You just made a
    Context Map!
    A Context Map gives us a place to see the current system
    as-is (the problem space), the strategic domains, and their
    dependencies.

    View Slide

  54. Making sense of the Context
    Map
    We may notice a few things:

    View Slide

  55. Making sense of the Context
    Map
    We may notice a few things:
    One bounded context contains multiple sub-(supporting)
    domains

    View Slide

  56. View Slide

  57. Making sense of the Context
    Map
    We may notice a few things:
    One bounded context contains multiple sub-(supporting)
    domains
    Multiple bounded contexts are required to support a single
    domain

    View Slide

  58. View Slide

  59. An Ideal Architecture
    Each Domain should have its own Bounded Context
    Key concept in DDD!

    View Slide

  60. View Slide

  61. Increased cohesion!
    We just found the areas where code "naturally" ts
    together, because they are serving the same business goal.

    View Slide

  62. Apply It! ⚡
    Break your application into
    domain modules
    Incremental refactoring, using Ruby Modules to lead the
    way!

    View Slide

  63. class Trip < ActiveRecord::Base
    belongs_to :vehicle
    belongs_to :passenger
    belongs_to :driver
    end
    class TripsController < ApplicationController
    # ...
    end

    View Slide

  64. module Ridesharing
    class Trip < ActiveRecord::Base
    belongs_to :vehicle
    belongs_to :passenger
    belongs_to :driver
    end
    end
    module Ridesharing
    class TripsController < ApplicationController
    # ...
    end
    end

    View Slide

  65. Find references to newly modulized classes and change them.

    View Slide

  66. # config/routes.rb
    resources :trips

    View Slide

  67. # config/routes.rb
    namespace :ridesharing, path: '/' do
    resources :trips
    end

    View Slide

  68. class Invoice
    belongs_to :trip
    end

    View Slide

  69. class Invoice
    belongs_to :trip, class_name: Ridesharing::Trip
    end

    View Slide

  70. Creating domain-oriented
    folders
    app/domains/ridesharing/trip.rb
    app/domains/ridesharing/service_tier.rb
    app/domains/ridesharing/vehicle.rb
    app/domains/ridesharing/trips_controller.rb
    app/domains/ridesharing/trips/show.html.erb

    View Slide

  71. View Slide

  72. Hence: modulizing increases
    cohesion
    Let's move on to coupling...

    View Slide

  73. ActiveRecord relationships
    can be abused!
    Objects start knowing too much about the entire world.
    "God Objects"

    View Slide

  74. class PaymentConfirmation
    belongs_to :trip, class_name: Ridesharing::Trip
    belongs_to :passenger, class_name: Ridesharing::Passen
    belongs_to :credit_card
    has_many :menu_items
    belongs_to :coupon_code
    has_one :retriable_email_job
    # ad infinitum...
    end

    View Slide

  75. View Slide

  76. Aggregate Roots
    Aggregate Roots are top-level domain models that reveal
    an object graph of related entities beneath them.

    View Slide

  77. Aggregate Roots
    Aggregate Roots are top-level domain models that reveal
    an object graph of related entities beneath them.
    Can be considered a Facade

    View Slide

  78. View Slide

  79. Decrease coupling by only
    exposing aggregate roots
    Make it a rule in your system that you may only access
    another domain's Aggregate Root.

    View Slide

  80. Decrease coupling by only
    exposing aggregate roots
    Make it a rule in your system that you may only access
    another domain's Aggregate Root.
    Internally, it's OK to reach for whatever you need.

    View Slide

  81. Make service objects that
    provide Aggregate Roots
    Your source domain can provide a service (Adapter) that
    returns the Aggregate Root

    View Slide

  82. module Ridesharing
    class FetchTrip
    def call(id)
    Trip
    .includes(:passenger,
    :trip, ...)
    .find(id)
    # Alternatively, return something custom
    # OpenStruct.new(trip: Trip.find(id), ...)
    end
    end
    end

    View Slide

  83. class PaymentConfirmation
    belongs_to :trip, class_name: Ridesharing::Trip
    belongs_to :passenger, class_name: Ridesharing::Passen
    # ...
    end

    View Slide

  84. class PaymentConfirmation
    def trip
    # Returns the Trip aggregate root
    Ridesharing::FetchTrip.new.find(payment_id)
    end
    end
    # OLD: payment_confirmation.passenger
    # NEW: payment_confirmation.trip.passenger

    View Slide

  85. Decrease coupling by
    publishing events for async
    dependencies
    Domains that only need unidirectional data ow work well
    here!

    View Slide

  86. Just send an event notifying
    the outside world!
    Instead of needing to know about the outside world, we
    simply publish an event.

    View Slide

  87. # Old way
    class TripController
    def create
    # ...
    ReallySpecificGoogleAnalyticsThing
    .tag_manager_logging('trip_created',
    ENV['GA_ID'],
    trip)
    end
    end

    View Slide

  88. class TripController
    def create
    # ...
    EventPublisher.publish(:trip_created, trip.id)
    end
    end

    View Slide

  89. Conway's Law and DDD
    Conway's Law, paraphrased: "Software systems tend to
    look like the organizations that produce them"

    View Slide

  90. Conway's Law and DDD
    Conway's Law, paraphrased: "Software systems tend to
    look like the organizations that produce them"
    DDD modeling oftentimes reveals domains that follow
    organizational layouts.
    Your software systems follow organizational optimizations.

    View Slide

  91. Conway's Law and DDD
    Conway's Law, paraphrased: "Software systems tend to
    look like the organizations that produce them"
    DDD modeling oftentimes reveals domains that follow
    organizational layouts.
    Your software systems follow organizational optimizations.
    Thus this is a very natural place to draw a seam!

    View Slide

  92. Warning: Limitations apply!
    Don't try to do this on every project!

    View Slide

  93. Warning: Limitations apply!
    Don't try to do this on every project!
    I've been guilty of overdesigning.

    View Slide

  94. Warning: Limitations apply!
    Don't try to do this on every project!
    I've been guilty of overdesigning.
    Try it out, step by step

    View Slide

  95. Warning: Limitations apply!
    Don't try to do this on every project!
    I've been guilty of overdesigning.
    Try it out, step by step
    Back it out if this doesn't " t"

    View Slide

  96. What we did tonight
    Visualized our system with a Context Map
    ✏ Drew boundaries in our code!
    Do a little bit of refactoring with domain modules,
    Aggregate Roots and Domain Events.

    View Slide

  97. Sample code:
    https://www.github.com/andrewhao/delorean

    View Slide

  98. Thanks!
    Github: andrewhao
    Twitter: @andrewhao
    Email: andrew@carbon ve.com

    View Slide

  99. Prior Art
    W. P. Stevens ; G. J. Myers ; L. L. Constantine. "Structured
    Design" - IBM Systems Journal, Vol 13 Issue 2, 1974
    Evans, Eric. Domain Driven Design
    Vernon, Vaughan. Implementing Domain-Driven Design
    http://www.win.tue.nl/~wstomv/quotes/structured-
    design.html#6
    https://www.infoq.com/articles/ddd-contextmapping
    http://gorodinski.com/blog/2013/04/29/sub-domains-and-
    bounded-contexts-in-domain-driven-design-ddd/

    View Slide