Slide 1

Slide 1 text

ELIXIRDAZE 2017 Using Your Umbrella Chris Bell • @cjbell_

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

“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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

$ mix new my_app —umbrella

Slide 12

Slide 12 text

my_app/
 ├── config/
 | ├── config.exs
 ├── apps/
 | mix.exs

Slide 13

Slide 13 text

$ cd my_app/apps
 $ mix new sub_app —sup
 $ mix phoenix.new web_app

Slide 14

Slide 14 text

my_app/
 ├── config/
 | ├── config.exs
 ├── apps/ | ├── web_app/ | ├── sub_app/
 | mix.exs

Slide 15

Slide 15 text

$ mix test 
 # we can run this from the top level
 # (to run all tests in all apps)
 # or inside one of the apps

Slide 16

Slide 16 text

$ iex -S mix
 # we can also run this from the top
 # level to start all the apps inside

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

Designing Umbrella Applications PART II

Slide 20

Slide 20 text

Lets steal some ideas from Domain Driven Design

Slide 21

Slide 21 text

Separate your applications into specific bounded contexts

Slide 22

Slide 22 text

“[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

Slide 23

Slide 23 text

“[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?

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

Lets see an example

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

API Backoffice Sales Shipping Customer 
 Support Product 
 Catalog Ecommerce Project

Slide 29

Slide 29 text

• 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:

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

What about integrating with Ecto in our apps?

Slide 32

Slide 32 text

API Backoffice Sales Shipping Customer 
 Support Product 
 Catalog Ecommerce Project

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

API Backoffice Sales Shipping Customer 
 Support Product 
 Catalog Ecommerce Domain DB

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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!

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

And depend on it in our domain context applications

Slide 44

Slide 44 text

defmodule Sales.Mixfile do def application do [mod: {Sales, []}, applications: [:db]] end 
 defp deps do [{:db, in_umbrella: true}] end
 end

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

What about Phoenix?

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

What does this give us?

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

API Sales DB DB Web Req

Slide 54

Slide 54 text

“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’

Slide 55

Slide 55 text

• 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:

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

Umbrella applications in the wild PART III

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

Generate internal documentation using ex_doc for your apps TIP

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

Circular app dependencies are not allowed. GOTCHA

Slide 63

Slide 63 text

You can no longer run 
 mix ecto.gen.migration from the top level. GOTCHA

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

Thank you for your time.
 Questions? [email protected]