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

The Big Ball of Nouns

The Big Ball of Nouns

A talk about modular software design in Elixir given at CodeBEAM Lite Amsterdam 2018

Maciej Kaszubowski

November 30, 2018
Tweet

More Decks by Maciej Kaszubowski

Other Decks in Programming

Transcript

  1. mkaszubowski94
    Maciej Kaszubowski
    THE BIG BALL
    OF NOUNS

    View full-size slide

  2. mkaszubowski94
    Hi, I’m Maciej

    View full-size slide

  3. mkaszubowski94
    2+ years of Elixir

    View full-size slide

  4. mkaszubowski94
    Functional Programming

    View full-size slide

  5. mkaszubowski94
    immutable data
    no side effects
    explicit code

    View full-size slide

  6. mkaszubowski94
    immutable data
    no side effects
    explicit code
    simpler software

    View full-size slide

  7. mkaszubowski94
    and yet…

    View full-size slide

  8. mkaszubowski94
    time
    complexity

    View full-size slide

  9. mkaszubowski94
    time
    development
    progress
    complexity

    View full-size slide

  10. mkaszubowski94
    why is that?

    View full-size slide

  11. mkaszubowski94
    f(x)
    explicit input explicit output
    what we want

    View full-size slide

  12. mkaszubowski94
    f(x)
    input output
    what we want
    g(x)
    output

    View full-size slide

  13. mkaszubowski94
    input output
    what we have

    View full-size slide

  14. mkaszubowski94

    View full-size slide

  15. mkaszubowski94

    View full-size slide

  16. mkaszubowski94

    View full-size slide

  17. mkaszubowski94

    View full-size slide

  18. mkaszubowski94

    View full-size slide

  19. mkaszubowski94

    View full-size slide

  20. mkaszubowski94

    View full-size slide

  21. mkaszubowski94

    View full-size slide

  22. mkaszubowski94
    on a higher level, our
    systems are not very
    functional

    View full-size slide

  23. mkaszubowski94
    Database
    mutable state
    shared dependency
    complexity

    View full-size slide

  24. mkaszubowski94
    Object
    Object
    Object
    Object
    Object
    Object
    SHARED
    STATE
    SHARED
    STATE

    View full-size slide

  25. mkaszubowski94
    Maciej Kaszubowski
    What can we do?

    View full-size slide

  26. mkaszubowski94
    https://www.confluent.io/blog/event-sourcing-cqrs-stream-processing-apache-kafka-whats-connection/
    Event Sourcing / CQRS

    View full-size slide

  27. mkaszubowski94
    Domain Driven Design

    View full-size slide

  28. mkaszubowski94
    Actor Model
    https://www.brianstorti.com/the-actor-model/

    View full-size slide

  29. mkaszubowski94
    Microservices
    HTTP
    HTTP
    HTTP

    View full-size slide

  30. mkaszubowski94
    these are
    hard and risky

    View full-size slide

  31. mkaszubowski94
    a monolith, stateless app with
    a relational DB is still
    your safest option

    View full-size slide

  32. mkaszubowski94
    Maciej Kaszubowski
    Modular Software
    Design

    View full-size slide

  33. mkaszubowski94
    1972

    View full-size slide

  34. mkaszubowski94
    one big
    problem

    View full-size slide

  35. mkaszubowski94

    View full-size slide

  36. mkaszubowski94
    Modular design
    smaller, simpler modules
    strong boundaries
    low coupling

    View full-size slide

  37. mkaszubowski94
    Each module can be:
    understood in isolation
    modified in isolation
    tested in isolation
    replaced or removed

    View full-size slide

  38. mkaszubowski94
    ok, but how?

    View full-size slide

  39. mkaszubowski94
    Maciej Kaszubowski
    Example Project

    View full-size slide

  40. mkaszubowski94
    create a new job
    name
    image url
    price
    publish
    John Alchemist
    create new job
    manage jobs
    find a job
    logout
    description
    date

    View full-size slide

  41. 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
    publish
    and one more accepted
    this didn’t go well canceled

    View full-size slide

  42. 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

    View full-size slide

  43. 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$

    View full-size slide

  44. mkaszubowski94
    defmodule ModularElixir.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

    View full-size slide

  45. mkaszubowski94
    JobContext
    - fetch_by_id()
    - search()
    - find_by_user_id()
    - start()
    - accept()
    - cancel()
    - publish()

    View full-size slide

  46. mkaszubowski94
    new feature:
    slugs

    View full-size slide

  47. 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

    View full-size slide

  48. mkaszubowski94
    defmodule ModularElixir.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

    View full-size slide

  49. mkaszubowski94
    JobContext
    - fetch_by_id()
    - search()
    - find_by_user_id()
    - start()
    - accept()
    - cancel()
    - publish()
    - create()
    - find_by_slug()

    View full-size slide

  50. mkaszubowski94
    new feature:
    pay before publishing

    View full-size slide

  51. mkaszubowski94
    create a new job
    name
    image url
    price
    continue
    John Alchemist
    create new job
    manage jobs
    find a job
    logout
    description
    date

    View full-size slide

  52. 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

    View full-size slide

  53. mkaszubowski94
    defmodule ModularElixir.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 :datetime, :utc_datetime
    belongs_to :creator, ModularElixir.User
    belongs_to :contractor, ModularElixir.User
    end
    end

    View full-size slide

  54. mkaszubowski94
    JobContext
    - fetch_by_id()
    - search()
    - find_by_user_id()
    - start()
    — accept()
    - cancel()
    - publish()
    - create()

    View full-size slide

  55. mkaszubowski94
    JobContext
    - fetch_by_id()
    - search()
    - find_by_user_id()
    - start()
    - accept()
    - cancel()
    - publish()
    - create()

    View full-size slide

  56. mkaszubowski94
    new feature:
    job reminders

    View full-size slide

  57. 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

    View full-size slide

  58. mkaszubowski94
    defmodule ModularElixir.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

    View full-size slide

  59. mkaszubowski94
    JobContext
    - fetch_by_id()
    - search()
    - find_by_user_id()
    - start()
    - accept()
    - cancel()
    - publish()
    - create()
    - find_by_slug()
    - send_reminder()

    View full-size slide

  60. mkaszubowski94
    jobs
    JobContext
    Payments
    preload jobs

    View full-size slide

  61. mkaszubowski94
    jobs
    JobContext
    User
    Context
    users
    join (jobs_count)
    Payments
    preload jobs

    View full-size slide

  62. mkaszubowski94
    jobs
    JobContext
    User
    Context
    users
    join (jobs_count)
    Payments
    preload jobs
    Chat
    Context

    View full-size slide

  63. mkaszubowski94
    jobs
    JobContext
    User
    Context
    users
    join (jobs_count)
    Payments
    preload jobs
    Chat
    Context
    JobSubtask
    Context

    View full-size slide

  64. mkaszubowski94
    jobs
    JobContext
    User
    Context
    users
    join (jobs_count)
    Payments
    JobSubtask
    Context
    preload jobs
    Chat
    Context
    JOB
    USER
    CONVERSATION
    MESSAGE
    PAYMENT
    SUBTASK

    View full-size slide

  65. mkaszubowski94
    In most projects:
    • few nouns / entities
    • a lot of interactions between them
    • logic is organised around the nouns
    • a lot of coupling as a result

    View full-size slide

  66. mkaszubowski94
    We’re used to the nouns
    • Object Oriented Programming
    • Databases
    • REST
    • CRUD

    View full-size slide

  67. mkaszubowski94
    nouns are not
    universal

    View full-size slide

  68. mkaszubowski94
    defmodule ModularElixir.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

    View full-size slide

  69. mkaszubowski94
    “job” means many
    different things

    View full-size slide

  70. mkaszubowski94
    Maciej Kaszubowski
    Extracting Behaviour

    View full-size slide

  71. mkaszubowski94
    the most important question:
    what is the desired outcome?

    View full-size slide

  72. mkaszubowski94
    1. what is the desired outcome?

    View full-size slide

  73. mkaszubowski94
    1. what is the desired outcome?
    2. what data do I need to do this?

    View full-size slide

  74. mkaszubowski94
    1. what is the desired outcome?
    2. what data do I need to do this?
    3. how can I get this data?

    View full-size slide

  75. 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?

    View full-size slide

  76. mkaszubowski94
    Maciej Kaszubowski
    Extracting Slugs

    View full-size slide

  77. 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

    View full-size slide

  78. mkaszubowski94
    desired outcome:
    jobs should be accessible via
    human-friendly slugs

    View full-size slide

  79. mkaszubowski94
    required data:
    id-slug mapping for jobs

    View full-size slide

  80. mkaszubowski94
    SEO
    Context

    View full-size slide

  81. mkaszubowski94
    SEO
    Context
    slugs

    View full-size slide

  82. mkaszubowski94
    SEO
    Context
    slugs
    - register_slug()
    - get_id_by_slug()
    - get_current_slug()
    Interface:

    View full-size slide

  83. mkaszubowski94
    def show(conn, %{"job_id"  slug}) do
    job =
    slug
     SeoContext.fetch_id_by_slug("job")
     JobContext.fetch_by_id()
    # 
    end

    View full-size slide

  84. mkaszubowski94
    def show(conn, %{"job_id"  slug}) do
    job =
    slug
     SeoContext.fetch_id_by_slug("job")
     JobContext.fetch_by_id()
    # 
    end
    Plug?

    View full-size slide

  85. mkaszubowski94
    defp put_slugs(jobs) do
    ids = Enun.map(jobs, & &1.id)
    slugs = SEOContext.fetch_slugs_by_ids(id)
    Enum.map(jobs, fn job 
    Map.put(job, :slug, slugs[job.id])
    end)
    end

    View full-size slide

  86. mkaszubowski94
    defmodule ModularElixir.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

    View full-size slide

  87. mkaszubowski94
    JobContext
    - fetch_by_id()
    - search()
    - find_by_user_id()
    - start()
    - accept()
    - cancel()
    - publish()
    - create()
    - find_by_slug()
    - send_reminder()

    View full-size slide

  88. mkaszubowski94
    SEO Context
    • slugs for other resources - easy to do

    View full-size slide

  89. mkaszubowski94
    SEO Context
    • slugs for other resources - easy to do
    • multiple slugs - easy to do

    View full-size slide

  90. 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

    View full-size slide

  91. 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

    View full-size slide

  92. mkaszubowski94
    Maciej Kaszubowski
    Extracting Notifications

    View full-size slide

  93. 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

    View full-size slide

  94. mkaszubowski94
    desired outcome:
    notification is sent when a job
    is starting soon

    View full-size slide

  95. mkaszubowski94
    required data:
    job information
    device information

    View full-size slide

  96. mkaszubowski94
    Job
    Reminders
    upcoming_jobs
    devices

    View full-size slide

  97. mkaszubowski94
    Job
    Reminders
    upcoming_jobs
    - put_job()
    - remove_job()
    - register_device()
    devices
    Interface:

    View full-size slide

  98. mkaszubowski94
    Job
    Reminders
    upcoming_jobs
    - put_job()
    - remove_job()
    - register_device()
    devices
    Interface:
    It’s hard to use
    (you have to remember to
    remove the job after starting)

    View full-size slide

  99. mkaszubowski94
    - register_device()
    Interface:
    Job
    Context
    search
    Job
    Reminders
    sent_reminders
    devices

    View full-size slide

  100. 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

    View full-size slide

  101. mkaszubowski94
    defmodule Notifications do
    def send_notifications(datetime) do
    jobs = JobContext.search(…)
    already_sent = fetch_already_sent()
    sender = FirebaseAdapter
    :ok = Logic.send(
    jobs, datetime, already_sent, sender
    )
    end
    end

    View full-size slide

  102. 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

    View full-size slide

  103. mkaszubowski94
    logic is isolated from the data
    source

    View full-size slide

  104. mkaszubowski94
    Logic
    desired
    outcome
    Notifications
    Job
    Context
    required
    data
    data
    source

    View full-size slide

  105. mkaszubowski94
    Logic
    desired
    outcome
    Notifications
    required
    data
    events stream

    View full-size slide

  106. mkaszubowski94
    Logic
    desired
    outcome
    Notifications
    required
    data

    View full-size slide

  107. mkaszubowski94
    Logic
    desired
    outcome
    Notifications
    required
    data
    TESTS

    View full-size slide

  108. mkaszubowski94
    defmodule ModularElixir.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

    View full-size slide

  109. mkaszubowski94
    JobContext
    - fetch_by_id()
    - search()
    - find_by_user_id()
    - start()
    - accept()
    - cancel()
    - publish()
    - create()
    - find_by_slug()
    - send_reminder()

    View full-size slide

  110. mkaszubowski94
    Maciej Kaszubowski
    Extracting Publishing

    View full-size slide

  111. mkaszubowski94
    desired outcome:
    job should be published only
    after the payment is made

    View full-size slide

  112. mkaszubowski94
    desired outcome:
    job should be published only
    after the payment is made

    View full-size slide

  113. mkaszubowski94
    problem:
    …published where?

    View full-size slide

  114. mkaszubowski94
    problem:
    the same model is used for
    multiple behaviours

    View full-size slide

  115. mkaszubowski94
    JobContext
    • adding / publishing jobs
    • searching for available jobs
    • managing ongoing jobs
    • archive / history
    • …

    View full-size slide

  116. mkaszubowski94
    JobContext
    - fetch_by_id()
    - search()
    - find_by_user_id()
    - start()
    - accept()
    - cancel()
    - publish()
    - create()
    - find_by_slug()
    - send_reminder()

    View full-size slide

  117. mkaszubowski94
    Job
    Publishing
    jobs
    Job
    Board
    jobs
    publish

    View full-size slide

  118. mkaszubowski94
    Job
    Publishing
    jobs
    Job
    Board
    jobs
    publish
    enforces job
    structure in params

    View full-size slide

  119. mkaszubowski94
    defmodule JobBoard do
    defmodule Job do
    @keys [:name, :description, ]
    @enforce_keys @keys
    defstruct @keys
    end
    def publish(%Job{} = job) do
    # 
    end
    end

    View full-size slide

  120. mkaszubowski94
    Job
    Publishing
    jobs
    Job
    Board
    jobs
    publish
    enforces job
    structure in params
    controls the rules
    for publishing

    View full-size slide

  121. mkaszubowski94
    Job
    Publishing
    - create()
    - get_drafts()
    - publish()

    View full-size slide

  122. mkaszubowski94
    defmodule ModularElixir.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

    View full-size slide

  123. mkaszubowski94
    JobContext
    - fetch_by_id()
    - search()
    - find_by_user_id()
    - start()
    - accept()
    - cancel()
    - publish()
    - create()
    - find_by_slug()
    - send_reminder()

    View full-size slide

  124. mkaszubowski94
    JobContext
    - fetch_by_id()
    - search()
    - find_by_user_id()
    - start()
    - accept()
    - cancel()
    - publish()
    - create()
    - find_by_slug()
    - send_reminder()

    View full-size slide

  125. mkaszubowski94
    JobBoard
    - fetch_by_id()
    - search()
    - find_by_user_id()
    - start()
    - accept()
    - cancel()
    - publish()
    - create()
    - find_by_slug()
    - send_reminder()

    View full-size slide

  126. mkaszubowski94
    JobBoard
    • can be tested in isolation
    (without mocking payments)
    • linear logic (without conditionals)
    • no risk of showing unpublished jobs

    View full-size slide

  127. mkaszubowski94
    JobPublishing
    • rules for publishing can be changed
    in isolation
    • can be used alongside other
    modules

    View full-size slide

  128. mkaszubowski94
    Job
    Board
    JobBoard
    params are enforced
    by the interface
    Job
    Publishing

    View full-size slide

  129. mkaszubowski94
    Job
    Board
    JobBoard
    params are enforced
    by the interface
    Admin

    View full-size slide

  130. mkaszubowski94
    Job
    Board
    JobBoard
    params are enforced
    by the interface
    TESTS

    View full-size slide

  131. mkaszubowski94
    JobBoard
    - fetch_by_id()
    - search()
    - find_by_user_id()
    - start()
    - accept()
    - cancel()
    - publish()
    - create()
    - find_by_slug()
    - send_reminder()

    View full-size slide

  132. mkaszubowski94
    JobBoard
    jobs
    Ongoing
    Jobs
    jobs

    View full-size slide

  133. mkaszubowski94
    defmodule ModularElixir.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

    View full-size slide

  134. mkaszubowski94
    JobBoard
    - fetch_by_id()
    - search()
    - find_by_user_id()
    - start()
    - accept()
    - cancel()
    - publish()
    - create()
    - find_by_slug()
    - send_reminder()

    View full-size slide

  135. mkaszubowski94
    Maciej Kaszubowski
    Outcomes

    View full-size slide

  136. mkaszubowski94
    JobContext
    jobs

    View full-size slide

  137. 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

    View full-size slide

  138. mkaszubowski94
    Job
    Publishing
    jobs
    Job
    Board
    jobs
    Reminders
    devices
    SEO
    slugs
    Ongoing
    Jobs
    jobs
    Job
    Archive
    jobs

    View full-size slide

  139. 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

    View full-size slide

  140. mkaszubowski94
    focused on
    behaviour, not nouns

    View full-size slide

  141. mkaszubowski94
    the flow of the app is
    visible in the module
    structure and dependencies

    View full-size slide

  142. mkaszubowski94
    Job
    Publishing
    Job
    Board
    Ongoing
    Jobs
    Job
    Archive
    Reminders
    Payments

    View full-size slide

  143. mkaszubowski94
    the logic is much
    simpler

    View full-size slide

  144. mkaszubowski94
    UserContext
    users

    View full-size slide

  145. mkaszubowski94
    Auth
    credentials
    Profiles
    jobs
    Notifications
    devices

    View full-size slide

  146. mkaszubowski94
    Maciej Kaszubowski
    Isolate important
    decisions

    View full-size slide

  147. mkaszubowski94
    start with a list of
    important decisions

    View full-size slide

  148. mkaszubowski94
    Important decisions
    • what are the rules for publishing jobs?
    • when/what notifications are sent?
    • which jobs are visible on job board?
    • how is a contractor chosen for each job?
    • what happens after a job is started? when the job
    ends?
    • how payments work?
    • …

    View full-size slide

  149. mkaszubowski94
    then, create a module
    for each decision

    View full-size slide

  150. mkaszubowski94
    Maciej Kaszubowski
    Summary

    View full-size slide

  151. mkaszubowski94
    this is not a silver
    bullet

    View full-size slide

  152. mkaszubowski94
    it’s just a mindset

    View full-size slide

  153. mkaszubowski94
    focus on behaviour,
    not data

    View full-size slide

  154. mkaszubowski94
    put behaviour into
    modules

    View full-size slide

  155. mkaszubowski94
    Make each module
    • independent
    • easy to test
    • easy to understand
    • with a strong interface

    View full-size slide

  156. mkaszubowski94
    Maciej Kaszubowski
    THANKS!

    View full-size slide