Loading presentation...

Present Remotely

Send the link below via email or IM

Copy

Present to your audience

Start remote presentation

  • Invited audience members will follow you as you navigate and present
  • People invited to a presentation do not need a Prezi account
  • This link expires 10 minutes after you close the presentation
  • A maximum of 30 users can follow your presentation
  • Learn more about this feature in our knowledge base article

Do you really want to delete this prezi?

Neither you, nor the coeditors you shared it with will be able to recover it again.

DeleteCancel

Make your likes visible on Facebook?

Connect your Facebook account to Prezi and let your likes appear on your timeline.
You can change this under Settings & Account at any time.

No, thanks

FiaR @ ErlangBA Meetup

No description
by

Inaka Labs

on 10 November 2014

Comments (0)

Please log in to add your comment.

Report abuse

Transcript of FiaR @ ErlangBA Meetup

Four in a Row
Learning Erlang at Inaka
About Euen
About Brujo
About Euen
About Inaka
Goals
Goals
Developer since I was 10
15 years in the industry
6 years of Erlang
now CTO @ Inaka
3 years
teaching
Erlang
Web Developer
Worked with PHP / Ruby
3 years of experience
Studying Software Engineering
No previous knowledge of Erlang
We're based in Argentina
We're part of
Erlang Solutions
We do Erlang consulting
We build end-to-end applications with
Erlang
, Ruby, Node.js, Android and iOS components
We develop highly-concurrent servers for applications like
Whisper
Our devs are
polyglots
Recreate a
real-life
experience
Without
real-life
pressure
Teach
our way
of work
Teach
git[hub]
, code-reviews, TDD, etc.
Generate something actually useful
Encourage the developer to
learn by doing
Start
doing stuff
right away
Learn
practical
Erlang
Face
real
problems
Create
something
Team
working
Task:
a module that represents a
match
Expected Functions:
start
() -> match()
play
(col(), match()) ->
won
|
drawn
| {
next
, match()}
Lessons:
Project Setup
erlang.mk
TDD Basics
Sequential Erlang
Step #1
The Core
Step #1
The Core
Expected Functions:
start
() -> {
ok
, pid()}
play
(pid(), col()) ->
won
|
drawn
|
next
stop
(pid()) ->
ok
Lessons:
OTP:
gen_server
Concurrent Erlang
Processes, pids,…
Behaviours
Step #2
Processes
Step #3
Supervision
Task:
a
supervisor
for the
matches
Expected Functions:
start_link
() -> {
ok
, pid()}
start_match
() -> {
ok
, pid()}
Lessons:
OTP:
supervisor
Basic app structure
Step #2
Processes
Task:
a
process
that represents a
match
Step #3
Supervision
Step #4
Storage
Tasks:
Use
sumo_db
to persist in
MySQL
Add player names and timestamps
Use
fiar
as a
facade
module
Lessons:
The use of facade modules for libraries
OTP:
Application
Application configuration
Repository model for persistency
Step #4
Storage
Starting a Match
contains_four
(_Chip, List)
when

length
(List) < 4
->

false
;
contains_four
(Chip, [Chip, Chip, Chip, Chip | _ ])
->

true
;
contains_four
(Chip, [ _ | Rest ]) ->
contains_four
(Chip, Rest).
Recursion & Pattern Matching
Playing
Interesting Pieces
-
record
(
state
,
{
board
::
board
(),

next_chip
= 1 ::
chip
()
}).

start
()
->

#
state
{
board
= {[], [], [], [], [], [], []}}.
play
(Col, _)
when
Col < 1
orelse
Col > 7
->
throw
(invalid_column);
play
(Col, State =
#state
{
board
= Board,
next_chip
= NextChip})
->
Column =
element
(Col, Board),
case
Column
of
[_, _, _, _, _, _, _] ->
throw
(
invalid_column
);
[NextChip, NextChip, NextChip | _] ->
won
;
_ ->
NewColumn = [NextChip | Column],
NewBoard =
setelement
(Col, Board, NewColumn),
case

