Skip to content

Instantly share code, notes, and snippets.

@wulymammoth
Created January 21, 2019 23:30
Show Gist options
  • Save wulymammoth/21b1014ce07a11a170d59f318d7654da to your computer and use it in GitHub Desktop.
Save wulymammoth/21b1014ce07a11a170d59f318d7654da to your computer and use it in GitHub Desktop.
Domain-Driven Design

Domain-driven Design (DDD)

Context mapping

Exercise to help us discover all the concepts living in our organization, and our systems

Get everyone in a room and discover the...

  • Nouns - entities
  • Verbs - events
  • Core domain - primary area of focus of the business
    • example: AutoMaxx's core domain is car sales
  • Supporting domains - subdomains that support the business/plays a role in making the core domain happen
    • examples:
      1. online listings - put the car onto the site
      2. financial transactions - charge the buyer and pay the seller
      3. optimization & analytics - metric tracking
      4. customer support

Ubiquitous language

The terms that the business speaks within each domain; adopt them instead of having separate nomenclature within an engineering organization.

  • Put these terms into a glossary
  • Speak about them consistently within the team
  • Use the terms consistently in the code
  • Examples:
    • A Rating has a specific meaning in the inspection subdomain -- vehicle health score
      • But has a different meaning under customer support subdomain -- customer support experience score

Precise language; precise models

Each domain may use specific nuanced terminology/nomenclature

  • keeps our product in sync with the business stakeholders

Contexts expose domain entities

  • collection & member getters and setters

    • domain: Inspection
    • get_vehicle
    • list_vehicles
    • update_vehicle
    • get_mechanic
  • exposing domain actions/procedures

    • add_vehicle_to_garage_queue
    • rate_vehicle

Sharing

  • Discouraged:
    • direct usage of a domain entity in another domain
  • Suggested:
    • conversion (at the boundaries / top-level module) [read-only]
      • Struct or model transform
      • example:
        • from marketing visitor -> marketing user
          • In marketing module:
            • visitor_for_user function
    • collaborator schema [read/write]
      • anti-corruption layer
      • create internal schema that uses a reference to the external schema
    • avoid cross-context joins
      # AVOID THIS
      defmodule AutoMaxx.Marketing.Visitor do
        schema "marketing_visitors" do:
          belongs_to :user, AutoMaxx.Identity.User
      
      # DO THIS
      field :user_id, :string
      field :user_id, :uuid
      end
      

Tree-like data / Aggregates

Leverage aggregate roots (top-level entities)

  • When we want to work with a vehicle rating or vehicle list price, let's pass around a vehicle instead

    • This simplies our APIs (public interfaces)
  • Example:

    • Before:

      - Inspection (context)
        - rating
        - vehicle
        - list price
      
    • After:

      - Inspection
        - vehicle
          - rating
          - list price
      
  • Dos / Don'ts

    # READING/GETTING
    
    ## DON'T
    defmodule AutoMaxx.Inspection do
      def get_vehicle, do: # ...
      def get_vehicle_rating, do: # ...
      def get_vehicle_list_price, do: # ...
    end
    
    ## DO (should be able to obtain each in aggregate)
    defmodule AutoMaxx.Inspection do
      def get_vehicle, do: # ...
    end
    
    # UPDATING / SETTING
    
    ## DON'T
    defmodule AutoMaxx.Inspection do
      def update_rating(rating_id, new_rating), do: # ...
    end
    
    ## DO
    defmodule AutoMaxx.Inspection do
      def rate_vehicle(vehicle_id, new_rating), do: # ...
    end

Event-driven messaging (context de-coupling)

Often times we have things like this:

# coupling (why does our identity context need to know about something that is a part of marketing)
# - `subscribe_user_to_email` function

defmodule AutoMaxx.Identity do
  def create_user do
    do_create_user()
    |> AutoMaxx.Inspection.create_mechanic()
    |> AutoMaxx.Inspection.subscribe_user_to_email()
    |> AutoMaxx.Inspection.track_event("user_created")
  end
end

But we should instead just publish an event and interested parties/contexts can subscribe and perform actions/procedures:

## Publish event(s) over a bus

defmodule AutoMaxx.Identity do
  def create_user do
    do_create_user() |> publish_event("Identity.user.created")
  end

  use EventBus.EventSource

  defp publish_event(%User{} = user, event_name) do
    EventSource.notify %{id: UUID.uuid4(), topic: event_name}
      %{user: user}
    end
  end
end

## create EventHandler module in each context
defmodule AutoMaxx.Marketing.EventHandler do
  def process({:"identity.user.created" = event_name, event_id}) do
    %{data: %{user: user}} = EventBus.fetch_event({event_name, event_id})
    AutoMaxx.Marketing.subscribe_user_to_email_list(user)
  end

  def process({:"another_event, event_id}), do: # ...
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment