Data flow

Overview

There are many times when we want to have several actions respond to one HTTP request. Moreover, some of those actions will be the same for more than one function and you will have a requirement to proceed to the next step (fire next action) only after a successful return from the current one. To achieve that, a dataflow mechanism has been created.

The basic idea is to provide a tool for defining the flow of data from the beginning of the request to its end. For example, we can specify, that before calling user:create we want to check the permissions of the caller, validate input data, check for uniqueness, log the activity and only after every stage has completed successfully - proceed to the create function. It is also possible to have other functions execute after the function requested by the HTTP server (see the next paragraph for the syntax). In some cases we do not have to worry about arguments requested by the HTTP server mode – it is captured and the values passed by after request functions are up to the developer.

Control flow

To enable the mechanism of dataflow, the controller module, which we want to use dataflow within, must export dataflow/1 and error/2 functions.

dataflow/1

Function dataflow/1 is responsible for providing the list of functions inside its module, which will be sequentially called, one after another. Each of those functions must return either {ok, Args} or {error, Reason} tuple.

Specification:

dataflow(ControllerFun) -> {BeforeList, AfterList} | BeforeList

BeforeList = AfterList = [Function]
ControllerFun = Function = atom()

Args will be used as the arguments for the next function in the dataflow list or as the arguments for the proper function call.

The proper function will be called only when all the functions specified in dataflow succeed (and it will be called with arguments returned from the last function on the dataflow's list).

error/2

If something goes wrong and one of the functions returns an error, the calling sequence is stopped and the error(FunName, Reason) is called (FunName is the name of the function where the error occurred).

If one of the obligatory dataflow functions is not present (exported), the standard procedure is held: at first the validate/1 function is called, then the proper one is fired.

dataflow.jpg

Example

Let's assume we want to create the following scenario of removing a user from the system (users:remove/1 function - argument is an ID of the user):

  1. check if the user connects via https

  2. check if the user has the correct permissions
  3. validate the passed parameters (passed in URL, like: users/delete/3 to remove user with ID=3)

  4. log the activity
  5. proceed to the removal function

To achieve this users module must at least look like that:

   ...
   -export([dataflow/1, error/2]).
   ...
   dataflow(remove) -> [check_https, check_permissions,
                        validate_number, log].
   ...
   check_https(_, _) ->
     case wpart:fget("__https") of
       true -> {ok, ok};
       false -> {error, no_https}
     end.

   check_permissions(_, ok) ->
     %% check if user is logged in and has the good permissions
     case Result of
       not_logged_in              -> {error, not_logged_in};
       not_enough_permissions     -> {error, not_enough_permissions};
       _                          -> {ok, ok}
     end.

   validate_number(_, ok) ->
     case hd(lists:reverse(string:tokens(wpart:fget("__path"), [$/]))) of
       N when is_integer(N)  -> {ok, N};
       _                     -> {error, invalid_url};
     end.

   log(Action, Number) ->
     %% log activity to the file
     {ok, Number}.

   error(check_https, no_https) ->
     {redirect, "https://" ++ e_conf:host() ++ wpart:fget("__path")};
   error(check_permissions, not_logged_in) ->
     {redirect, "/login"};
   error(check_permissions, not_enough_permissions) ->
     {template, "errors/not_enough_permissions.html"};
   error(validate_number, invalid_url) ->
     {error, 404}.

   remove(Number) ->
     %% proceed with user removal
     {template, "users/list.html"}.

Dataflow (last edited 2011-03-20 10:04:12 by BruceFitzsimons)