Slide 1

Slide 1 text

Building beautiful systems Phoenix Contexts and Domain-Driven Design Andrew Hao @andrewhao bit.ly/phoenix-contexts

Slide 2

Slide 2 text

Hi, I'm Andrew Friendly neighborhood programmer at Carbon Five 2 / 128

Slide 3

Slide 3 text

3 / 128

Slide 4

Slide 4 text

Axiom #1 Maintaining large systems is dif cult* 4 / 128

Slide 5

Slide 5 text

Axiom #1 Maintaining large systems is dif cult* * like, super mega dif cult 4 / 128

Slide 6

Slide 6 text

Axiom #2 Complex systems are not intentionally messy* 5 / 128

Slide 7

Slide 7 text

Axiom #2 Complex systems are not intentionally messy* * for the most part 5 / 128

Slide 8

Slide 8 text

The bulk of complexity is accidental Let's ship things fast! 6 / 128

Slide 9

Slide 9 text

The bulk of complexity is accidental Let's ship things fast! Death by a thousand cuts 6 / 128

Slide 10

Slide 10 text

The bulk of complexity is accidental Let's ship things fast! Death by a thousand cuts Abstractions that might work for small systems can't scale when the system grows. 6 / 128

Slide 11

Slide 11 text

The solution? Modularization 7 / 128

Slide 12

Slide 12 text

A blast from the past Information hiding D.L. Parnas - "On the Criteria to Be Used in Decomposing Systems into Modules" 8 / 128

Slide 13

Slide 13 text

9 / 128

Slide 14

Slide 14 text

"We propose instead that one begins with a list of dif cult design decisions or design decisions which are likely to change. "Each module is then designed to hide such a decision from the others." (Emphasis added) 10 / 128

Slide 15

Slide 15 text

High cohesion Concepts that belong together are likely to change together as a unit 11 / 128

Slide 16

Slide 16 text

Loose coupling Minimal dependencies on external systems Hide implementation details to outside callers 12 / 128

Slide 17

Slide 17 text

Today: We will: Introduce Phoenix contexts Build a Context Map and use it to introduce Domain-Driven Design concepts Apply our learnings with Elixir! 13 / 128

Slide 18

Slide 18 text

Thanks core team! Phoenix Contexts 14 / 128

Slide 19

Slide 19 text

What are contexts? Elixir modules that group system functionality by business domain 15 / 128

Slide 20

Slide 20 text

Follow the scaffold Create a User resource in the Identity context $ mix phx.gen.html Identity User users \ name:string email:string 16 / 128

Slide 21

Slide 21 text

Follow the scaffold Create a User resource in the Identity context $ mix phx.gen.html Identity User users \ name:string email:string 17 / 128

Slide 22

Slide 22 text

Follow the scaffold Create a User resource in the Identity context $ mix phx.gen.html Identity User users \ name:string email:string 18 / 128

Slide 23

Slide 23 text

Follow the scaffold Create a User resource in the Identity context $ mix phx.gen.html Identity User users \ name:string email:string 19 / 128

Slide 24

Slide 24 text

Ecto schema # lib/my_app/identity/user.ex defmodule MyApp.Identity.User do use Ecto.Schema schema "users" do field :name, :string field :email, :string timestamps() end end 20 / 128

Slide 25

Slide 25 text

Domain resource operations in the context # lib/my_app/identity/identity.ex defmodule MyApp.Identity do def get_user!(id), do: ... def create_user(attrs \\ %{}) do: ... def update_user(%User{} = user, attrs) do: ... def delete_user(%User{} = user) do: ... end 21 / 128

Slide 26

Slide 26 text

Web controller for the resource # lib/my_app_web/controllers/user_controller.ex defmodule MyApp.UserController do def index(conn, _params) do users = Identity.list_users() render(conn, "index.html", users: users) end def show(conn, %{"id" => id}) do user = Identity.get_user!(id) render(conn, "show.html", user: user) end end 22 / 128

Slide 27

Slide 27 text

Migration for Ecto persistence defmodule MyApp.Repo.Migrations.CreateUsers do def change do create table(:users) do add :name, :string add :email, :string timestamps() end end end 23 / 128

Slide 28

Slide 28 text

Contexts, the Phoenix way: All web concerns live in MyAppWeb context. 24 / 128

Slide 29

