Slide 1

Slide 1 text

From Ruby to Erlang: An Unexpected Journey Christopher Meiklejohn Friday, October 4, 13

Slide 2

Slide 2 text

From Ruby to Erlang: An Experience Report Christopher Meiklejohn Friday, October 4, 13

Slide 3

Slide 3 text

Friday, October 4, 13

Slide 4

Slide 4 text

cmeiklejohn / @cmeik My Name is My Name Friday, October 4, 13

Slide 5

Slide 5 text

What do you do? Friday, October 4, 13

Slide 6

Slide 6 text

Friday, October 4, 13

Slide 7

Slide 7 text

Friday, October 4, 13

Slide 8

Slide 8 text

Friday, October 4, 13

Slide 9

Slide 9 text

What did you do? Friday, October 4, 13

Slide 10

Slide 10 text

Friday, October 4, 13

Slide 11

Slide 11 text

Friday, October 4, 13

Slide 12

Slide 12 text

The Story Friday, October 4, 13

Slide 13

Slide 13 text

Friday, October 4, 13

Slide 14

Slide 14 text

The Motivation Friday, October 4, 13

Slide 15

Slide 15 text

The Application Story Friday, October 4, 13

Slide 16

Slide 16 text

Application structure. The Application Story Friday, October 4, 13

Slide 17

Slide 17 text

S Friday, October 4, 13

Slide 18

Slide 18 text

S R R R Friday, October 4, 13

Slide 19

Slide 19 text

S R R R T Friday, October 4, 13

Slide 20

Slide 20 text

Building The Application Friday, October 4, 13

Slide 21

Slide 21 text

{erl_opts, [debug_info, warnings_as_errors, {parse_transform, lager_transform}]}. {edoc_opts, [{preprocess, true}]}. {deps, [ {erlydtl, "0.7.0", {git, "git://github.com/evanmiller/erlydtl.git", {tag, "6a9845f"}}}, {node_package, "1.3.4", {git, "git://github.com/basho/node_package", {tag, "1.3.4"}}}, {lager, ".*", {git, "git://github.com/basho/lager", {tag, "1.2.2"}}}, {webmachine, ".*", {git, "git://github.com/basho/webmachine.git", {tag, "1.10.0"}}}, {erlcloud, ".*", {git, "git://github.com/basho/erlcloud.git", {tag, "0.4.1p1"}}} ]}. riak_cs_control/rebar.con!g Friday, October 4, 13

Slide 22

Slide 22 text

gen_server: State Coordination Friday, October 4, 13

Slide 23

Slide 23 text