analyze
(Col, NewColumn, NextChip, NewBoard)
of
next
->
NewState = State
#state
{
board
= NewBoard,
next_chip
=
diff_chip
(NextChip)},
{
next
, NewState};
won
->
won
;
drawn
->
drawn
end
end
.
get_row
(RowNum, Board)
->
Columns =
tuple_to_list
(Board),
lists
:
map
(
fun
(Column) ->
get_chip
(RowNum, Column)
end
, Columns).
High Order Functions
Starting a Match
Playing
-
module
(
fiar_match
).

start
()
->
gen_server
:
start_link
(
fiar_match
, [], []).
play
(Pid, Col)
->

case

gen_server
:
call
(Pid, {
play
, Col})
of
{
ok
, Reply} -> Reply;
{
error
, Ex} ->
throw
(Ex)
end
.

handle_call
({
play
, Col}, _From, State)
->
try
{Reply, NewState} =
case

fiar_core
:
play
(Col, State)
of
{Result, NextState} -> {Result, NextState};
Result -> {Result, State}
end
,
{reply, {
ok
, Reply}, NewState}
catch
_:Ex ->
{
reply
, {
error
, Ex}, State}
end
.
Starting a Match
Interesting Pieces
-
module
(
fiar_sup
).

start_match
() ->
supervisor
:
start_child
(
fiar_sup
, []).
-
module
(
fiar_sup
).

start_link
() ->
supervisor
:
start_link
(
{
local
,
fiar_sup
},
fiar_sup
, []).

init
([]) ->
{
ok
, {{
simple_one_for_one
, 10, 60},
[{
fiar_match
, {
fiar_match
,
start
, []},
permanent
, 5000,
worker
, [
fiar_match
]}]
}}.
Starting a Match
Playing
-
module
(
fiar_match_repo
).

start
(Player1, Player2)
->
Match =
fiar_match
:
new
(
Player1, Player2),
StoredMatch =
sumo
:
persist
(
fiar_match
, Match),
fiar_match
:
get_id
(StoredMatch).
play
(Mid, Col)
->
Match =
case

sumo
:
find
(
fiar_match
, Mid)
of
notfound
->
throw
({
notfound
, Mid});
M -> M
end
,
Status =
fiar_match
:
get_status
(Match),
case
Status
of
on_course
->
State =
fiar_match
:
get_state
(Match),
{Reply, Status0, State0} =
do_play
(Col, State, Status)
Match0 =
fiar_match
:
set_status
(Match, Status0),
Match1 =
fiar_match
:
set_state
(Match0, State0),
Match2 =
fiar_match
:
set_updated_at
(Match1),
sumo
:
persist
(
fiar_match
, Match2),
Reply;
Status ->
throw
({
match_finished
, Status})
end
.
Step #5
REST
Tasks:
Use
cowboy
to provide a RESTful API:
GET
|
POST
/matches
GET
|
PUT
/matches/
:match_id
Lessons:
HTTP
Protocol
REST
ful
API
Design
Using
Cowboy
and
Shotgun
Working with
JSON
in Erlang
-
module
(
fiar_single_match_handler
).

handle_put
(Req, State)
->
{MatchIdBin, Req1} =
cowboy_req
:
binding
(
match_id
, Req),
try

MatchId =
binary_to_integer
(MatchIdBin, 10),
{ok, Body, Req2} =
cowboy_req
:
body
(Req1),
Col =
maps
:
get
(
<<"column">>
,
jiffy
:
decode
(Body)),
fiar
:
play
(MatchId, Col),
Match =
fiar
:
get_match
(MatchId),
MatchJson =
fiar_match
:
to_json
(Match),
RespBody =
jiffy
:
encode
(MatchJson),
Req3 =
cowboy_req
:
set_resp_body
(RespBody, Req2),
{
true
, Req3, State}
catch
_:Exception ->
fiar_utils
:
handle_exception
(Exception, Req1, State)
end
.
Starting a Match
start_cowboy_listeners
()
->
Dispatch =
cowboy_router
:
compile
(
[
{
'_'
, [
{
"/matches"
,
fiar_matches_handler
, []},
{
"/matches/:match_id"
,
fiar_single_match_handler
, []}
]}
]),
cowboy
:
start_http
(
fiar_http_listener
, 100, [{
port
, 8080}],
[{
env
, [{
dispatch
, Dispatch}]}]
).
Routing Tables in Cowboy
Playing
Interesting Pieces
-
module
(
fiar_matches_handler
).