Slide 29 text

Contexts, the Phoenix way: All web concerns live in MyAppWeb context. Contexts encapsulate persistence and domain logic 24 / 128

Slide 30

Slide 30 text

Contexts, the Phoenix way: All web concerns live in MyAppWeb context. Contexts encapsulate persistence and domain logic The outer context module is the public interface to the rest of the app 24 / 128

Slide 31

Slide 31 text

♂ I have a few questions 25 / 128

Slide 32

Slide 32 text

What should I name the context? 26 / 128

Slide 33

Slide 33 text

How should I think about resources that are needed in multiple contexts? 27 / 128

Slide 34

Slide 34 text

How do I know if it's too broad (coarse) or too speci c ( ne)? 28 / 128

Slide 35

Slide 35 text

Our fundamental question How do we design system boundaries? 29 / 128

Slide 36

Slide 36 text

30 / 128

Slide 37

Slide 37 text

Domain-Driven Design Authored by Eric Evans in 2003 31 / 128

Slide 38

Slide 38 text

DDD is both: High-level strategic design activities and Concrete software patterns 32 / 128

Slide 39

Slide 39 text

DDD is both: High-level strategic design activities and Concrete software patterns Don't get tripped up! 32 / 128

Slide 40

Slide 40 text

Usually, we reach for Horizontal layers of abstractions Web layer Domain layer Persistence layer Adapters 33 / 128

Slide 41

Slide 41 text

Bigger picture 34 / 128

Slide 42

Slide 42 text

Let's think in terms of Vertically decomposed business use cases 35 / 128

Slide 43

Slide 43 text

Welcome to AutoMaxx! A used-car marketplace Sellers bring their cars in for an inspection and appraisal at our stores, where we buy their vehicle. Interested buyers test drive cars in our lot, purchasing them if they are interested. 36 / 128

Slide 44

Slide 44 text

Your business is constantly changing Marketing wants us to change copy on the web site 37 / 128

Slide 45

Slide 45 text

Your business is constantly changing Marketing wants us to change copy on the web site Finance wants us to change how we do tax calculations on a car sale by region 37 / 128

Slide 46

Slide 46 text

Your business is constantly changing Marketing wants us to change copy on the web site Finance wants us to change how we do tax calculations on a car sale by region Operations wants us to build a new feature in the vehicle inventory system 37 / 128

Slide 47

Slide 47 text

Your business is constantly changing Marketing wants us to change copy on the web site Finance wants us to change how we do tax calculations on a car sale by region Operations wants us to build a new feature in the vehicle inventory system Product has a new initiative to offer purchase transactions in Bitcoin 37 / 128

Slide 48

Slide 48 text

Your business is constantly changing Marketing wants us to change copy on the web site Finance wants us to change how we do tax calculations on a car sale by region Operations wants us to build a new feature in the vehicle inventory system Product has a new initiative to offer purchase transactions in Bitcoin Customer support wants us to build a better support dashboard 37 / 128

Slide 49

Slide 49 text

Listen to the business 38 / 128

Slide 50

Slide 50 text

DDD, summarized Design your software systems according to your business domains by: Paying attention to the language you speak in the business 39 / 128

Slide 51

Slide 51 text

Listen to the business* 40 / 128

Slide 52

Slide 52 text

Listen to the business* *Literally, listen 40 / 128

Slide 53

Slide 53 text

Context mapping An exercise to help us discover all the concepts living in our organization, and our systems. 41 / 128

Slide 54

Slide 54 text

Context Mapping Get everyone in a room Put up on a wall all the: Nouns - entities Verbs - events 42 / 128

Slide 55

Slide 55 text

43 / 128

Slide 56

Slide 56 text

Context Mapping Group like concepts and actions together. There may be overlaps - that's OK if your concepts belong in multiple groups. More important to just get it down. 44 / 128

Slide 57

Slide 57 text

45 / 128

Slide 58

Slide 58 text

Context Mapping Take a step back. Natural clusters and groupings should be emerging 46 / 128

Slide 59

Slide 59 text

De nition! Core domain The Core Domain is the primary area of focus of your business 47 / 128

Slide 60

Slide 60 text

De nition! Core domain The Core Domain is the primary area of focus of your business AutoMaxx Core Domain: Car Sales 47 / 128

Slide 61

Slide 61 text

48 / 128