init([]) -> riak_cs_control_configuration:configure_s3_connection(), {ok, #state{}}. get_users() -> gen_server:call(?MODULE, get_users, infinity). get_user(KeyId) -> gen_server:call(?MODULE, {get_user, KeyId}, infinity). handle_call(get_users, _From, State) -> case handle_request({multipart_get, "users"}) of {ok, Response} -> Users = riak_cs_control_formatting:format_users(Response), {reply, {ok, Users}, State}; Error -> {reply, Error, State} end; handle_call({get_user, KeyId}, _From, State) -> case handle_request({get, "user/" ++ KeyId}) of {ok, Response} -> User = riak_cs_control_formatting:format_user(Response), {reply, {ok, User}, State}; Error -> {reply, Error, State} end; riak_cs_control/src/riak_cs_control_session.erl Friday, October 4, 13

Slide 24

Slide 24 text

%% @doc Format a user, by ensuring that all keys are atomized. format_user({struct, _} = Attributes) -> {struct, AttributeList} = atomize(Attributes), {struct, append_admin_status(AttributeList)}; format_user(Attributes) -> Attributes. %% @doc Given a proplist of attributes, add admin status. append_admin_status(Attributes) -> RiakCsAdminKey = riak_cs_control_configuration:cs_configuration( cs_admin_key), BinaryAdminKey = list_to_binary(RiakCsAdminKey), KeyId = proplists:get_value(key_id, Attributes), lists:append(Attributes, [{admin, KeyId =:= BinaryAdminKey}]). %% @doc Format an entire list of users. format_users(Users) -> [format_user(User) || User <- Users]. riak_cs_control/src/riak_cs_control_formatting.erl Friday, October 4, 13

Slide 25

Slide 25 text

Webmachine: REST API Friday, October 4, 13

Slide 26

Slide 26 text

%% @doc Initialize the resource. init([]) -> {ok, #context{users=undefined,user=undefined}}. %% @doc Provide respones in JSON only. content_types_provided(ReqData, Context) -> {[{"application/json", to_json}], ReqData, Context}. %% @doc Validate CSRF token. forbidden(ReqData, Context) -> {riak_cs_control_security:is_protected(ReqData, Context), ReqData, Context}. %% @doc Support retrieval and creation of users. allowed_methods(ReqData, Context) -> {['HEAD', 'GET', 'POST'], ReqData, Context}. %% @doc Return true if we were able to retrieve the user. resource_exists(ReqData, Context) -> case maybe_retrieve_users(Context) of {true, NewContext} -> {true, ReqData, NewContext}; {false, Context} -> {false, ReqData, Context} end. riak_cs_control/src/riak_cs_control_wm_users.erl Friday, October 4, 13

Slide 27

Slide 27 text

%% @doc Attempt to retrieve the users and store in the context if %% possible. maybe_retrieve_users(Context) -> case Context#context.users of undefined -> case riak_cs_control_session:get_users() of {ok, Response} -> {true, Context#context{users=Response}}; _ -> {false, Context} end; _Users -> {true, Context} end. %% @doc Return serialized users. to_json(ReqData, Context) -> case maybe_retrieve_users(Context) of {true, NewContext} -> Users = NewContext#context.users, Response = mochijson2:encode({struct, [{users, Users}]}), {Response, ReqData, NewContext}; {false, Context} -> Response = mochijson2:encode({struct, [{users, []}]}), {Response, ReqData, Context} end. riak_cs_control/src/riak_cs_control_wm_users.erl Friday, October 4, 13

Slide 28

Slide 28 text

Webmachine: Visual Debugger Friday, October 4, 13

Slide 29

Slide 29 text

Friday, October 4, 13

Slide 30

Slide 30 text

Asset Management Friday, October 4, 13

Slide 31

Slide 31 text

Serving assets. Asset Management Friday, October 4, 13

Slide 32

Slide 32 text

%% @doc Return a context which determines if we serve %% up the application template or a file resource. identify_resource(ReqData, #context{resource=undefined}=Context) -> case wrq:disp_path(ReqData) of "" -> {true, Context#context{resource=template, filename=undefined}}; _ -> Tokens = wrq:path_tokens(ReqData), Filename = normalize_filepath(Tokens), {true, Context#context{resource=filename, filename=Filename}} end; identify_resource(_ReqData, Context) -> {true, Context}. riak_cs_control/src/riak_cs_control_wm_asset.erl Friday, October 4, 13

Slide 33

Slide 33 text

%% @doc If the file exists, allow it through, otherwise assume true if %% they are asking for the application template. resource_exists(ReqData, Context) -> case identify_resource(ReqData, Context) of {true, NewContext=#context{resource=template}} -> {true, ReqData, NewContext}; {true, NewContext=#context{resource=filename, filename=Filename}} -> case filelib:is_regular(Filename) of true -> {true, ReqData, NewContext}; _ -> {false, ReqData, NewContext} end end. riak_cs_control/src/riak_cs_control_wm_asset.erl Friday, October 4, 13

Slide 34

Slide 34 text

%% @doc Return the proper content type of the file, or default to %% text/html. content_types_provided(ReqData, Context) -> case identify_resource(ReqData, Context) of {true, NewContext=#context{resource=filename, filename=Filename}} -> MimeType = webmachine_util:guess_mime(Filename), {[{MimeType, to_resource}], ReqData, NewContext}; {true, NewContext} -> {[{"text/html", to_resource}], ReqData, NewContext} end. riak_cs_control/src/riak_cs_control_wm_asset.erl Friday, October 4, 13

Slide 35

Slide 35 text

%% @doc Return the application template, or the contents of a file %% resource. to_resource(ReqData, Context) -> case identify_resource(ReqData, Context) of {true, NewContext=#context{resource=template}} -> Token = riak_cs_control_security:csrf_token( ReqData, Context), {ok, Content} = application_dtl:render( [{csrf_token, Token}]), {Content, wrq:set_resp_header("Set-Cookie", "csrf_token="++Token++ "; httponly", ReqData), NewContext}; {true, NewContext=#context{resource=filename, filename=Filename}} -> {ok, Source} = file:read_file(Filename), {Source, ReqData, NewContext} end. riak_cs_control/src/riak_cs_control_wm_asset.erl Friday, October 4, 13

Slide 36

Slide 36 text

Generating assets. Asset Management Friday, October 4, 13

Slide 37

Slide 37 text

{plugins, [rebar_js_handlebars_plugin, rebar_js_concatenator_plugin]}. riak_cs_control/rebar.con!g Friday, October 4, 13

Slide 38

Slide 38 text

{js_handlebars, [ {doc_root, "priv/www/js/templates"}, {out_dir, "priv/www/js"}, {target, "Ember.TEMPLATES"}, {compiler, "Ember.Handlebars.compile"}, {templates, [{"generated/templates.js", [ "application.hbs", "users.hbs", "users/button.hbs", "users/new.hbs", "users/item.hbs", "users/index.hbs"]}]} ]}. riak_cs_control/rebar.con!g Friday, October 4, 13

Slide 39

Slide 39 text

{js_concatenator, [ {doc_root, "priv/www/js/vendor"}, {out_dir, "priv/www/js"}, {concatenations, [{"generated/vendor.js", [ "jquery-1.7.2.js", "jquery-ui-1.8.16.custom.min.js", "spin-1.2.7.min.js", "handlebars.js", "ember-latest.js", "ember-data-latest.js", "minispade.js"], []}]} ]}. riak_cs_control/rebar.con!g Friday, October 4, 13

Slide 40

Slide 40 text

➜ riak_cs_control git:(master) rebar compile skip_deps=true ==> rel (compile) ==> riak_cs_control (compile) Compiled handlebars asset priv/www/js/generated/templates.js Friday, October 4, 13

Slide 41

Slide 41 text

CSRF Protection Friday, October 4, 13

Slide 42

Slide 42 text

%% @doc Validate CSRF token. forbidden(ReqData, Context) -> {riak_cs_control_security:is_protected(ReqData, Context), ReqData, Context}. riak_cs_control/src/riak_cs_control_wm_user.erl Friday, October 4, 13

Slide 43

Slide 43 text

%% @doc Get the CSRF token from the cookie. -spec get_csrf_token(reqdata(), context()) -> csrf_token(). get_csrf_token(ReqData, _Context) -> wrq:get_cookie_value("csrf_token", ReqData). %% @doc Ensure this request contains a valid csrf protection token. -spec is_valid_csrf_token(reqdata(), context()) -> boolean(). is_valid_csrf_token(ReqData, Context) -> HeaderToken = wrq:get_req_header("X-CSRF-Token", ReqData), CookieToken = get_csrf_token(ReqData, Context), HeaderToken /= undefined andalso HeaderToken == CookieToken. %% @doc Is this a protected method? -spec is_protected_method(reqdata()) -> boolean(). is_protected_method(ReqData) -> Method = wrq:method(ReqData), Method == 'POST' orelse Method == 'PUT'. %% @doc Is this a protected? -spec is_protected(reqdata(), context()) -> boolean(). is_protected(ReqData, Context) -> (is_null_origin(ReqData) or not is_valid_csrf_token(ReqData, Context)) and is_protected_method(ReqData). riak_cs_control/src/riak_cs_control_security.erl Friday, October 4, 13

Slide 44

Slide 44 text

The Testing Story Friday, October 4, 13

Slide 45

Slide 45 text

EUnit: Unit Testing Friday, October 4, 13

Slide 46

Slide 46 text

Similar to MiniTest; Test::Unit EUnit: Unit Testing Friday, October 4, 13

Slide 47

Slide 47 text

length_test() -> ?assert(length([1,2,3]) =:= 3). Friday, October 4, 13

Slide 48

Slide 48 text

-module(fib). -export([fib/1]). -include_lib("eunit/include/eunit.hrl"). fib(0) -> 1; fib(1) -> 1; fib(N) when N > 1 -> fib(N-1) + fib(N-2). fib_test_() -> [?_assert(fib(0) =:= 1), ?_assert(fib(1) =:= 1), ?_assert(fib(2) =:= 2), ?_assert(fib(3) =:= 3), ?_assert(fib(4) =:= 5), ?_assert(fib(5) =:= 8), ?_assertException(error, function_clause, fib(-1)), ?_assert(fib(31) =:= 2178309)]. Friday, October 4, 13

Slide 49

Slide 49 text

The Dialyzer Friday, October 4, 13

Slide 50

Slide 50 text

Discrepancy Analyzer for Erlang programs The Dialyzer Friday, October 4, 13

Slide 51

Slide 51 text

Based on success typings The Dialyzer Friday, October 4, 13

Slide 52

Slide 52 text

-module(test). -compile([export_all]). is_ok(Symbol) -> case Symbol of {ok, _} -> true; {error, _} -> false end. check() -> is_ok(ok). Friday, October 4, 13

Slide 53

Slide 53 text

Checking whether the PLT /Users/cmeiklejohn/.dialyzer_plt is up-to- date... yes Proceeding with analysis... test.erl:13: Function check/0 has no local return test.erl:14: The call test:is_ok('ok') will never return since it differs in the 1st argument from the success typing arguments: ({'error',_} | {'ok',_}) done in 0m0.60s -module(test). -compile([export_all]). is_ok(Symbol) -> case Symbol of {ok, _} -> true; {error, _} -> false end. check() -> is_ok(ok). Friday, October 4, 13

Slide 54

Slide 54 text

QuickCheck / PropEr Friday, October 4, 13

Slide 55

Slide 55 text

Property-based testing utilities QuickCheck / PropEr Friday, October 4, 13

Slide 56

Slide 56 text

prop_reverse() -> ?FORALL(X, %% Binding list(int()), %% Generator %% Property lists:reverse(lists:reverse(X)) == X ). Friday, October 4, 13

Slide 57

Slide 57 text

3> eqc:quickcheck(eqc1:prop_reverse()). ...................................................................... .............................. OK, passed 100 tests true Friday, October 4, 13

Slide 58

Slide 58 text

prop_delete() -> ?FORALL(Ls, non_empty(list(int())), ?FORALL(X, elements(Ls), not lists:member(X, lists:delete(X, Ls)))). Friday, October 4, 13

Slide 59

Slide 59 text

10> eqc:quickcheck(eqc1:prop_delete()). ....................................................Failed! After 53 tests. [-15,-12,1,1,9,17] 1 false Friday, October 4, 13

Slide 60

Slide 60 text

10> eqc:quickcheck(eqc1:prop_delete()). ....................................................Failed! After 53 tests. [-15,-12,1,1,9,17] 1 Shrinking...(3 times) [1,1] 1 false Friday, October 4, 13

Slide 61

Slide 61 text

prop_delete() -> ?FORALL(Ls, non_empty(list(int())), ?FORALL(X, elements(Ls), not lists:member(X, lists:delete(X, Ls)))). Friday, October 4, 13

Slide 62

Slide 62 text

The Deployment Story Friday, October 4, 13

Slide 63

Slide 63 text

The reltool Utility Friday, October 4, 13

Slide 64

Slide 64 text

Standalone Erlang release; bundled runtime The reltool Utility Friday, October 4, 13

Slide 65

Slide 65 text

The node_package Utility Friday, October 4, 13

Slide 66

Slide 66 text

Fedora, Debian, BSD, OS X, SmartOS, Solaris The node_package Utility Friday, October 4, 13

Slide 67

Slide 67 text

The HiPE Application Friday, October 4, 13

Slide 68

Slide 68 text

Native code compilation The HiPE Application Friday, October 4, 13

Slide 69

Slide 69 text

The Heroku Buildpack Friday, October 4, 13

Slide 70

Slide 70 text

Git-based; dependencies through rebar The Heroku Buildpack Friday, October 4, 13

Slide 71

Slide 71 text

The DSU Story Friday, October 4, 13

Slide 72

Slide 72 text

Hot-code loading; control upgrade point. The DSU Story Friday, October 4, 13

Slide 73

Slide 73 text

The Conclusion Friday, October 4, 13

Slide 74

Slide 74 text

Easy to reason about; very small application. The Conclusion Friday, October 4, 13

Slide 75

Slide 75 text

Lack of tooling; trivial solution to solve. The Conclusion Friday, October 4, 13

Slide 76

Slide 76 text

Try Erlang for your next project! The Conclusion Friday, October 4, 13

Slide 77

Slide 77 text

https://github.com/basho/riak_cs_control https://github.com/cmeiklejohn/webmachine-tutorial Thanks! Questions? Friday, October 4, 13