handle_post
(Req, State)
->
{ok, Body, Req1} =
cowboy_req
:
body
(Req),
try
Decoded =
jiffy
:
decode
(Body, [
return_maps
]),
Player1 =
maps
:
get
(
<<"player1">>
, Decoded),
Player2 =
maps
:
get
(
<<"player2">>
, Decoded),
Mid =
fiar
:
start_match
(Player1, Player2),
Match =
fiar
:
get_match
(Mid),
MatchJson =
fiar_match
:
to_json
(Match),
RespBody =
jiffy
:
encode
(MatchJson),
Req2 =
cowboy_req
:
set_resp_body
(RespBody, Req1),
{
true
, Req2, State}
catch
_:Exception ->
fiar_utils
:
handle_exception
(
Exception, Req1, State)
end
.
Step #5
REST
Step #6
Users
Tasks:
Add basic authentication to endpoints
New Endpoint:
POST
/users
Lessons:
Basic Auth
over
HTTP
Refactoring & Abstraction
-
module
(
fiar_single_match_handler
).

is_authorized
(Req, State)
->
case

fiar_auth
:
check_auth
(Req)
of
{
authenticated
, User, _Req1} ->
{
true
, Req,
#
{
user
=> User}};
{
not_authenticated
, AuthHeader, Req1} ->
{{
false
, AuthHeader}, Req1, State}
end
.
Creating a User
-
module
(
fiar_auth
).

credentials
(Req)
->
try

cowboy_req
:
parse_header
(
<<"authorization">>
, Req)
of
{
ok
, {
<<"basic">>
, Credentials}, _} -> Credentials;
{
ok
, _, _Req1} ->
undefined
catch
_:Error ->
Msg =
"error on auth: ~p~n\tStack: ~p~n"
,
lager
:
error
(Msg, [Error,
get_stacktrace
()]),
throw
(Exception)
end
.
Basic Authentication in Cowboy
Authenticating
Interesting Pieces
-
module
(
fiar_users_handler
).

handle_post
(Req, State)
->
{ok, Body, Req1} =
cowboy_req
:
body
(Req),
try
Decoded =
jiffy
:
decode
(Body, [
return_maps
]),
Username =
maps
:
get
(
<<"username">>
, Decoded),
User =
fiar
:
new_user
(Username),
UserJson =
fiar_user
:
to_json
(User),
RespBody =
jiffy
:
encode
(UserJson),
Req2 =
cowboy_req
:
set_resp_body
(RespBody, Req1),
{
true
, Req2, State}
catch
_:Exception ->
fiar_utils
:
handle_exception
(Exception, Req1, State)
end
.
Step #6
Users
Step #7
SSE
Tasks:
Add Server-Sent Events support
New Endpoint:
GET
/matches/:match_id/events
Lessons:
SSE
over
HTTP
lasse
OTP
: gen_event
Decoupling
with
events
Process registration
-
module
(
fiar_event_handler
).

start_link
() ->
{
ok
, Pid} =
gen_event
:
start_link
({
local
,
fiar_events
}),
gen_event
:
add_handler
(
fiar_events
,
fiar_event_handler
, {}),
{
ok
, Pid}.

handle_event
(
{
fiar_match
,
updated
, [Match]}, State)
->
UserId =
fiar_match
:
get_player
(Match),
fiar
:
notify
(
match_updated
, UserId, Match),
{
ok
, State};
Connecting to SSE
Dispatching Events
System Sequence
-
module
(
fiar_notify_handler
).






















Step #7
SSE
B9
E9
notify
(MatchId, UserId, Match)
->
ProcessName =
process_name
(MatchId, UserId),
lasse_handler
:
notify
(ProcessName, Match).
init
(_InitArgs, _LastEventId, Req)
->
try
case

