Pro Yearly is on sale from $80 to $50! »

From Ruby to Erlang: An Experience Report

From Ruby to Erlang: An Experience Report

Chicago Erlang Factory Lite 2013

3e09fee7b359be847ed5fa48f524a3d3?s=128

Christopher Meiklejohn

October 04, 2013
Tweet

Transcript

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

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

    October 4, 13
  3. Friday, October 4, 13

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

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

  6. Friday, October 4, 13

  7. Friday, October 4, 13

  8. Friday, October 4, 13

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

  10. Friday, October 4, 13

  11. Friday, October 4, 13

  12. The Story Friday, October 4, 13

  13. Friday, October 4, 13

  14. The Motivation Friday, October 4, 13

  15. The Application Story Friday, October 4, 13

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

  17. S Friday, October 4, 13

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

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

  20. Building The Application Friday, October 4, 13

  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
  22. gen_server: State Coordination Friday, October 4, 13

  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
  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
  25. Webmachine: REST API Friday, October 4, 13

  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
  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
  28. Webmachine: Visual Debugger Friday, October 4, 13

  29. Friday, October 4, 13

  30. Asset Management Friday, October 4, 13

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

  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
  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
  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
  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
  36. Generating assets. Asset Management Friday, October 4, 13

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

  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
  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
  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
  41. CSRF Protection Friday, October 4, 13

  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
  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
  44. The Testing Story Friday, October 4, 13

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

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

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

  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
  49. The Dialyzer Friday, October 4, 13

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

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

  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
  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
  54. QuickCheck / PropEr Friday, October 4, 13

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

  56. prop_reverse() -> ?FORALL(X, %% Binding list(int()), %% Generator %% Property

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

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

    October 4, 13
  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
  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
  62. The Deployment Story Friday, October 4, 13

  63. The reltool Utility Friday, October 4, 13

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

    4, 13
  65. The node_package Utility Friday, October 4, 13

  66. Fedora, Debian, BSD, OS X, SmartOS, Solaris The node_package Utility

    Friday, October 4, 13
  67. The HiPE Application Friday, October 4, 13

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

  69. The Heroku Buildpack Friday, October 4, 13

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

    13
  71. The DSU Story Friday, October 4, 13

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

    4, 13
  73. The Conclusion Friday, October 4, 13

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

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

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

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