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

Using Your Umbrella – ElixirDaze

Using Your Umbrella – ElixirDaze

A look at what Applications and Umbrella Applications are in Elixir, as well as a deep dive into an approach for structuring your application as bounded contexts using Umbrella Apps.

Chris Bell

March 03, 2017
Tweet

More Decks by Chris Bell

Other Decks in Technology

Transcript

  1. ELIXIRDAZE 2017
    Using Your Umbrella
    Chris Bell • @cjbell_

    View Slide

  2. • twitter.com/cjbell_
    • speakerdeck.com/cjbell88
    • github.com/cjbell

    View Slide

  3. May 20th |> New York City
    empex.co • @empexco

    View Slide

  4. Using Your Umbrella
    The what, why, and how to use Umbrella apps

    View Slide

  5. Applications & Umbrella
    Applications in Elixir
    PART I
    (a brief introduction to)

    View Slide

  6. “In Elixir an application is a component
    implementing some specific functionality,
    that can be started and stopped as a unit,
    and which can be re-used in other systems.”
    Elixir Application Documentation

    View Slide

  7. defp deps do
    [{:ecto, “~> 2.0”},
    {:phoenix, “~> 1.2”},
    {:spotify_ex, “~> 2.0”}]
    end

    View Slide

  8. Applications give us a defined way
    to configure and run our Elixir code.

    View Slide

  9. defmodule MyApp do

    use Application
    def start(_, _) do
    import Supervisor.Spec, warn: false

    children = [worker(MyApp.Worker, [])]
    opts = [strategy: :one_for_one]

    Supervisor.start_link(children, opts)
    end
    end

    View Slide

  10. An umbrella application is a single
    project that is made up of one or
    more local applications.

    View Slide

  11. $ mix new my_app —umbrella

    View Slide

  12. my_app/

    ├── config/

    | ├── config.exs

    ├── apps/

    | mix.exs

    View Slide

  13. $ cd my_app/apps

    $ mix new sub_app —sup

    $ mix phoenix.new web_app

    View Slide

  14. my_app/

    ├── config/

    | ├── config.exs

    ├── apps/
    | ├── web_app/
    | ├── sub_app/

    | mix.exs

    View Slide

  15. $ mix test 

    # we can run this from the top level

    # (to run all tests in all apps)

    # or inside one of the apps

    View Slide

  16. $ iex -S mix

    # we can also run this from the top

    # level to start all the apps inside

    View Slide

  17. defmodule WebApp.MixFile do
    # rest of file omitted 

    defp deps do
    # declaring a dependency on an app under
    # your umbrella is easy
    [{:sub_app, in_umbrella: true}]
    end
    end

    View Slide

  18. We can make complex
    applications that are composed of
    small, isolated applications
    THE BIG IDEA:

    View Slide

  19. Designing Umbrella
    Applications
    PART II

    View Slide

  20. Lets steal some ideas from
    Domain Driven Design

    View Slide

  21. Separate your applications into
    specific bounded contexts

    View Slide

  22. “[Bounded Contexts are] a conceptual
    boundary where a domain model is
    applicable. It provides Ubiquitous Language
    that is spoken by the team and expressed in
    its carefully designed software model”
    Eric Evans: Domain Driven Design

    View Slide

  23. “[Bounded Contexts are] a conceptual
    boundary where a domain model is
    applicable. It provides Ubiquitous Language
    that is spoken by the team and expressed in
    its carefully designed software model”
    Eric Evans: Domain Driven Design
    Huh?

    View Slide

  24. Think about breaking your app into
    smaller pieces that have clear
    responsibilities and defined boundaries

    View Slide

  25. We want each application in our
    umbrella to focus on a single context

    View Slide

  26. Lets see an example

    View Slide

  27. Ecommerce App
    • Processes Sales
    • Ships Orders
    • Manages Customer
    support requests
    • Manages product
    catalog

    View Slide

  28. API Backoffice
    Sales Shipping
    Customer 

    Support
    Product 

    Catalog
    Ecommerce Project

    View Slide

  29. • Be tested in isolation
    • Be developed in isolation
    • Be deployed independently (if you wish)
    • Hides its complexity from other applications
    through a defined public interface
    Each application can:

    View Slide

  30. Yes, you can think of them
    as microservices…
    Read more: http://blog.plataformatec.com.br/
    2015/06/elixir-in-times-of-microservices/

    View Slide

  31. What about integrating with
    Ecto in our apps?

    View Slide

  32. API Backoffice
    Sales Shipping
    Customer 

    Support
    Product 

    Catalog
    Ecommerce Project

    View Slide

  33. We can introduce a single DB app
    that manages our migrations and
    has access to our database through
    an Ecto.Repo

    View Slide

  34. API Backoffice
    Sales Shipping
    Customer 

    Support
    Product 

    Catalog
    Ecommerce Domain
    DB

    View Slide

  35. defmodule DB.Repo do

    use Ecto.Repo, otp_app: :db

    end
    defmodule DB.Mixfile do

    defp deps do
    [{:ecto, “~> 2.1”},
    {:postgrex, “~> 0.13”}]
    end

    end

    View Slide

  36. Sales
    Ecommerce Domain
    Customer
    Order LineItem
    DB
    Shipping
    Customer
    Order LineItem
    DB
    Repo

    View Slide

  37. Each application defines its own
    schemas which are essentially
    ‘value objects’ we can pass around

    View Slide

  38. defmodule Sales.Customer do

    use Ecto.Schema
    schema “customers” do

    # Relevant sales customer fields

    end

    end
    defmodule Shipping.Customer do

    use Ecto.Schema
    schema “customers” do

    # Relevant shipping customer fields

    end

    end

    View Slide

  39. defmodule Sales.Customer do

    use Ecto.Schema
    schema “customers” do

    # Relevant sales customer fields

    end

    end
    defmodule Shipping.Customer do

    use Ecto.Schema
    schema “customers” do

    # Relevant shipping customer fields

    end

    end
    But we’re duplicating our schemas!

    View Slide

  40. Remember: DRY is about
    avoiding duplication of logic
    not duplication of code.

    View Slide

  41. We call out to the DB app through
    entities / aggregates / services

    View Slide

  42. defmodule Sales.OrderManager do

    def complete_order(user, order, params)
    build_completion_changeset()
    |> DB.Repo.update()
    |> handle_result()
    end
    end

    View Slide

  43. And depend on it in our domain
    context applications

    View Slide

  44. defmodule Sales.Mixfile do
    def application do
    [mod: {Sales, []},
    applications: [:db]]
    end

    defp deps do
    [{:db, in_umbrella: true}]
    end

    end

    View Slide

  45. Could also have a Repo per application
    instead of DB as separate app.
    OTHER OPTIONS ARE AVAILABLE:

    View Slide

  46. What about Phoenix?

    View Slide

  47. Treat Phoenix as the web layer into
    your domain context(s)
    PHOENIX IS NOT YOUR APPLICATION

    View Slide

  48. defmodule API.V1.CheckoutController do
    plug :ensure_current_user

    plug :ensure_current_order

    def create(conn, params) do
    user = get_current_user(conn)
    order = get_current_order(conn)

    Sales.OrderManager.complete_order(user, order, params)

    |> case do
    {:ok, order} -> render(conn, data: order)
    {:error, error} -> render_error(conn, 422, data: error)
    end
    end

    end

    View Slide

  49. Our web layer concerns itself
    with all things web (parsing
    params, reading headers for
    auth’d users etc)

    View Slide

  50. Phoenix 1.3 introduces
    generators to do this inside of
    your Phoenix app

    View Slide

  51. What does this give us?

    View Slide

  52. We have a dependency flow that
    only points inwards, meaning
    nothing on the inside knows about
    something on the outside

    View Slide

  53. API Sales DB DB
    Web Req

    View Slide

  54. “Code that is loosely coupled isn’t necessarily
    easy-to-delete, but it is much easier to replace,
    and much easier to change too.”
    @tef – ‘Write Code That Is Easy To Delete, Not Easy To Extend’

    View Slide

  55. • Clear separation of concerns
    • Your Phoenix app becomes very thin
    • Your application more clearly signals its
    features and its intent
    • Simple, unit testable business logic separate
    from your UI layer
    What you end up with:

    View Slide

  56. Of course, this is just one way
    of doing things and it’s not
    necessarily the right way™

    View Slide

  57. Umbrella applications in
    the wild
    PART III

    View Slide

  58. github.com/bitwalker/distillery
    Take a look at Distillery for deploying
    your Umbrella application.
    TIP

    View Slide

  59. release :api do
    set applications: [:api]
    end


    release :backoffice do
    set applications: [:backoffice, :another_app]
    end

    # distillery makes it easy!

    # read more at

    # https://hexdocs.pm/distillery/umbrella-projects.html

    View Slide

  60. Generate internal documentation
    using ex_doc for your apps
    TIP

    View Slide

  61. You don’t have to start out with an
    Umbrella app on day 1. Refactoring
    into separate applications is easy.
    TIP

    View Slide

  62. Circular app dependencies
    are not allowed.
    GOTCHA

    View Slide

  63. You can no longer run 

    mix ecto.gen.migration
    from the top level.
    GOTCHA

    View Slide

  64. Dependency conflicts can be
    tricky to resolve. You may have to
    use multiple overrides.
    GOTCHA

    View Slide

  65. Thank you for your time.

    Questions?
    [email protected]

    View Slide