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.
“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
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
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
“[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
“[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?
• 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:
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
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!
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
“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’
• 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:
release :backoffice do set applications: [:backoffice, :another_app] end # distillery makes it easy! # read more at # https://hexdocs.pm/distillery/umbrella-projects.html