More than
Syntax
Patrick Huesler

@phuesler
More than
Syntax
web apis with
   erlang
a ruby dev’s POV
snow
boarding
Surfing
WIPEOUT
erlang
I’m an erlang

    beginner
so don’t believe everything
           I say
this is not an
  erlang
introduction
erlang
refresher
functional
 language
runtime
system
open telecom platform

      OTP
why

erlang?
seriously

concurrent
with built in
actors
because it is

distributed
fault

tolerant
hot
swaping
remote
shell
erlang at
wooga
technical
challenges
monthly active users

20,543,500
for Diamond Dash

 http://www.appdata.com/apps/facebook/127995567256931-diamond-dash (03/10/2012)
daily active users

3,871,133
for Diamond Dash

http://www.appdata.com/apps/facebook/127995567256931-diamond-dash (03/10/2012)
backend traffic up to

 6,500 RPS
for Monster World
what does mean that for a


Database?
read/write ratio?

write heavy
different
architectures
no

   database
is faster than no database
stateful
Let’s use

   S3
all the way
http://www.slideshare.net/wooga/from-0-to-1000000-daily-users-with-erlang
http://www.slideshare.net/wooga/from-0-to-1000000-daily-users-with-erlang
processes are

cheap
  in erlang
erlang

Web APIs
rebar
$:rebar create-app appid=aloha
how to manage

dependencies
 like rubygems does?
