Beyond
gabrielelana !
gabrielelana "
gabriele.lana@gmail.com #
You are here
You should
be here
ElixirConf.EU
Ecto is not
your Model
— Michał Muskała
“
”Member of the Ecto core team
maintainer of the MongoDB adapter.
Be defensive at your
boundaries, trust
your internals
DATA
VALIDATION
INTERNALS
Validation at storage boundary
leads to an anemic domain model
Why do you stick
everything in a
database?
“
”— Joe Armstrong
MicroservicesDo you need a storage abstraction?
Your storage must be
independent from the
storage of other services
and the storage must be
close to your problem, so
my answer is no
It′s an abstraction or just indirection?
Phoenix is
not your
application
— Lance Halvorsen
“
”P
of the Phoenix Guides.
Onboarding
Exploiting
Framework?
APPLICATION
PHOENIX
A framework must make
decisions. You cannot
make good or bad
decisions without a
context
If you are not ruling
that's ok, but better
be worth it
OTP
How many Phoenix
applications in the
wild have at least
one generic server?
🔎
CLOSER
LOOK
defmodule HelloPhoenix.Router do
use HelloPhoenix.Web, :router
!
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_flash
plug :protect_from_forgery
plug :put_secure_browser_headers
end
!
pipeline :api do
plug :accepts, ["json"]
end
!
scope "/", HelloPhoenix do
pipe_through :browser
!
get "/", PageController, :index
end
!
# ...
end
Poor HTTP support
defmodule Chat.RoomChannel do
use Phoenix.Channel
!
# ...
!
def handle_in("new:msg", msg, socket) do
broadcast! socket, "new:msg", %{user: msg["user"], body: msg["body"]}
{:reply, {:ok, %{msg: msg["body"]}}, assign(socket, :user, msg["user"])}
end
end
Bidirectional sockets
Reinventing HTTP over WS
• error conditions
• response status
• chaching
• …⚠
CRUD resources?
Server side rendering?
Flash messages?
Generators?
#
WHAT
THEN?
Embrace HTTP
#
Embrace HTTP
#-module(ping_resource).
-compile([export_all]).
-include_lib("webmachine/include/webmachine.hrl").
!
init(Config) ->
{ok, empty}.
!
content_types_provided(Request, State) ->
{[{"text/html", to_html}, {"text/plain", to_text}], Request, State}.
!
allowed_methods(Request, State) ->
{['GET', 'HEAD'], Request, State}.
!
to_html(Request, State) ->
Response = "<html><body>PONG</body></html>"
{Response, Request, State}.
!
to_text(Request, State) ->
Response = "PONG"
{Response, Request, State}.
Flip dependencies
#defmodule Ping.Resource do
use HTTP.Resource
!
route "/counter/:id"
!
def init(conn), do: {:ok, conn, %{}}
!
def allowed_methods(conn, state), do: ["GET", "POST"]
!
def exists?(conn, {id, state}), do: {id, state |> Map.has_key?(id)}
def exists?(conn, state), do: {p(conn, :id), state}
!
def retrieve(conn, {id, state}), do: {id, state |> Map.get(id)}
!
def create(conn, {id, state}), do: {id, state |> Map.update!(id, &(&1 + 1))}
!
@content_type "text/plain"
def to_text(conn, {id, state}), do: {id, state |> Map.get(id)}
end
HTTP
APPLICATION
Flip dependencies
#
var express = require('express')
var app = express()
!
app.get('/ping', (req, res) => {
res.send('pong')
})
!
app.listen(3000, () => {
console.log('Listening on port 3000')
})
More specific frameworks
#
Go stateful
#go
[ self chooseCheese.
self confirmCheese
] whileFalse.
self informCheese
!
chooseCheese
cheese := self
chooseFrom: #('Greyerzer' 'Tilsiter' 'Sbrinz')
caption: 'What''s your favorite Cheese?'.
cheese isNil ifTrue: [ self chooseCheese ]
!
confirmCheese
^ self confirm: 'Is ', cheese, 'your favorite Cheese?'
!
informCheese
self inform: 'Your favorite is ', cheese, '.'
is a continuation-based
web application framework
Change data flow
#aggregate
$
write
model
commands
(HTTP)
event
bus
events
events
events
(WS)
events
$
event
store
events
projections
$
read
model
events
services
…
…
query
facade
queries
(HTTP)
data
(HTTP)
CQRS/ES
Beyond
Questions?

Beyond Phoenix