Phoenix Is Not Your Application. Lance Halvorsen ElixirConf EU 2016

Similar documents
Hex. Package

Taking Off with /

The Little Elixir & OTP Guidebook by Tan Wei Hao

The Actor Model, Part Two. CSCI 5828: Foundations of Software Engineering Lecture 18 10/23/2014

Versioned APIs with Phoenix. Elvio Vicosa

It s good to be here... I almost wasn t. Beautiful Tests by Bruce A. Tate icanmakeitbe*er

functional ErlangVM Parallelism

Macros, and protocols, and metaprogramming - Oh My!

Beautiful Concurrency with Erlang

Building a video conference (WebRTC) controller with Elixir. Or how Elixir was introduced into VoiSmart

The Stack, Free Store, and Global Namespace

In today s video I'm going show you how you can set up your own online business using marketing and affiliate marketing.

Elixir and Phoenix fast, concurrent and explicit Tobias pragtob.info

Arduino IDE Friday, 26 October 2018

03. DECORATOR PATTERN. Design Eye for the Inheritance Guy

Strategies for Rapid Web Prototyping. Ruby on Rails. Clemens H. Cap

A Small Web Server. Programming II - Elixir Version. Johan Montelius. Spring Term 2018

Elixir and Phoenix fast, concurrent and explicit Tobias pragtob.info

Mastering Modern Payments

A PROGRAM IS A SEQUENCE of instructions that a computer can execute to

WE ARE ALL BLESSED. Bruce Tate

Formal Methods of Software Design, Eric Hehner, segment 24 page 1 out of 5

QuickCheck Mini for Elixir. Thomas Arts Quviq AB

ENHANCED EMBEDDED SYSTEMS NERVES PROJECT

The following content is provided under a Creative Commons license. Your support

What is Node.js? Tim Davis Director, The Turtle Partnership Ltd

big picture parallel db (one data center) mix of OLTP and batch analysis lots of data, high r/w rates, 1000s of cheap boxes thus many failures

PATTERN MAKING FOR THE PHOENIX HOOP

BBC LEARNING ENGLISH 6 Minute English Wireless furniture for phones

iex(1)> defmodule RepeatN do def repeat_n(function, count) do ...(1)> end repeat_n(function, count - 1) {:module, RepeatN,...}

BBC Learning English Face up to Phrasals Mark's Mistake

Organized by Jean-François Cloutier Held at Big Room Studios, Portland, ME. Holy Elixir, Batman! Meetup June 16, 2015

Instructor: Craig Duckett. Lecture 04: Thursday, April 5, Relationships

Topic 12: Connecting Express and Mongo

Binary, Hexadecimal and Octal number system

BEGINNER PHP Table of Contents

Better late than never

Lesson 3 Transcript: Part 1 of 2 - Tools & Scripting

Read & Download (PDF Kindle) XML For Dummies

Subscribe To The Blog Program in itunes Click Here

This book is dedicated to Sara, Inara, and the newest little one, who make it all worthwhile.

Using X-Particles with Team Render

Intro. Speed V Growth

Uninstall A Apps Windows 8 Programming Using Microsoft Visual C++

MTAT Agile Software Development

Evaluation Guide for ASP.NET Web CMS and Experience Platforms

Refactoring Without Ropes

BUILD YOUR OWN RUBY ON RAILS WEB APPLICATIONS BY PATRICK LENZ DOWNLOAD EBOOK : BUILD YOUR OWN RUBY ON RAILS WEB APPLICATIONS BY PATRICK LENZ PDF

SOAP: Cross Platform Web Services Development Using XML PDF

Digital Marketing Manager, Marketing Manager, Agency Owner. Bachelors in Marketing, Advertising, Communications, or equivalent experience

Outline for Today. How can we speed up operations that work on integer data? A simple data structure for ordered dictionaries.

How to git with proper etiquette

Author: Paul Zenden. Hot-or-Not Elixir with José Valim

Chrome if I want to. What that should do, is have my specifications run against four different instances of Chrome, in parallel.

Speech 2 Part 2 Transcript: The role of DB2 in Web 2.0 and in the IOD World

How to use the Vlookup function in Excel

Principles of Ruby Applica3on Design. Dean Wampler Senior Mentor and Consultant Object Mentor, Inc. Chicago, IL