rebar
$: rebar get-deps
$: rebar compile
$: rebar get-deps compile
1 % Compiler Options for rebar
   2 {erl_opts, [
   3     {src_dirs, ["src", "test"]},
   4     debug_info
   5 ]}.
   6
   7 % Dependencies
   8 {deps, [
   9     {elli, ".*", {git, "git://github.com/knutin/elli.git", "HEAD"}},
  10     {etest, ".*", {git, "git://github.com/wooga/etest.git", "HEAD"}},
  11     {etest_http, ".*", {git, "git://github.com/wooga/etest_http.git",
HEAD"}}
  12 ]}.
  13
  14 % Which files to cleanup when rebar clean is executed.
  15 {clean_files, ["ebin/*.beam"]}.
What

webserver
 shall we use?
elli
1 elli:start_link([
2                      {callback, aloha_api},
3                      {port, 3000}
4                 ])
how do I do

Routing?
1   Path = [<<"foo">>, <<"bar">>],
 2   HTTPMethod = "GET",
 3   Request = {HTTPMethod, Path},
 4   RouteDefinitions = [{{"GET",[<<"foo">>,<<"bar">>]},
                 {my_handler, my_method, []}}],
 5   case erl_route_url:match(Request, RouteDefinitions) of
 6        {error, notfound} ->
 7            io:format("path not found");
 8        {ok, Params, {M,F,_}} ->
 9            apply(M,F,Params)
10   end.
you’re doing it
wrong!!!
use

pattern
matching
1 handle(Req, _Args) ->
2     Path = elli_request:path(Req),
3     handle(Req#req.method, Path, Req).
4
5 handle('GET',[<<"foo">>, <<"bar"], Req) ->
6     {ok, [], <<"kekahi mau pipi">>};
7
8 handle(_, _, _Req) ->
9     {404, [], <<"wipe out!">>}.
does it support

middleware?
1 Config = [
 2            {mods, [
 3                     {aloha_ware, []},
 4                     {mahalo_ware, []},
 5                   ]}
 6          ],
 7
 8 elli:start_link([
 9                  {callback, aloha_api},
10                  {port, 3000},
11                  {callback_args, Config}
12              ]).
events
request_complete
request_throw
request_exit
request_error
request_parse_error
bad_request
client_closed
client_timeout
elli_startup
1 handle_event(request_complete, [
 2                                 Req,
 3                                 ResponseCode,
 4                                 ResponseHeaders,
 5                                 ResponseBody,
 6                                 Timings
 7                                ], Config) ->
 8
 9     TimingKeys = [
10         accepted,
11         request_start,
12         heades_end,
13         body_end,
14         user_start,
15         user_end,
16         request_end
17     ],
18     ok;
awesome
monitoring
1 handle_event(request_throw, [
 2                                  Req,
 3                                  Exception,Stack
 4                               ], _Config) ->
 5     ok;
 6
 7 handle_event(request_exit, [
 8                                  Req,
 9                                  Exit,
10                                  Stack
11                            ], _Config) ->
12     ok;
13
14 handle_event(request_error, [
15                                  Req,
16                                  Error,
17                                  Stack
18                              ], _Config) ->
19     ok;
20
how about

environments?
$: erl -pa deps/*/ebin ebin n
   -config myconfig
1 [
                Config file
 2       {aloha, [
 3              {worker_port, 3333}
 4            ]
 5       },
 6
 7      {lager, [
 8         {handlers, [
 9            {lager_console_backend, debug},
10            {lager_file_backend, [
11               {"log/debug.log", debug, 10485760, "$D0", 5},
12               {"log/error.log", error, 10485760, "$D0", 5},
13               {"log/console.log", info, 10485760, "$D0", 5}
14            ]}
15         ]}
16      ]}
17 ].
how about

logging?
1   error_logger:info_msg("alohan").
2   % prints alohanok
3
4   error_logger:warning_msg("freak set").
5   % prints freak set ahead nok
6
7   error_logger:error_msg("wipe outn").
8   % prints wipe outnok
sasl
=PROGRESS REPORT==== 8-Oct-2012::12:06:24 ===
          supervisor: {local,sasl_safe_sup}
             started: [{pid,<0.39.0>},
                       {name,overload},
                       {mfargs,{overload,start_link,
[]}},
                       {restart_type,permanent},
                       {shutdown,2000},
                       {child_type,worker}]

=PROGRESS REPORT==== 8-Oct-2012::12:06:24 ===
          supervisor: {local,sasl_sup}
             started: [{pid,<0.37.0>},
                       {name,sasl_safe_sup},
                       {mfargs,
                           {supervisor,start_link,

[{local,sasl_safe_sup},sasl,safe]}},
how about
unix style?
lager
1 [
                Config file
 2       {aloha, [
 3              {worker_port, 3333}
 4            ]
 5       },
 6
 7      {lager, [
 8         {handlers, [
 9            {lager_console_backend, debug},
10            {lager_file_backend, [
11               {"log/debug.log", debug, 10485760, "$D0", 5},
12               {"log/error.log", error, 10485760, "$D0", 5},
13               {"log/console.log", info, 10485760, "$D0", 5}
14            ]}
15         ]}
16      ]}
17 ].
1 start(_StartType, _StartArgs) ->
 2     ok = application:start(compiler),
 3     ok = application:start(syntax_tools),
 4     ok = application:start(lager),
 5
 6     % start mochiweb module reloader
 7     reloader:start(),
 8
 9     elli:start_link([{callback, aloha_api}, {port, 3000}]),
10     aloha_sup:start_link().
1 dev_start(App) -> dev_start(App, temporary).
 2
 3 dev_start(App, Type) ->
 4     case application:start(App, Type) of
 5          {error, {not_started, DepApp}} ->
 6              dev_start(DepApp),
 7              dev_start(App, Type);
 8          ok -> ok;
 9          {error, {already_started, App}} -> ok
10     end.
1   lager:debug("alohan").
2   % prints alohanok
3
4   lager:warning("freak set").
5   % prints freak set ahead nok
6
7   lager:error("wipe outn").
8   % prints wipe outnok
how about

  Unit
testing?
eunit
no
stacktrace
the easy way
etest
1   -module(aloha_utils_test).
 2   -compile(export_all).
 3
 4   % Include etest's assertion macros.
 5   -include_lib("etest/include/etest.hrl").
 6
 7   before_suite() ->
 8       %start up applications
 9       ok.
10
11   before_test() ->
12       %load fixtures
13       ok.
14
15   test_hello() ->
16       ?assert_equal("kekahi mau pipi", aloha_utils:hello()).
17
18   after_test() ->
19       % tear down fixtures
20       ok.
21
22   after_suite() ->
23       % stop applications
24       ok.
$: ./deps/etest/bin/etest-runner
How about

  http
testing?
1   -module (aloha_api_test).
 2   -compile (export_all).
 3
 4   % etest macros
 5   -include_lib ("etest/include/etest.hrl").
 6   % etest_http macros
 7   -include_lib ("etest_http/include/etest_http.hrl").
 8
 9   before_suite() ->
10       application:start(aloha).
11
12   before_test() -> ok.
13
14   after_test() -> ok.
15
16   after_suite() ->
17       application:stop(aloha).
18
19   test_aloha() ->
20       Response = ?perform_get("http://localhost:3000/aloha"),
21       ?assert_status(200, Response),
22       ?assert_body("kekahi mau pipi", Response).
$: ./deps/etest/bin/etest-runner
how about

 auto
reload?
Mochiweb
reloader
1 start(_StartType, _StartArgs) ->
 2     ok = application:start(compiler),
 3     ok = application:start(syntax_tools),
 4     ok = application:start(lager),
 5
 6     % start mochiweb module reloader
 7     reloader:start(),
 8
 9     elli:start_link([{callback, aloha_api}, {port, 3000}]),
10     aloha_sup:start_link().
beware of
 NIFs
how about

  auto
compile?
guard
1 # # -*- encoding : utf-8 -*-
2
3 guard 'shell' do
4   watch(%r{^src/.+.erl$}) do |m|
5     puts `rebar compile`
6   end
7 end
how ab bout

deploying to
  heroku?
$: heroku create aloha-erl -s cedar
Heroku
buildpack
$: heroku config:add
BUILDPACK_URL=http://github.com/
heroku/heroku-buildpack-
erlang.git
procfile
web: erl -pa deps/*/ebin ebin
      -noshell
      -noinput
      -config priv/config/
      environments/development
      -s aloha_app
1 start(_StartType, _StartArgs) ->
 2     ok = application:start(compiler),
 3     ok = application:start(syntax_tools),
 4     ok = application:start(lager),
 5     reloader:start(),
 6
 7     {ok, DefaultPort} = application:get_env(aloha, worker_port),
 8     Port = get_port(DefaultPort),
 9
10     elli:start_link([{callback, aloha_api}, {port, Port}]),
11     aloha_sup:start_link().
12
13 get_port(Default) ->
14     Key = "PORT",
15     case os:getenv(Key) of
16          false -> Default;
17          Val -> list_to_integer(Val)
18     end.
foreman
$: foreman start
deploy
$: git push heroku master
-----> Heroku receiving push
-----> Fetching custom git buildpack... done
-----> Erlang app detected
-----> Installing Rebar from buildpack
-----> Building with Rebar
   ==> build_1us6u0b7p5agc (get-deps)
   Pulling etest from {git,"git://github.com/wooga/etest.git","HEAD"}
   ......
   Compiled src/aloha_api.erl
-----> Discovering process types
   Procfile declares types -> web
-----> Compiled slug size: 6.3MB
-----> Launching... done, v19
   http://aloha-erl.herokuapp.com deployed to Heroku

To git@heroku.com:aloha-erl.git
 4bf3f39..198ba04 master -> master
$:curl aloha-erl.herokuapp.com/
             aloha
aloha-erl.herokuapp.com
that’s it folks

mahalo
thanks to
• https://twitter.com/wooga
• https://twitter.com/knutin
• https://twitter.com/hukl
• https://twitter.com/hungryblank
• https://twitter.com/guillermoo
•https://twitter.com/klickmich
• Andreas hasselberg
• Johannes Huning
Resources
• https://github.com/phuesler/aloha_erl
• http://vimeo.com/45212367
• https://github.com/knutin/elli
• https://github.com/wooga/etest
• https://github.com/wooga/etest_http
• https://github.com/basho/lager
• https://github.com/mochi/mochiweb/blob/master/src/reloader.erl
• https://github.com/heroku/heroku-buildpack-erlang
media
• Erlang: The Movie - YouTube
• bomb: http://www.flickr.com/photos/7969902@N07/511234695/

More than syntax