Slide 62

Slide 62 text

49 / 128

Slide 63

Slide 63 text

De nition! Supporting domains A Supporting Domain (or Subdomain) are the areas of the business that play roles in making the Core Domain happen. 50 / 128

Slide 64

Slide 64 text

De nition! Supporting domains A Supporting Domain (or Subdomain) are the areas of the business that play roles in making the Core Domain happen. Online Listings (put the car on the website) 50 / 128

Slide 65

Slide 65 text

De nition! Supporting domains A Supporting Domain (or Subdomain) are the areas of the business that play roles in making the Core Domain happen. Online Listings (put the car on the website) Financial Transactions (charge the buyer, pay the seller) 50 / 128

Slide 66

Slide 66 text

De nition! Supporting domains A Supporting Domain (or Subdomain) are the areas of the business that play roles in making the Core Domain happen. Online Listings (put the car on the website) Financial Transactions (charge the buyer, pay the seller) Optimization & Analytics (track business metrics) 50 / 128

Slide 67

Slide 67 text

De nition! Supporting domains A Supporting Domain (or Subdomain) are the areas of the business that play roles in making the Core Domain happen. Online Listings (put the car on the website) Financial Transactions (charge the buyer, pay the seller) Optimization & Analytics (track business metrics) Customer Support (keep people happy) 50 / 128

Slide 68

Slide 68 text

51 / 128

Slide 69

Slide 69 text

52 / 128

Slide 70

Slide 70 text

Take a step back You've discovered your domains! The Context Map points us to our business domains! These may map to real organization structures (Conway's Law) 53 / 128

Slide 71

Slide 71 text

Ubiquitous Language The terms the business speaks within each domain! Put these in a Glossary Speak about them consistently in the team Use terms consistently in the code 54 / 128

Slide 72

Slide 72 text

De nition! Bounded Context Concretely: a software system (like a codebase or running application) Linguistically: a delineation in your domain where concepts are "bounded", or contained 55 / 128

Slide 73

Slide 73 text

For example: A Rating has a speci c meaning in the Inspection subdomain (a vehicle health score) ...but it means something else in the Customer Support subdomain (a customer support experience score) 56 / 128

Slide 74

Slide 74 text

For example: A User in the Identity context is-- A Mechanic in the Inspection context, is-- A Seller or Buyer in the Financial Transaction context 57 / 128

Slide 75

Slide 75 text

Bounded Contexts Precise language, precise models Your domains may now use their own speci c, nuanced terminology. Powerful modeling tool, and keeps your product in sync with the business stakeholders 58 / 128

Slide 76

Slide 76 text

In the I'm Being Serious Department Become Language Addicts Internalize your domain! 59 / 128

Slide 77

Slide 77 text

60 / 128

Slide 78

Slide 78 text

Elixir time! ♂ 61 / 128

Slide 79

Slide 79 text

Contexts can expose domain entities defmodule AutoMaxx.Inspection do def get_vehicle!() do: ... def list_vehicles() do: ... def update_vehicle() do: ... def get_mechanic!() do: ... #... end 62 / 128

Slide 80

Slide 80 text

Contexts can expose methods that perform domain actions defmodule AutoMaxx.Inspection do def add_vehicle_to_garage_queue(vehicle) do: ... def rate_vehicle(vehicle, mechanic, inspection_rating) do end 63 / 128

Slide 81

Slide 81 text

64 / 128

Slide 82

Slide 82 text

defmodule AutoMaxx.VehicleInspectionRatingController do def update(conn, %{ user_id: user_id, vehicle_id: vehicle_id, rating: new_rating }) do with user <- Repo.get_by(User, user_id), vehicle <- Repo.get_by(Vehicle, vehicle_id) |> Repo.preload(:rating), :ok <- InspectionRatingPolicy.editable_by?(vehicle, user) do inspection_rating = vehicle.rating |> rating.changeset(%{value: new_rating}) |> Repo.insert!() render(conn, "show.html", inspection_rating: inspection_rating) else render(conn, "error.html", message: "Oops!") end end end 65 / 128

Slide 83

Slide 83 text