SITE 2 SITE SYNC TRANSFER NOTES

Read & Download (PDF Kindle) Data Structures And Other Objects Using Java (4th Edition)

Real Life Web Development. Joseph Paul Cohen

It Might Be Valid, But It's Still Wrong Paul Maskens and Andy Kramek

AHHHHHHH!!!! NOT TESTING! Anything but testing! Beat me, whip me, send me to Detroit, but don t make me write tests!

Erlang 101. Google Doc

Free Downloads Professional Microsoft SQL Server 2012 Reporting Services

ShowMe Guides OpenCart 1.5 User Manual Ebooks Free

Jaywalking in Traffic Safe Migrations at Scale. Brad Urani Staff Engineer

Build Database Apps in Elixir for Scalability and Performance

How To Add Songs To Ipod Without Syncing >>>CLICK HERE<<<

Wrap up & Experimentation. CS147L Lecture 8 Mike Krieger

They are a block of code plus the bindings to the environment they came from (Ragusa Idiom / function object).

Meet our Example Buyer Persona Adele Revella, CEO

Who am I? I m a python developer who has been working on OpenStack since I currently work for Aptira, who do OpenStack, SDN, and orchestration

Note : That the code coverage tool is not available for Java CAPS Repository based projects.

Eventually, you'll be returned to the AVD Manager. From there, you'll see your new device.

mk-convert Contents 1 Converting to minikanren, quasimatically. 08 July 2014

I apologize if this has been asked before, but I've been searching the forums for the past few hours and haven't been able to find anything.

Welcome to this IBM podcast, Realizing More. Value from Your IMS Compiler Upgrade. I'm Kimberly Gist

Read & Download (PDF Kindle) DOS: Programming Success In A Day: Beginners Guide To Fast, Easy And Efficient Learning Of DOS Programming (DOS, ADA,

Formal Methods of Software Design, Eric Hehner, segment 1 page 1 out of 5

WOMBATOAM OPERATIONS & MAINTENANCE FOR ERLANG & ELIXIR SYSTEMS

WOMBATOAM OPERATIONS & MAINTENANCE FOR ERLANG & ELIXIR SYSTEMS

PROFESSOR: Last time, we took a look at an explicit control evaluator for Lisp, and that bridged the gap between

Learn Functional Programming with Elixir

Computer Error Code 2013 Mysql Server During Query Workbench

in functions). Try to play around with a few more data types and you'll be comfortable with the language in little time. Getting started: Install and

P1_L3 Operating Systems Security Page 1

a success story formal methods in HCI problem context transaction processing existing programs... didn t work mid 80s local authority DP dept

Scaling with Continuous Deployment

This Week on developerworks Push for ios, XQuery, Spark, CoffeeScript, top Rational content Episode date:

MITOCW ocw f99-lec07_300k

A developer s guide to load testing

Blog post on updates yesterday and today:

Hello, and welcome to another episode of. Getting the Most Out of IBM U2. This is Kenny Brunel, and

Data Structures And Other Objects Using Java Download Free (EPUB, PDF)

Elixir and Phoenix. fast, concurrent and explicit. Tobias pragtob.info

MITOCW watch?v=9h6muyzjms0

WINDOWS NT FILE SYSTEM INTERNALS : OSR CLASSIC REPRINTS BY RAJEEV NAGAR

ChiliProject - Bug # 529: builder is not part of the bundle. Add it to Gemfile

porcelain Documentation

DECENTRALIZED SOCIAL NETWORKING WITH WORDPRESS. November 7, 2018 WordPress Meetup Vienna Alex Kirk

Transcription:

Phoenix Is Not Your Application Lance Halvorsen ElixirConf EU 2016

Phoenix is one of your applications.

Our Language Gives Us Away

We Say I'm building a Phoenix app. Or a Rails app. Or an Ember app. Or a React app.

Is That True? No! Well, currently it might be. But, it doesn't have to be.

Ideally We Would Say I'm building a logistics app. Or an analytics app. Or a physics engine. Or a fortune cookie app.

... with a web interface.

Your application has its own domain.

Phoenix is the interface.

And Phoenix has its own domain as well.

A Thought Experiment We have a large application written in Rails. It has, say, 100 models and 75 controllers. It's been migrated from Rails 2 to Rails 3 to Rails 4. The tests are "pretty good". We now need to port this application to Rhoda, or Lotus, or...

