[erlang-questions] Feedback on webmachine route

Damien Krotkine <>
Fri Sep 25 18:03:46 CEST 2015


Hi,

I'm kinda new to Erlang, and I'm extending the ReST API of Riak, so that
a single request will make it fetch multiple keys and return their
values, concatenated.
It's spawning a process for each keys for fetching the values, then wait
to receive all the values to stich them together and return it to the
client.

I'm sure this code is horrible from the point of view of a seasoned
Erlang developer, and I welcome any feedback. Especially regarding speed
and robustness against faults.

Thanks,
dams

Link to code here: http://lpaste.net/6968618518325493760

Also pasted below:

-module(events_api).

%% webmachine resource exports
-export([
         init/1,
         content_types_provided/2,
         service_available/2,
         malformed_request/2,
         to_textplain/2
        ]).

-include_lib("webmachine/include/webmachine.hrl").

-record(ctx, { bucket = undefined, 
               key = undefined
             }).

init(_) ->
    {ok, #ctx{}}.

content_types_provided(ReqData, Context) ->
    {[{"plain/text", to_textplain}],
     ReqData, Context}.

malformed_request(ReqData, Ctx) ->
    {false, ReqData, Ctx}.

service_available(ReqData, Ctx) ->
    {true, ReqData, Ctx}.

to_textplain(ReqData, Ctx) ->
    Epoch    = wrq:get_qs_value("epoch", "n/a", ReqData),
    DC       = wrq:get_qs_value("dc", ReqData),
    Types    = wrq:get_qs_value("types", "n/a", ReqData),
    Personas = wrq:get_qs_value("personas", "n/a", ReqData),

    error_logger:info_msg("epoch:~p DC:~p types:~p personas:~p",[ Epoch,
    DC, Types, Personas ]),

    {ok, Client} = riak:local_client(),

    % fetch metadata, returns a list of data keys
    DataKeys = get_datakeys(list_to_binary(Epoch), list_to_binary(DC),
    Client),
    error_logger:info_msg("DataKeys: ~p ",[ DataKeys ]),
    % get the actual event blobs from the data keys
    Blobs = get_values(Client, DataKeys),

    error_logger:info_msg(" got ~p blobs",[ lists:flatlength(Blobs) ]),

    % join blobs together, and return it
    Body = binary_join(Blobs),
    {[Body], ReqData, Ctx}.

% 1442926965

get_datakeys(Epoch, DC, Client) ->
    Bucket = <<"epochs">>,
    Key = <<Epoch/binary, "-", DC/binary>>,
    Options = [ {r, 1}, {timeout, 1000}  ],
    error_logger:info_msg("fetching metadata bucket:~p key:~p ...",[
    Bucket, Key ]),
    { ok, RiakObject } = riak_client:get(Bucket, Key, Options, Client),
    Data = riak_object:get_value(RiakObject),
    Partials = binary:split(Data, <<"|">>, [ global ]),
    % data keys have the epoch prepended, let's do that
    lists:map( fun(Partial) -> <<Epoch/binary, ":", Partial/binary>>
    end, Partials).


% easy api
get_values(Client, DataKeys) ->
    get_values(Client, DataKeys, [], []).

% main code: spawn a child that fetches, accumulate child pid
get_values(Client, [DataKey | Tail], Children, []) ->
    Parent = self(),
    Child = spawn_link(
	      fun() -> 
		      Options = [ {r, 1}, {timeout, 1000}  ],
		      Bucket = <<"events">>,
		      { ok, RiakObject } = riak_client:get(Bucket,
		      DataKey, Options, Client),
		      Blob = riak_object:get_value(RiakObject),
		      Parent ! { self(), Blob
				}
	      end ),
    get_values(Client, Tail, [ Child | Children ], []).

% all keys fetches are spawn, wait and collect all the results
get_values(_Client, [], Children, Blobs) ->
    receive {Child, Blob} ->
	    case lists:member(Child, Children) of
		true -> get_values(undefined, [], lists:delete(Child,
		Children), [Blob| Blobs]);
		false -> erlang:error("Wrong signal: doesn't match
		protocol")
	    end
    end;

% all results collected, return the blobs.
get_values(_Client, [], [], Blobs) ->
    Blobs;



binary_join([]) ->
  <<>>;
binary_join([Part]) ->
  Part;
binary_join([Head|Tail]) ->
  lists:foldl(fun (Value, Acc) -> <<Acc/binary, Value/binary>> end,
  Head, Tail).


More information about the erlang-questions mailing list