defmodule AutoMaxx.VehicleInspectionRatingController do def update(conn, %{ user_id: user_id, vehicle_id: vehicle_id, rating: new_rating }) do with user <- Repo.get_by(User, user_id), vehicle <- Repo.get_by(Vehicle, vehicle_id) |> Repo.preload(:rating), :ok <- InspectionRatingPolicy.editable_by?(vehicle, user) do inspection_rating = vehicle.rating |> rating.changeset(%{value: new_rating}) |> Repo.insert!() render(conn, "show.html", inspection_rating: inspection_rating) else render(conn, "error.html", message: "Oops!") end end end 66 / 128

Slide 84

Slide 84 text

defmodule AutoMaxx.VehicleInspectionRatingController do def update(conn, %{ user_id: user_id, vehicle_id: vehicle_id, rating: new_rating }) do with user <- Repo.get_by(User, user_id), vehicle <- Repo.get_by(Vehicle, vehicle_id) |> Repo.preload(:rating), :ok <- InspectionRatingPolicy.editable_by?(vehicle, user) do inspection_rating = vehicle.rating |> rating.changeset(%{value: new_rating}) |> Repo.insert!() render(conn, "show.html", inspection_rating: inspection_rating) else render(conn, "error.html", message: "Oops!") end end end 67 / 128

Slide 85

Slide 85 text

Move domain entities into the Identity context # lib/automaxx/identity/user.ex defmodule AutoMaxx.Identity.User do schema "users" do: ... end Add domain action to the Identity context API # lib/automaxx/identity/identity.ex defmodule AutoMaxx.Identity do def get_user(user_id) do Repo.get_by(AutoMaxx.Identity.User, id: user_id) end end 68 / 128

Slide 86

Slide 86 text

Move domain entities into the Inspection context # lib/automaxx/inspection/vehicle.ex defmodule AutoMaxx.Inspection.Vehicle do schema "vehicles" do belongs_to :owner, AutoMaxx.Identity.User has_one :rating, AutoMaxx.Inspection.Rating end end # lib/automaxx/inspection/rating.ex defmodule AutoMaxx.Inspection.Rating do schema "inspection_ratings" do belongs_to :vehicle, AutoMaxx.Inspection.Vehicle belongs_to :rated_by, AutoMaxx.Identity.User end end 69 / 128

Slide 87

Slide 87 text

Add domain action to the Inspection context API defmodule AutoMaxx.Inspection do def rate_vehicle(vehicle, mechanic, inspection_rating) do with :ok <- Inspection.RatingPolicy.editable_by?(vehicle, mechanic) d Repo.get_by(Inspection.Vehicle, vehicle_id) |> Repo.preload(:rating) |> Inspection.Rating.changeset(%{ rated_by: mechanic.id, value: inspection_rating }) |> Repo.insert!() else {:auth_error, "Unauthorized"} end end end 70 / 128

Slide 88

Slide 88 text