How does that feel?

(My palms are sweating.)

Why does this seem so daunting?

Our application logic is entangled with the interface.

Our Application Logic is Dispersed across models across controllers across views maybe even across templates

Maintainability Becomes an Issue More difficult to isolate and extract pieces as the application grows. More difficult to localize problems when the domains are mixed.

Portability Becomes an Issue Changing frameworks is almost certainly going to require a rewrite. Since the logic is dispersed, it's easy to miss functionality during the rewrite. Tests will help, but even excellent coverage can't guarantee that you'll catch everything.

The solution is to build our application separately first, and layer the interface on top later.

How does this happen?

It begins with the HTTP request/response cycle.

HTTP is stateless. But applications are not. We store state in the database. We need to get state on each request. We need to write state back to the db after change. We lean on the framework for data access. We model our domain entities for databases.

Consider the Difference class Comment class Comment < ActiveRecord::Base

class Comment # do comment things class Comment < ActiveRecord::Base # do comment things # handle database connections # model database relationships # validate data # generate and execute SQL # and the list goes on

A Comment is part of our domain. A Comment model is part of ActiveRecord's domain.

Ecto Has a Better Story There are no models, only schemas. There is good separation of concerns. But we're still thinking in database terms.

Models are not part of our domain.

A router probably isn't part of our domain either.

Or a controller.

Or a view.

We need to decouple our domain from the framework's domain.

Both in the code and in our heads.

OTP has an answer.

Applications

An OTP Application is a Behaviour.

A Behaviour Is A distillation of years of experience by the Erlang team A Design pattern A module of code common to each Behaviour (the wiring and plumbing) A list of callbacks for customization

But what is an Application, really? It's an indepent module or group of modules. It implements a specific piece of functionality. It can be started and stopped indepently. It is supervised. It can be reused in other OTP Applications.

We already use them all the time. Every mix.exs file will have a list of depent OTP Applications. def application do [mod: {HelloPhoenix, []}, applications[:phoenix, :phoenix_html, :cowboy, :logger, :gettext, :phoenix_ecto, :postgrex]] }

We can see our Applications with :observer.start.

Let's say we have a freshly generated Phoenix application called hello_phoenix.

$ iex -S mix phoenix.server iex> :observer.start

For our hello_phoenix app, we define the Application module in lib/ hello_phoenix.ex.

defmodule HelloPhoenix do use Application def start(_type, _args) do import Supervisor.Spec, warn: false children = [ supervisor(hellophoenix.endpoint, []), supervisor(hellophoenix.repo, []), ] opts = [strategy: :one_for_one, name: HelloPhoenix.Supervisor] Supervisor.start_link(children, opts) def config_change(changed, _new, removed) do HelloPhoenix.Endpoint.config_change(changed, removed) :ok

defmodule HelloPhoenix do use Application def start(_type, _args) do import Supervisor.Spec, warn: false children = [ supervisor(hellophoenix.endpoint, []), supervisor(hellophoenix.repo, []), ] opts = [strategy: :one_for_one, name: HelloPhoenix.Supervisor] Supervisor.start_link(children, opts) def config_change(changed, _new, removed) do HelloPhoenix.Endpoint.config_change(changed, removed) :ok

defmodule HelloPhoenix do use Application def start(_type, _args) do import Supervisor.Spec, warn: false children = [ } supervisor(hellophoenix.endpoint, []), supervisor(hellophoenix.repo, []), ] opts = [strategy: :one_for_one, name: HelloPhoenix.Supervisor] Supervisor.start_link(children, opts) def config_change(changed, _new, removed) do HelloPhoenix.Endpoint.config_change(changed, removed) :ok

defmodule HelloPhoenix do use Application def start(_type, _args) do import Supervisor.Spec, warn: false children = [ supervisor(hellophoenix.endpoint, []), supervisor(hellophoenix.repo, []), ] opts = [strategy: :one_for_one, name: HelloPhoenix.Supervisor] Supervisor.start_link(children, opts) def config_change(changed, _new, removed) do HelloPhoenix.Endpoint.config_change(changed, removed) :ok

defmodule HelloPhoenix do use Application def start(_type, _args) do import Supervisor.Spec, warn: false children = [ supervisor(hellophoenix.endpoint, []), supervisor(hellophoenix.repo, []), ] opts = [strategy: :one_for_one, name: HelloPhoenix.Supervisor] Supervisor.start_link(children, opts) def config_change(changed, _new, removed) do HelloPhoenix.Endpoint.config_change(changed, removed) :ok

How does this help us separate domains?

We can build our application in pure Elixir. And generate a new Phoenix application. Then import our Elixir application into our Phoenix application. Finally, we can have the Phoenix domain elements talk to our real domain, in the Elixir application.

Big Box App

Interface Application

Let's Try It Out

Step 1 Generate a new mix application

$ mix new kv --module KV --sup * creating README.md * creating.gitignore * creating mix.exs * creating config * creating config/config.exs * creating lib * creating lib/kv.ex * creating test * creating test/test_helper.exs * creating test/kv_test.exs Your Mix project was created successfully. You can use "mix" to compile it, test it, and more: cd kv mix test Run "mix help" for more commands.

Step 1.5 Follow the rest of the tutorial :^)

Step 2. Build out the KV GenServer

defmodule KV.Registry do use GenServer def start_link do GenServer.start_link( MODULE, :ok, name: :kv_registry) def lookup(server, name) do GenServer.call(server, {:lookup, name}) def create(server, name) do GenServer.call(server, {:create, name}) def init(:ok) do {:ok, %{}} def handle_call({:create, name}, _from, names) do if Map.has_key?(names, name) do {:reply, name, names} else {:ok, bucket} = KV.Bucket.start_link names = Map.put(names, name, bucket) {:reply, name, names}

defmodule KV.Registry do use GenServer def start_link do GenServer.start_link( MODULE, :ok, name: :kv_registry) def lookup(server, name) do GenServer.call(server, {:lookup, name}) def create(server, name) do GenServer.call(server, {:create, name}) # callbacks go here

defmodule KV.Registry do # client functions go here def init(:ok) do {:ok, %{}} def handle_call({:lookup, name}, _from, names) do {:reply, Map.fetch(names, name), names} def handle_call({:create, name}, _from, names) do if Map.has_key?(names, name) do {:reply, name, names} else {:ok, bucket} = KV.Bucket.start_link names = Map.put(names, name, bucket) {:reply, name, names}

defmodule KV.Registry do use GenServer

defmodule KV.Registry do def start_link do GenServer.start_link( MODULE, :ok, name: :kv_registry)... def init(:ok) do {:ok, %{}}

defmodule KV.Registry do def start_link do GenServer.start_link( MODULE, :ok, name: :kv_registry)... def init(:ok) do {:ok, %{}}

defmodule KV.Registry do def start_link do GenServer.start_link( MODULE, :ok, name: :kv_registry)... def init(:ok) do {:ok, %{}}

defmodule KV.Registry do def start_link do GenServer.start_link( MODULE, :ok, name: :kv_registry)... def init(:ok) do {:ok, %{}}

defmodule KV.Registry do def start_link do GenServer.start_link( MODULE, :ok, name: :kv_registry)... def init(:ok) do {:ok, %{}}

defmodule KV.Registry do def start_link do GenServer.start_link( MODULE, :ok, name: :kv_registry)... def init(:ok) do {:ok, %{}}

defmodule KV.Registry do def start_link do GenServer.start_link( MODULE, :ok, name: :kv_registry)... def init(:ok) do {:ok, %{}}

defmodule KV.Registry do def start_link do GenServer.start_link( MODULE, :ok, name: :kv_registry)... def init(:ok) do {:ok, %{}}

defmodule KV.Registry do def lookup(server, name) do GenServer.call(server, {:lookup, name})... def handle_call({:lookup, name}, _from, names) do {:reply, Map.fetch(names, name), names}

defmodule KV.Registry do :kv_registry def lookup(server, name) do GenServer.call(server, {:lookup, name})... def handle_call({:lookup, name}, _from, names) do {:reply, Map.fetch(names, name), names}

defmodule KV.Registry do def lookup(server, name) do GenServer.call(server, {:lookup, name})... def handle_call({:lookup, name}, _from, names) do {:reply, Map.fetch(names, name), names}

defmodule KV.Registry do def lookup(server, name) do GenServer.call(server, {:lookup, name})... def handle_call({:lookup, name}, _from, names) do {:reply, Map.fetch(names, name), names}

defmodule KV.Registry do def lookup(server, name) do GenServer.call(server, {:lookup, name})... def handle_call({:lookup, name}, _from, names) do {:reply, Map.fetch(names, name), names}

defmodule KV.Registry do def lookup(server, name) do GenServer.call(server, {:lookup, name})... def handle_call({:lookup, name}, _from, names) do {:reply, Map.fetch(names, name), names}

defmodule KV.Registry do def lookup(server, name) do GenServer.call(server, {:lookup, name})... def handle_call({:lookup, name}, _from, names) do {:reply, Map.fetch(names, name), names}

defmodule KV.Registry do def lookup(server, name) do GenServer.call(server, {:lookup, name})... def handle_call({:lookup, name}, _from, names) do {:reply, Map.fetch(names, name), names}

defmodule KV.Registry do def lookup(server, name) do GenServer.call(server, {:lookup, name})... def handle_call({:lookup, name}, _from, names) do {:reply, Map.fetch(names, name), names}

defmodule KV.Registry do def lookup(server, name) do GenServer.call(server, {:lookup, name})... def handle_call({:lookup, name}, _from, names) do {:reply, Map.fetch(names, name), names}

defmodule KV.Registry do def create(server, name) do GenServer.call(server, {:create, name})... def handle_call({:create, name}, _from, names) do if Map.has_key?(names, name) do {:reply, name, names} else {:ok, bucket} = KV.Bucket.start_link names = Map.put(names, name, bucket) {:reply, name, names}

defmodule KV.Registry do def create(server, name) do GenServer.call(server, {:create, name})... def handle_call({:create, name}, _from, names) do if Map.has_key?(names, name) do {:reply, name, names} else {:ok, bucket} = KV.Bucket.start_link names = Map.put(names, name, bucket) {:reply, name, names}

defmodule KV.Registry do def create(server, name) do GenServer.call(server, {:create, name})... def handle_call({:create, name}, _from, names) do if Map.has_key?(names, name) do {:reply, name, names} else {:ok, bucket} = KV.Bucket.start_link names = Map.put(names, name, bucket) {:reply, name, names}

Step 4. Register the GenServer as a Worker

Add the KV.Registry module as a worker in lib/kv.ex. This ensures the worker will start when the application does. defmodule KV do use Application def start(_type, _args) do import Supervisor.Spec, warn: false children = [ worker(kv.registry, []) ] opts = [strategy: :one_for_one, name: KV.Supervisor] Supervisor.start_link(children, opts)

Step 5. Generate a new Phoenix application

Call it kv_interface Generate it without Ecto $ mix phoenix.new kv_interface no-ecto * creating kv_interface/config/config.exs * creating kv_interface/config/dev.exs <snip> * creating kv_interface/web/views/layout_view.ex * creating kv_interface/web/views/page_view.ex Fetch and install depencies? [Yn] y * running mix deps.get * running npm install && node node_modules/brunch/bin/brunch build We are all set! Run your Phoenix application: $ cd kv_interface $ mix phoenix.server You can also run your app inside IEx (Interactive Elixir) as: $ iex -S mix phoenix.server

Step 6. Bring KV into kv_interface

Add it as a depency in mix.exs. defp deps do [{:phoenix, "~> 1.1.4"}, {:phoenix_html, "~> 2.4"}, {:phoenix_live_reload, "~> 1.0", only: :dev}, {:gettext, "~> 0.9"}, {:cowboy, "~> 1.0"}, {:kv, path: "../kv"}]

Step 7. Add KV to the application list

In mix.exs. def application do [mod: {KvInterface, []}, applications: [:phoenix, :phoenix_html, :cowboy, :logger, :gettext, :kv]]

Re-check with :observer.start

Get Them Talking Together Add a controller at web/controllers/ registry_controller.ex. defmodule KvInterface.RegistryController do use KvInterface.Web, :controller

Add a show Action def show(conn, %{"name" => name}) do item = KV.Registry.lookup(:kv_registry, name) rer(conn, "show.html", item: item)

Add a create Action def create(conn, %{"name" => name}) do item = KV.Registry.create(:kv_registry, name) rer(conn, "show.html", item: item)

What have we gained? Isolation! Develop in isolation Test in isolation Domain separation Maintainability Portability

Thank you! That's a wrap. And this is me: @lance_halvorsen github/lancehalvorsen