Upgrade to Pro — share decks privately, control downloads, hide ads and more …

From Ruby to Erlang: An Experience Report

From Ruby to Erlang: An Experience Report

Chicago Erlang Factory Lite 2013

Christopher Meiklejohn

October 04, 2013
Tweet

More Decks by Christopher Meiklejohn

Other Decks in Programming

Transcript

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

    View Slide

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

    View Slide

  3. Friday, October 4, 13

    View Slide

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

    View Slide

  5. What do you do?
    Friday, October 4, 13

    View Slide

  6. Friday, October 4, 13

    View Slide

  7. Friday, October 4, 13

    View Slide

  8. Friday, October 4, 13

    View Slide

  9. What did you do?
    Friday, October 4, 13

    View Slide

  10. Friday, October 4, 13

    View Slide

  11. Friday, October 4, 13

    View Slide

  12. The Story
    Friday, October 4, 13

    View Slide

  13. Friday, October 4, 13

    View Slide

  14. The Motivation
    Friday, October 4, 13

    View Slide

  15. The Application Story
    Friday, October 4, 13

    View Slide

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

    View Slide

  17. S
    Friday, October 4, 13

    View Slide

  18. S R
    R
    R
    Friday, October 4, 13

    View Slide

  19. S R
    R
    R
    T
    Friday, October 4, 13

    View Slide

  20. Building The Application
    Friday, October 4, 13

    View Slide

  21. {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

    View Slide

  22. gen_server: State Coordination
    Friday, October 4, 13

    View Slide

  23. 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

    View Slide

  24. %% @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

    View Slide

  25. Webmachine: REST API
    Friday, October 4, 13

    View Slide

  26. %% @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

    View Slide

  27. %% @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

    View Slide

  28. Webmachine: Visual Debugger
    Friday, October 4, 13

    View Slide

  29. Friday, October 4, 13

    View Slide

  30. Asset Management
    Friday, October 4, 13

    View Slide

  31. Serving assets.
    Asset Management
    Friday, October 4, 13

    View Slide

  32. %% @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

    View Slide

  33. %% @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

    View Slide

  34. %% @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

    View Slide

  35. %% @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

    View Slide

  36. Generating assets.
    Asset Management
    Friday, October 4, 13

    View Slide

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

    View Slide

  38. {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

    View Slide

  39. {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

    View Slide

  40. ➜ 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

    View Slide

  41. CSRF Protection
    Friday, October 4, 13

    View Slide

  42. %% @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

    View Slide

  43. %% @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

    View Slide

  44. The Testing Story
    Friday, October 4, 13

    View Slide

  45. EUnit: Unit Testing
    Friday, October 4, 13

    View Slide

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

    View Slide

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

    View Slide

  48. -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

    View Slide

  49. The Dialyzer
    Friday, October 4, 13

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  53. 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

    View Slide

  54. QuickCheck / PropEr
    Friday, October 4, 13

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  60. 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

    View Slide

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

    View Slide

  62. The Deployment Story
    Friday, October 4, 13

    View Slide

  63. The reltool Utility
    Friday, October 4, 13

    View Slide

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

    View Slide

  65. The node_package Utility
    Friday, October 4, 13

    View Slide

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

    View Slide

  67. The HiPE Application
    Friday, October 4, 13

    View Slide

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

    View Slide

  69. The Heroku Buildpack
    Friday, October 4, 13

    View Slide

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

    View Slide

  71. The DSU Story
    Friday, October 4, 13

    View Slide

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

    View Slide

  73. The Conclusion
    Friday, October 4, 13

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide