Elixir. Phoenix. Domain Driven Design
Mafinar Khan
Software Developer, Tread Inc
In this Talk
- Some History
- Some Definitions
- Some Examples
- One Proposition
A bit of history... 2009
Asset Tracking System
- Realtime Location Tracking
- Permissions Hierarchy
- Geofence
- Reports
- Public API
- Hiring/Subscription
- Fleet Management
- Anti-theft
- Driving Behavior
Django (MVC.. MTV?)
- Apps encompassing use cases
- Active Records for... eh, Domains?
- Views (Controllers) making the (Fat) model queries/writes
- Templates Rendering it
Pass 2
- Move UI to a SPA (React wasn't invented yet)
- API (Piston, DRF wasn't hot yet)
- Models are still Fat, controllers are still thin
Uh-oh
- Requirements pouring in
- Unplanned for features
- Lots of changes... everywhere
- Domain == Models
- but Domain != Models (What is a Model?)
communication(business, with: code)
Domain Driven Design
- Bounded Context
- Domain Entities
- Value Objects
- Service Objects
- Aggregate Roots
- Event Sourcing
Ubiquitous Language
- Common Vocabulary between all parties
- The Verb/Noun Game
- Facade talking to the nitty-gritty
- Less contextually costly changes
- (Eventually) Less Frequent changes
Elixir
- Functional/Immutable
- Optionally Types
- Pattern Matching
- OTP
- GenStage
Phoenix (> 1.3)
- Ecto for Repository
- Context
- OTP
- Thin(ner) Controller
- Separation of (Domain) Concerns
- Makes you THINK ABOUT ARCHITECTURE
Before
After
defp locations(conn,
%{"id": id, "lat": lat, "lng": lng) do
tablename = from(
v in "vehicle",
select: [:tablename], Repo.get()
)
query =
from(
p in tablename,
select: %{
name: p.name,
distance:
fragment(
"ST_Distance(
the_geom,
ST_MakePoint(?, ?)::geography)",
^lng,
^lat
)
},
where: not is_nil(p.name),
order_by: [asc: 2],
limit: ^limit
)
locations = query |> Repo.all()
render(conn, "locations.json", locations: locations)
end
def location(conn, %{"id": id}, _current_user) do
locations = id
|> LocationService.get_vehicle()
|> LocationService.get_locations_for()
render(conn, "locations.json", locations: locations)
end
Phoenix Contexts
- Bounded Contexts
- Concerns with Services
- Maps easily with Ubiquitous language
- Preferably the only thing the controller sees
- Middleware between Repository and View
- Can be topic of discussion in those Product X Business jams
Schema
Domain
defmodule Wltx.DB.Vehicle do
@moduledoc """
Schema for the vehicle/asset table
"""
use Ecto.Schema
schema "asset_vehicle" do
field(:license_plate, :string)
field(:vehicle_type_id, :integer)
field(:group_id, :integer)
field(:tag_id, :integer)
field(:code, :string)
field(:model, :string)
field(:color, :string)
field(:device_id, :integer)
field(:tablename
, :string)
end
end
defmodule Wltx.Vehicle do
defstruct
license_plate: "",
attributes: %AssetAttributes{},
related_table: "",
group: %Group{},
device: %Device{},
can_be_tracked: false,
is_whitelabelled: false
end
Aggregates?
- Hello, GenServer!
- Supervisor manages (Aggregate Roots)
- Connect multiple Domain Models/Schema?
Event Sourcing/CQRS
- What constitutes a state changes
- How do you see the results
- GPS Device |> TCPServer |> Parse |> Database |> ReportsOrRealTimeOrGeoFence
- Models better communicated via events/incidents
- Writers/Readers
- Actor Model Anyone?
The Hack Proposition
Tasks
- Discuss
- Decide on Structure
- Write Generators
- Hack!
Let's Hack!!!
Elixir, Phoenix and DDD
By Mafinar Khan
Elixir, Phoenix and DDD
- 2,839