Simplify the controller with your new context APIs defmodule AutoMaxx.VehicleInspectionRatingController do def update(conn, %{ user_id: user_id, vehicle_id: vehicle_id, rating: new_rating }) do with user <- Identity.get_user(user_id), vehicle <- Inspection.get_vehicle(vehicle_id), {:ok, rating} <- Inspection.rate_vehicle(vehicle, user, new_rati render(conn, "show.html", inspection_rating: rating) else render(conn, "error.html", message: "Oops!") end end end 71 / 128

Slide 89

Slide 89 text

Way better. 72 / 128

Slide 90

Slide 90 text

What did you notice here? Contexts only expose methods at their outer layer. Contexts hide internal implementations. Use domain language when naming actions, entities and concepts. 73 / 128

Slide 91

Slide 91 text

Concept sharing Or: Double Trouble 74 / 128

Slide 92

Slide 92 text

What happens when we need to use a User in a different context? Identity domain: User Inspection domain: Owner, Mechanic Marketing domain: Visitor 75 / 128

Slide 93

Slide 93 text

A few options: 1. Direct Usage (Do Nothing): Just directly use schemas between contexts 76 / 128

Slide 94

Slide 94 text

Sharing Concepts Direct Usage (Do Nothing) Mix domain models between contexts defmodule AutoMaxx.Inspection do def rate_vehicle(%AutoMaxx.Identity.User{} = user, vehicle, rating) do: ... end Maybe it's OK if you're refactoring, but I discourage this... 77 / 128

Slide 95

Slide 95 text

A few options: 1. Direct Usage (Do Nothing): Just directly use schemas between contexts 2. Struct Conversion: convert to internal concepts at the boundaries with pure structs 78 / 128

Slide 96

Slide 96 text

Sharing Concepts Struct Conversion Convert external concepts to internal concepts Useful for Read-Only use cases 79 / 128

Slide 97

Slide 97 text

Convert a Identity.User to a Marketing.Visitor defmodule AutoMaxx.Marketing.Visitor do defstruct [:handle, :uuid] end 80 / 128

Slide 98

Slide 98 text

Convert a Identity.User to a Marketing.Visitor defmodule AutoMaxx.Marketing.Visitor do defstruct [:handle, :uuid] end defmodule AutoMaxx.Marketing do def visitor_for_user(%AutoMaxx.Identity.User{} = user) do new_mapping = user |> Map.from_struct() |> Map.delete(:email) |> Map.put(:handle, user.email) struct(AutoMaxx.Marketing.Visitor, new_mapping) end end 81 / 128

Slide 99

Slide 99 text

Convert a Identity.User to a Marketing.Visitor defmodule AutoMaxx.Marketing.Visitor do defstruct [:handle, :uuid] end defmodule AutoMaxx.Marketing do def visitor_for_user(%AutoMaxx.Identity.User{} = user) do new_mapping = user |> Map.from_struct() |> Map.delete(:email) |> Map.put(:handle, user.email) struct(AutoMaxx.Marketing.Visitor, new_mapping) end end 82 / 128

Slide 100

Slide 100 text

defmodule AutoMaxx.Marketing.EmailSubscriptionController do def create(conn, %{user_id: user_id, payload: payload}) do Identity.get_user(user_id) # Convert the concept at the boundaries |> Marketing.visitor_for_user() # Then proceed to perform the domain action |> Marketing.subscribe_visitor_to_mailchimp(%{payload: payload}) end end 83 / 128

Slide 101

Slide 101 text

defmodule AutoMaxx.Marketing.EmailSubscriptionController do def create(conn, %{user_id: user_id, payload: payload}) do Identity.get_user(user_id) # Convert the concept at the boundaries |> Marketing.visitor_for_user() # Then proceed to perform the domain action |> Marketing.subscribe_visitor_to_mailchimp(%{payload: payload}) end end 84 / 128

Slide 102

Slide 102 text

defmodule AutoMaxx.Marketing.EmailSubscriptionController do def create(conn, %{user_id: user_id, payload: payload}) do Identity.get_user(user_id) # Convert the concept at the boundaries |> Marketing.visitor_for_user() # Then proceed to perform the domain action |> Marketing.subscribe_visitor_to_mailchimp(%{payload: payload}) end end 85 / 128

Slide 103

Slide 103 text

defmodule AutoMaxx.Marketing.EmailSubscriptionController do def create(conn, %{user_id: user_id, payload: payload}) do Identity.get_user(user_id) # Convert the concept at the boundaries |> Marketing.visitor_for_user() # Then proceed to perform the domain action |> Marketing.subscribe_visitor_to_mailchimp(%{payload: payload}) end end 86 / 128

Slide 104

Slide 104 text

Pattern matching and types Strict type guarantees with pattern matching def subscribe_visitor_to_mailchimp(%Visitor{} = visitor) do def visitor_for_user(%User{} = user) do... 87 / 128

Slide 105

Slide 105 text

Pattern matching and types Strict type guarantees with pattern matching def subscribe_visitor_to_mailchimp(%Visitor{} = visitor) do def visitor_for_user(%User{} = user) do... Even better - leverage the powers of typespecs. @spec subscribe_visitor_to_mailchimp(%Visitor{}) :: boolean @spec visitor_for_user(%User{}) :: %Visitor{} 87 / 128

Slide 106

Slide 106 text

A few options: 1. Direct Usage (Do Nothing): Just directly use schemas between contexts 2. Struct Conversion: convert to internal concepts at the boundaries with pure structs 3. Collaborator Schema: Create an internal schema persisted in Ecto that uses a reference to the external schema 88 / 128

Slide 107

Slide 107 text

Sharing Concepts Collaborator Schema Useful for Read+Write use cases 89 / 128

Slide 108

Slide 108 text

Sharing Concepts Collaborator Schema Useful for Read+Write use cases defmodule AutoMaxx.Inspection.Mechanic do schema "mechanics" do field :is_contractor, :boolean field :certification, :string belongs_to :user, AutoMaxx.Identity.User end end 89 / 128

Slide 109

Slide 109 text

defmodule AutoMaxx.Inspection do def create_mechanic_from_user( %AutoMaxx.Identity.User{} = user, is_contractor ) do %AutoMaxx.Inspection.Mechanic{ is_contractor: is_contractor, user: user } |> Repo.insert!() end def mechanic_for_user(user) do Repo.get_by(AutoMaxx.Inspection.Mechanic, user_id: user.id) end end 90 / 128

Slide 110

Slide 110 text

Sharing Concepts This is known as an Anti-Corruption Layer 91 / 128

Slide 111

Slide 111 text

Sharing Concepts Avoid cross-context joins if you can defmodule AutoMaxx.Marketing.Visitor do: schema "marketing_visitors" do: belongs_to :user, AutoMaxx.Identity.User 92 / 128

Slide 112

Slide 112 text

Sharing Concepts Avoid cross-context joins if you can defmodule AutoMaxx.Marketing.Visitor do: schema "marketing_visitors" do: belongs_to :user, AutoMaxx.Identity.User Instead, store them as external references field :user_id, :string field :user_id, :uuid 92 / 128

Slide 113

Slide 113 text

Aggregates Data that belongs together 93 / 128

Slide 114

Slide 114 text

We belong together! Look for data that naturally falls into groups 94 / 128

Slide 115

Slide 115 text

95 / 128

Slide 116

Slide 116 text

96 / 128

Slide 117

Slide 117 text

DON'T defmodule AutoMaxx.Inspection do def get_vehicle() do: # => %Vehicle{} def get_vehicle_rating() do: # => %Rating{} def get_vehicle_list_price() do: # => %ListPrice{} end 97 / 128

Slide 118

Slide 118 text

DON'T defmodule AutoMaxx.Inspection do def get_vehicle() do: # => %Vehicle{} def get_vehicle_rating() do: # => %Rating{} def get_vehicle_list_price() do: # => %ListPrice{} end DO defmodule AutoMaxx.Inspection do def get_vehicle() do: end # => %Vehicle{rating: %Rating{}, list_price: %ListPrice{}} 97 / 128

Slide 119

Slide 119 text

DON'T defmodule AutoMaxx.Inspection do def update_rating(rating_id, new_rating) do... end 98 / 128

Slide 120

Slide 120 text

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 98 / 128

Slide 121

Slide 121 text

Leverage Aggregate Roots Expose only Aggregate Roots in your context. Passing Aggregates around simpli es your APIs 99 / 128

Slide 122

Slide 122 text

Event-driven messaging Decouple your contexts even further 100 / 128

Slide 123

Slide 123 text

Where is the coupling here? # lib/automaxx/identity/identity.ex defmodule AutoMaxx.Identity do def create_user(...) do do_create_user() |> AutoMaxx.Inspection.maybe_create_mechanic() |> AutoMaxx.Marketing.subscribe_user_to_email_list() |> AutoMaxx.Analytics.track_event('user_created') end end 101 / 128

Slide 124

Slide 124 text

Where is the coupling here? # lib/automaxx/identity/identity.ex defmodule AutoMaxx.Identity do def create_user(...) do do_create_user() |> AutoMaxx.Inspection.maybe_create_mechanic() |> AutoMaxx.Marketing.subscribe_user_to_email_list() |> AutoMaxx.Analytics.track_event('user_created') end end 102 / 128

Slide 125

Slide 125 text

Where is the coupling here? # lib/automaxx/identity/identity.ex defmodule AutoMaxx.Identity do def create_user(...) do do_create_user() |> AutoMaxx.Inspection.maybe_create_mechanic() |> AutoMaxx.Marketing.subscribe_user_to_email_list() |> AutoMaxx.Analytics.track_event('user_created') end end 103 / 128

Slide 126

Slide 126 text

Where is the coupling here? # lib/automaxx/identity/identity.ex defmodule AutoMaxx.Identity do def create_user(...) do do_create_user() |> AutoMaxx.Inspection.maybe_create_mechanic() |> AutoMaxx.Marketing.subscribe_user_to_email_list() |> AutoMaxx.Analytics.track_event('user_created') end end 104 / 128

Slide 127

Slide 127 text

105 / 128

Slide 128

Slide 128 text

Publish facts (domain events) over a bus Interested contexts can subscribe to domain events 106 / 128

Slide 129

Slide 129 text

Publish facts (domain events) over a bus Interested contexts can subscribe to domain events Refer back to your Context Map and Glossary where your business stakeholders helped you de ne events! 106 / 128

Slide 130

Slide 130 text

Publisher: Identity context publishes identity.user.created Subscribers: Marketing context puts the user on the email marketing list Inspection context creates a corresponding Mechanic if the user is one Analytics context publishes metric to Google Analytics 107 / 128

Slide 131

Slide 131 text

108 / 128

Slide 132

Slide 132 text

Using the event_bus library Github: otobus/event_bus Or any other system that gives you pub/sub capabilities 109 / 128

Slide 133

Slide 133 text

# lib/automaxx/identity/identity.ex 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} do %{user: user} end end end 110 / 128

Slide 134

Slide 134 text

# lib/automaxx/identity/identity.ex 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} do %{user: user} end end end 111 / 128

Slide 135

Slide 135 text

In each context, create an EventHandler module. # lib/automaxx/marketing/event_handler.ex 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({:"some.other.event" = event_name, event_id}) do: ... end 112 / 128

Slide 136

Slide 136 text

In each context, create an EventHandler module. # lib/automaxx/marketing/event_handler.ex 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({:"some.other.event" = event_name, event_id}) do: ... end 113 / 128

Slide 137

Slide 137 text

# lib/automaxx/inspection/event_handler.ex defmodule AutoMaxx.Inspection.EventHandler do def process({:"identity.user.created" = event_name, event_id}) do %{data: %{user: user}} = EventBus.fetch_event({event_name, event_id}) AutoMaxx.Inspection.create_mechanic_from_user(user) end def process({:"yet.another.event" = event_name, event_id}) do: ... end 114 / 128

Slide 138

Slide 138 text

Caveats! 115 / 128

Slide 139

Slide 139 text

Caveats Start small Don't overdo it. Refactor things into one context at a time. 116 / 128

Slide 140

Slide 140 text

Caveats DDD concepts rst, then patterns Start with the Context Map. Make sure you understand the core concepts about linguistic clarity. Keep the team and the code speaking the same language! 117 / 128

Slide 141

Slide 141 text

Caveats For systems of a certain scale If you have a small-scale system, this might be overkill! 118 / 128

Slide 142

Slide 142 text

In conclusion 119 / 128

Slide 143

Slide 143 text

Listen to the business (Build a Context Map and a Glossary) 120 / 128

Slide 144

Slide 144 text

Strict boundaries Hide internal details behind a context 121 / 128

Slide 145

Slide 145 text

Linguistic precision & domain clarity Embrace the nuances of our domain models 122 / 128

Slide 146

Slide 146 text

Practical architecture tools Concept sharing & anti-corruption layers Ship aggregates around between contexts Use an event bus 123 / 128

Slide 147

Slide 147 text

Used the full power of the BEAM Lean on message passing, OTP, pattern matching and typespecs 124 / 128

Slide 148

Slide 148 text

That's what I think makes beautiful systems 125 / 128

Slide 149

Slide 149 text

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

Slide 150

Slide 150 text

Credits & Prior Art Evans, Eric. Domain-Driven Design: Tackling Complexity in the Heart of Software. Gorodinski, Lev. "Sub-domains and Bounded Contexts in Domain-Driven Design (DDD)". Hagemann, Stephan. Component-Based Rails Applications. Parnas, D.L. "On the Criteria To Be Used in Decomposing Systems into Modules". Vernon, Vaughan. Implementing Domain-Driven Design. 127 / 128

Slide 151

Slide 151 text

Credits & Prior Art W. P. Stevens ; G. J. Myers ; L. L. Constantine. "Structured Design" - IBM Systems Journal, Vol 13 Issue 2, 1974. Steinegger, Giessler, Hippchen, Abeck. Overview of a Domain-Driven Design Approach to Build Microservice-Based Applications Rob Martin - Perhap: Applying DDD and Reactive Architectures: https://www.youtube.com/watch? list=PLqj39LCvnOWZMVugtyKlHMF1o2zPNntFL&time_continue=5&v=kq4qT c 128 / 128