A talk given at ElixirConf EU 2019 in Prague. It talks about splitting big systems into smaller, simpler, more manageable modules that are easier to understand, test and work with.
mkaszubowski94 your jobs John Alchemist create new job manage jobs find a job logout my awesome job accept my other job cancel accept cancel yet another one and one more accepted this didn’t go well canceled accept cancel
mkaszubowski94 browse jobs John Alchemist create new job manage jobs find a job logout my awesome job my other job yet another one and one more filters 100$ 50$ 3500$ 99$ xxxx yyy zzz search
mkaszubowski94 start this job John Alchemist create new job manage jobs find a job logout Job Title I need something done start a job https://example.com/jobs/1234 100$
mkaszubowski94 defmodule MyApp.Job do use Ecto.Schema schema "jobs" do field :name, :string field :description, :string field :image_url, :string field :price, :integer field :datetime, :utc_datetime belongs_to :creator, ModularElixir.User belongs_to :contractor, ModularElixir.User end end
mkaszubowski94 start this job John Alchemist create new job manage jobs find a job logout my awesome job Don’t ask, you’ll get paid start a job https://example.com/jobs/my-awesome-job
mkaszubowski94 defmodule MyApp.Job do use Ecto.Schema schema "jobs" do field :name, :string field :description, :string field :image_url, :string field :paid, :boolean field :price, :integer field :slug, :string field :datetime, :utc_datetime belongs_to :creator, ModularElixir.User belongs_to :contractor, ModularElixir.User end end
mkaszubowski94 pay for job pay 110$ John Alchemist create new job manage jobs find a job logout my awesome job job price………….………..100$ fee (10%)…………………….10$ total……………………………..110$ Don’t ask, you’ll get paid Your job will be published after the payment
mkaszubowski94 defmodule MyApp.Job do use Ecto.Schema schema "jobs" do field :name, :string field :description, :string field :image_url, :string field :slug, :string field :paid, :boolean field :price, :integer field :datetime, :utc_datetime belongs_to :creator, ModularElixir.User belongs_to :contractor, ModularElixir.User end end
mkaszubowski94 browse jobs John Alchemist create new job manage jobs find a job logout my awesome job my other job yet another one and one more filters 100$ 50$ 3500$ 99$ xxxx yyy zzz search Upcoming job Job starting in 54 minutes ! x
mkaszubowski94 defmodule MyApp.Job do use Ecto.Schema schema "jobs" do field :name, :string field :description, :string field :image_url, :string field :paid, :boolean field :slug, :string field :started, :boolean field :notification_sent, :boolean field :price, :integer field :datetime, :utc_datetime belongs_to :creator, ModularElixir.User belongs_to :contractor, ModularElixir.User end end
mkaszubowski94 In most projects: • few nouns / entities • a lot of interactions between them • logic is organised around the nouns • most actions represented by updates • a lot of coupling as a result
mkaszubowski94 defmodule MyApp.Job do use Ecto.Schema schema "jobs" do field :name, :string field :description, :string field :image_url, :string field :paid, :boolean field :slug, :string field :started, :boolean field :notification_sent, :boolean field :price, :integer field :datetime, :utc_datetime belongs_to :creator, ModularElixir.User belongs_to :contractor, ModularElixir.User end end
mkaszubowski94 1. what is the desired outcome? 2. what data do I need to do this? 3. how can I get this data? 4. what should be exposed in the interface?
mkaszubowski94 start this job John Alchemist create new job manage jobs find a job logout my awesome job Don’t ask, you’ll get paid start a job https://example.com/jobs/my-awesome-job
mkaszubowski94 defmodule MyApp.Job do use Ecto.Schema schema "jobs" do field :name, :string field :description, :string field :image_url, :string field :paid, :boolean field :slug, :string field :started, :boolean field :notification_sent, :boolean field :price, :integer field :datetime, :utc_datetime belongs_to :creator, ModularElixir.User belongs_to :contractor, ModularElixir.User end end
mkaszubowski94 SEO Context • slugs for other resources - easy to do • multiple slugs - easy to do • slugs are resolved at the controller level • no slugs in domain code
mkaszubowski94 no slugs in domain logic /api/jobs/my-awesome-job/subtasks from( s in Subtask, join: j in Job, on: s.job_id j.id where: j.id ^job_id or j.slug ^job_id, # … ) /api/jobs/1/subtasks
mkaszubowski94 browse jobs John Alchemist create new job manage jobs find a job logout my awesome job my other job yet another one and one more filters 100$ 50$ 3500$ 99$ xxxx yyy zzz search Upcoming job Job starting in 54 minutes ! x
mkaszubowski94 defmodule Notifications.Worker do use GenServer def handle_info(:work, state) do Notifications.send_notifications(now()) Process.send_after(self(), :work, @interval) {:noreply, state) end end
mkaszubowski94 defmodule Notifications.Logic do def send(jobs, datetime, already_sent, sender) do Enum.each(jobs, fn job -> if starting_soon?(job) && not_sent_yet?(job, already_sent) do sender.send_notification(job) end end) end end
mkaszubowski94 defmodule MyApp.Job do use Ecto.Schema schema "jobs" do field :name, :string field :description, :string field :image_url, :string field :paid, :boolean field :slug, :string field :started, :boolean field :notification_sent, :boolean field :price, :integer field :datetime, :utc_datetime belongs_to :creator, ModularElixir.User belongs_to :contractor, ModularElixir.User end end
mkaszubowski94 defmodule JobBoard do defmodule Job do @keys [:name, :description, ] @enforce_keys @keys defstruct @keys end def publish(%Job{} = job) do # end end
mkaszubowski94 defmodule MyApp.JobBoard.Job do use Ecto.Schema @schema_prefix “job_board” schema "jobs" do field :name, :string field :description, :string field :image_url, :string field :paid, :boolean field :slug, :string field :started, :boolean field :notification_sent, :boolean field :price, :integer field :datetime, :utc_datetime belongs_to :creator, ModularElixir.User belongs_to :contractor, ModularElixir.User end end
mkaszubowski94 defmodule MyApp.JobBoard.Job do use Ecto.Schema @schema_prefix “job_board” schema "jobs" do field :name, :string field :description, :string field :image_url, :string field :paid, :boolean field :slug, :string field :started, :boolean field :notification_sent, :boolean field :price, :integer field :datetime, :utc_datetime belongs_to :creator, ModularElixir.User belongs_to :contractor, ModularElixir.User end end
mkaszubowski94 Important decisions • what are the rules for publishing jobs? • when/what notifications are sent? • which jobs are visible on the job board? • how is a contractor chosen for each job? • what happens after a job is started? when the job ends? • how payments work? • …
mkaszubowski94 JobContext • no clear responsibility • hard to change and understand • hard to test in isolation • grows with each new feature • hard to delete old features
mkaszubowski94 Smaller modules • clear responsibilities for each module • easy to change, refactor, understand • easy to test in isolation • tend to stay small • trivial to delete if no longer necessary • written once and then forgotten