fiar_auth
:
check_auth
(Req)
of
{
authenticated
, User, Req1} ->
{MatchId, Req2} =
cowboy_req
:
binding
(match_id, Req1),
fiar
:
get_match
(MatchId, User),
process_register
(MatchId, User),
{
ok
, Req2,
#
{
user
=> User}};
{
not_authenticated
, _AuthHeader, Req1} ->
{
shutdown
, 401, [], [], Req1,
#
{}}
end
catch
_:
notfound
-> {
shutdown
, 404, [], [], Req, #{}}
end
.
handle_notify
(Match, State)
->
MatchJson =
jiffy
:
encode
(
fiar_match
:
to_json
(Match)),
{
send
, [{
data
, MatchJson}, {
name
,
<<"turn">>
}], State}.
Euen
Brujo
notify_h
match_h
sumo
emysql
events
GET /matches/53/events
pr-reg
register(…)
PUT /matches/53
persist(…)
UPDATE matches …
notify(…)
ok
204 No Content
find(…)
notify_h
{match, updated, 53}
event: turn / data: …
Step #8
Website Ready
Tasks:
Get the server ready to support a client
New Endpoints:
GET
/events
DELETE
/matches/:match_id
Lessons
:
Changing
requirements
pg2
&
broadcasting
events
Extending
sumo_db
repos
Custom
events
Getting Global Events
Playing
-
module
(
fiar_notify_users_handler
).

init
(_InitArgs, _LastEventId, Req)
->
case

fiar_auth
:
check_auth
(Req)
of
{
authenticated
, User, Req1} ->
process_register
(
fiar_user
:
get_id
(User)),
pg2
:
join
(
fiar_connected_users
,
self
()),
ConnectedUsers =
get_connected_users
(),
FirstEvent =
[{
data
,
jiffy
:
encode
(ConnectedUsers)}],
fiar
:
send_event
(
fiar_user
,
connected
, [User]),
{
ok
, Req, [FirstEvent],
#
{
user
=> User}};
{
not_authenticated
, _AuthHeader, Req1} ->
{
shutdown
, 401, [], [], Req1,
#
{}}
end
.
Step #8
Website Ready
-
module
(
fiar_notify_users_handler
).

broadcast
(EventName, Content)
->
Pids =
pg2
:
get_members
(
fiar_connected_users
),
lists
:
foreach
(
fun
(Pid)
->
lasse_handler
:
notify
(
Pid, {EventName, Content})
end
, Pids).
B9
Step #9
Website
Task
:
Create a basic website for the app
Lessons
:
Changing
requirements (again)
Static content
with cowboy
EventSource
in Javascript
Cookies
with cowboy & shotgun
Static Contents
Cookies
-
module
(
fiar
).

start_cowboy_listeners
() ->
Dispatch =
cowboy_router
:
compile
([
{
'_'
,
[{
"/matches"
,
fiar_matches_handler
, []},
{
"/matches/:match_id"
,
fiar_single_match_handler
, []},
{
"/users"
,
fiar_users_handler
, []},
{
"/matches/:match_id/events"
,
lasse_handler
, [
fiar_notify_handler
]},
{
"/events"
,
lasse_handler
, [
fiar_notify_users_handler
]},
{
<<"/">>
,
cowboy_static
, {
file
,
"./priv/static/index.html"
}},
{
"/priv/static/[...]"
,
cowboy_static
, {
dir
,
"priv/static/"
}}
]}]),
cowboy
:
start_http
(
fiar_http_listener
, 100, [{
port
, 8080}],
[{
env
, [{
dispatch
, Dispatch}]}]).
-
module
(
fiar_auth
).

cookie_credentials
(Req)
->
case

cowboy_req
:
cookie
(
<<"auth">>
, Req)
of
{
undefined
, _} ->
undefined
;
{Token, _} ->
parse_cookie_auth
(Token)
end
.

parse_cookie_auth
(Token)
->
UserPass =
base64
:
decode
(Token),
case

binary
:
split
(UserPass,
<<":">>
)
of
[User, Pass] -> {User, Pass};
_ ->
undefined
end
.
Step #9
Website
Next Steps
Cache
in front of SumoDB
Multi-Node
support
Sessions
for security (re: cookies)
Final Words
Summary
Resources
github.com/inaka/fiar
prezi.com/user/inaka
inaka.net
inaka.net/blog
inaka.github.io
about.me/elbrujohalcon
We've learned:
Erlang & OTP
HTTP & SSE
Concurrent System
Architecture
TDD
Cowboy, Shotgun, Lasse
SumoDB
Workflow
Code Style
BRACE YOURSELVES
WALLS OF CODE ARE COMING
Full transcript