AOP for Erlang.

Copyright © 2011 Alexei Krasnopolski

Version: 1.0.1

Introduced in: 2011-07-15

Authors: Alexei Krasnopolski (krasnop@bellsouth.net) [web site: http://crasnopolski.com].

References

See also: aop.

Introduction
The project is a try to implement AOP for Erlang platform. AOP approach has significant success in area of web application design and others for implementation a profiling, logging and debugging support, transaction management, security management, different kinds of interceptors and so on. Good example of successful AOP using is Spring framework.
Terminology

Before starting an explanation of using the tool, lets refresh memory about generic AOP terms follow Aspect Oriented Programming with Spring. Not all principal of AOP are implemented in the project so we will discuss only following ones. Definitions are slightly changed to fit Erlang reality.

Join point
Imagine that our program or application is a space of function definitions. Each function is a point or entry of execution and join point is an activation of the entry during execution process. Only function entry can be join point.
Pointcut
Regular expression that matches join point. A pointcut can match one or set of join points.
Advice
Function that is executed in defined join point. Advice is generally defined as module name and function name. Advice can be different types: it can run before execution of join point function, it can run after join point function (after return, after catch of an exception or after final block) or it can run around of target join poin function.
Target
Function, module or set of modules for these we want to apply the AOP tool.
Aspect
Aspect defines combination of advice and set of corresponded pointcuts. Advice is main element of AOP configuration and controls how and where apply the advice function to target source code.
Proxy
Function that replaces target function and implements advice calling.
Weaving
Process of matching aspects to target source code and generating proxies instead of target function.

Figure below illustrates the discussed terms.

Getting started
To start with the AOP tool you have to complete at least first step from two ones below:
  1. Install Erlang http://www.erlang.org/download.html.
  2. Install Eclipse IDE http://www.eclipse.org/ with Erlide plugin http://erlide.sourceforge.net/. Although this is optional, I am doing developing of the software using these wonderful tools and highly recommend ones.
Let's create very simple example project to look over main features of the project. This example demonstrates profiling/debugging with AOP. The example code already exists in erl.aop project folder:
erl.aop
  |
  |== ebin
  |== examples
  |     |== aspects
  |     |     |---- advices.erl
  |     |     |---- defs.adf
  |     |
  |     |== target
  |     |     |---- program.erl
  |     |
  |     |---------- run_program.erl
  |
  |== include
  |     |---------- aop.hrl
  |
  |== src
        |---------- aop.erl
        |---------- fun_proxy.erl
        |---------- weaver.erl

Before starting with example we need to prepare the project. First let's open Erlang console (eshell) and change current dir to the project ebin folder:
1> c:cd("/home/alexei/eclipse/data/workspace/erl.aop/ebin").
/home/alexei/eclipse/data/workspace/erl.aop/ebin
Next step is compiling source files from src folder:
2> c("../src/aop.erl").
{ok,aop}
3> c("../src/fun_proxy.erl").
{ok,fun_proxy}
4> c("../src/weaver.erl").
{ok,weaver}
Now we can go to accomplish a few following steps to make AOP works.
  1. Choose the modules and functions for these we want to apply advices. Suppose we have Erlang module with 2 functions - process_message/1 and calc_service/2 and one more run/0 that calls above function with different arguments (see folder examples under the base directory of the project):
    -module(program).
    
    -export([calc_service/2, process_message/1, run/0]).
    
    calc_service(A, B) -> A + B.
    
    process_message(M) -> string:to_upper(M).
    
    run() ->
    	calc_service(1, 0),
    	calc_service(2, 2),
    	process_message("Add 3 to 5"),
    	process_message("Message to process"),
    	ok
    .	
    
  2. Create advice module with 2 functions - before_advice/1 and after_advice/2:
    -module(advices).
    
    -export([before_advice/1, after_advice/2]).
    
    before_advice([M, F, Args]) ->
    	io:format(">>> before function ~p:~p~n arguments:~n", [M, F]),
    	[io:format(" ~p~n", [Arg])|| Arg <- Args]
    .
    
    after_advice([M, F, _Args], R) ->
    	io:format("<<< after function ~p:~p~n return:~p~n", [M, F, R])
    .
    
  3. Create configuration file that describes aspects we are going to apply to target source code:
    %% defs.adf
    [Aspect(
    	Advice(before, advices, before_advice),
    	[
    		Pointcut("program", "\\w*_service", "*", public)
    	]
    ),
    Aspect( 
    	Advice(after_return, advices, after_advice), 
    	[
    		Pointcut("program", "process\\w*", "1", public)
    	]
    )]
    .
    
  4. Compile source code with AOP weaving:
    2> aop:compile(["../examples/target"], ["../examples/aspects"]).
    Sources = ["../examples/program.erl"] 
    Config files  = ["../examples/aspects/defs.adf"]
    Module name = program is weaved.         Warnings = []
    ok
    
  5. Run target module to test AOP advices:
    3> program:run().
    >>> before function program:calc_service_@
     arguments:
     1
     0
    >>> before function program:calc_service_@
     arguments:
     2
     2
    <<< after function program:process_message_@
     return:"ADD 3 TO 5"
    <<< after function program:process_message_@
     return:"MESSAGE TO PROCESS"
    ok
    
How it works
We have a original source code as a target for AOP weaving and we have configuration file/files with set of aspects these define advices and advice's pointcuts. During compiling stage Erlang compiler is using parse transform compiler plugin that provides AOP weaving:

          ----------            --------------------------
         | defs.adf |----->----| weaver:parse_transform/2 |
          ----------            --------------------------
                                           |
                                           | --- parse_transform
                                           |
   ----------------------           -----------------           --------------------
  | original source code |---->----| ERLANG Compiler |---->----| AOP adviced source |
   ----------------------           -----------------           --------------------
Table below describes how weaver transforms original function to proxy function with embedded calling of advice functions. Target function transformation during weaving process
Target function
(original source code)
Target function after weaving
target_func(P1,P2) -> {body}.
target_func_@(P1,P2) -> {body}.
Advice type AOP proxy function after weaving
before
target_func(P1,P2) ->
  before_advice([M,F,[P1,P2]]),
  target_func_@(P1,P2).
after return
target_func(P1,P2) ->
  R = target_func_@(P1,P2),
  after_advice([M,F,[P1,P2]], R), 
  R.
after throw
target_func(P1,P2) ->
  try
    target_func_@(P1,P2)
  catch
    Ex:Rz -> after_advice([M,F,[P1,P2]],{Ex,Rz}),
  end.
after final
target_func(P1,P2) -> 
  try
    target_func_@(P1,P2)
  after
    after_advice([M,F,[P1,P2]]),
  end.
around
target_func(P1,P2) ->
  erlang:apply(advice_module, arround_advice, 
      [?MODULE, target_func_@, [P1,P2]]).

Advice functions have to obey some rules as explained below.

Advice function specifications
Advice type Function specification
before
@spec before_advice([M, F, Args]) -> any()
  M = atom() - target module name
  F = atom() - target function name
  Args = list() - list of arguments of target function
after return
@spec after_advice([M, F, Args], R) -> any()
  M = atom() - target module name
  F = atom() - target function name
  Args = list() - list of arguments of target function
  R = any() - return value of target function
after throw
@spec after_advice([M, F, Args],{Ex,Rz}) -> any()
  M = atom() - target module name
  F = atom() - target function name
  Args = list() - list of arguments of target function
  Ex = term() - exception that is caught during target function execution. 
  Rz = term() - reason of the exception
after final
@spec after_advice([M, F, Args]) -> any()
  M = atom() - target module name
  F = atom() - target function name
  Args = list() - list of arguments of target function
around
@spec around_advice(M, F, Args) -> R
  M = atom() - target module name
  F = atom() - target function name
  Args = list() - list of arguments of target function
  R = any() - return value of target function
The body of around_advice have to contain at least -
  erlang:apply(M, F, Args)
Around advice has to return a value that returns the call above.
  
Configuration file has extension *.adf (ADvice DeFinition). Content of the files is Erlang term - list of aspect tuples (#aspect{} record is defined in include/aop.hrl). Functions Aspect/2, Advice/2 and Pointcut/4 can be used inside adf file. Aspect configuration
Configuration element
(function)
Arguments
Aspect(Advice, Pointcut)
Advice - tuple that defines the advice; 
Pointcut - list that defines list of pointcuts for the advice.
Advice(Module, Function)
Module = atom() - name of advice module; 
Function = atom() - name of advice function.
Pointcut(Module, Function, Arity, Scope)
Module = string() - regular expression that matches module names of desired joint points,
    ex.: "connection" - defines joint point module 'connection',
         "\\w*_db" - defines all joint point modules with suffix '_db'; 
Function = string() - regular expression that matches function names of desired joint points;
    ex.: "calc" - defines joint point function 'calc',
         "\\w*_service" - defines all joint point functions with suffix '_service'; 
Arity = string() | integer() - expression that matches arity of joint point functions,
    ex.: 1 - defines arity of joint point function equals 1,
         * - defines any arity,
         "2-4" - defines arity >= 2 and <= 4.
Scope = public | local | global - match for exported function (public), private (local) function or both (global).
         

Generated by EDoc, Aug 3 2012, 20:09:54.