-
Notifications
You must be signed in to change notification settings - Fork 1
Home
ennotation library is a simple implementation of AOP (http://en.wikipedia.org/wiki/Aspect-oriented_programming) in Erlang.
ennotation allows you to create and use the simple function wrappers just like a specs in your code. The modules using annotations are transformed during the compilation process (parse_transform) and might be used without changing the API of the module.
Currently only before and after annotation types are supported.
The annotation is a simple macro that can be put before the function definition, e.g.:
?MY_ANNOTATION(args_to_my_annotation).
my_fun(Foo, Bar) ->
something.
Each annotation is a user-defined function with a given spec:
-type(BeforeAnnResult :: {ok, ModifiedArgs :: list(term())} | {stop, Result :: term()}).
-type(AfterAnnResult :: ModifiedResult :: term()).
-spec(annotation/4 :: (AnnotationArg :: term(),
TargetModule :: atom(),
TargetFunction :: atom(),
Arguments | Result :: list(term())) -> BeforeAnnResult | AfterAnnResult).
where:
AnnotationArg is a parameter put in the code to change the annotation behaviour
TargetModule, TargetFunction are the target function specification
Arguments is a list of arguments that are passed to the function
There are two kinds of annotations:
-
before annotations – are called before the actual function is invoked and might change the arguments that are passed to the function and also break the call flow and return the desired value to the callee. before annotation should return either
{ok, NewArgs}
in the case when the call chain should proceed and theArgs
should be replaced withNewArgs
list (length(Args) == length(NewArgs)
) or{stop, Result}
if the call chain should be broken and theResult
returned to the caller immediately. - after annotations – are called after evaluating the actual function. They have access to the result returned from the function and can modify it. The returning value might be any Erlang term and will be return to the caller instead of the actual function result.
User might annotate the function with more than one annotation: they will be called in order of their declaration in the code. In the case when one of the before annotations breaks the call chain, neither the actual function, nor the after annotations will be invoked.
In order to create a new annotation, module in which annotation is going to be defined must contain:
-include_lib("ennotation/include/ennotation.hrl").
attribute defining the ?BEFORE
and ?AFTER
macros and enabling parse transform for this module. During the compilation there is a new header file created for the given application, named “include/MODULE_NAME_annotations.hrl” where MODULE_NAME is a name of the module where annotations were defined. The new header file will contain the annotation definitions that can be used within the code.
before annotation is created by putting ?BEFORE
macro before the annotation function definiton.
For example, following annotations are defined in before.erl file:
?BEFORE.
log_before(info, Mod, Func, Args) ->
error_logger:info_msg("~p:~p(~p) has been called!~n", [Mod, Func, Args]),
{ok, Args};
log_before(warning, Mod, Func, Args) ->
error_logger:warning_msg("~p:~p(~p) has been called!~n", [Mod, Func, Args]),
{ok, Args};
log_before(error, Mod, Func, Args) ->
error_logger:error_msg("~p:~p(~p) has been called!~n", [Mod, Func, Args]),
{ok, Args}.
When compiling before.erl new file, include/before_annotations.hrl will be created with new macro defined there:
?LOG_BEFORE(AnnotationArg).
We can use it now in the production code:
-module(my_mod).
-include("before_annotations.hrl").
...
?LOG_BEFORE(info).
foobar(A, B, C) ->
foobar_body.
The actual code that is going to be executed when calling my_mod:foobar(1, 2, 3)
will be:
before:log_before(info, my_mod, foobar, [1, 2, 3]),
foobar(1, 2, 3).
If we would like to break the call chain, we should define a new annotation:
?BEFORE.
breaker(NewReturn, _, _, _) ->
{stop, NewReturn}.
Then, we can use ?BREAKER(NewValue).
before our functions:
?BREAKER({some, new, value}).
my_func() ->
erlang:error({will, never, happen}).
From now every time we call my_mod:my_func()
we will get {some, new, value}
as a result.
after annotation is created by putting ?AFTER
macro before the annotation function definiton.
For example, following annotations are defined in after.erl file:
?AFTER.
log_after(info, Mod, Func, Result) ->
error_logger:info_msg("~p:~p has returned ~p!~n", [Mod, Func, Result]),
{ok, Args};
log_after(warning, Mod, Func, Result) ->
error_logger:warning_msg("~p:~p has returned ~p!~n", [Mod, Func, Result]),
{ok, Args};
log_after(error, Mod, Func, Result) ->
error_logger:error_msg("~p:~p has returned ~p!~n", [Mod, Func, Result]),
{ok, Args}.
When compiling after.erl new file, include/after_annotations.hrl will be created with new macro definition inside:
?LOG_AFTER(AnnotationArg).
We can use it now in the production code:
-module(my_mod2).
-include("after_annotations.hrl").
...
?LOG_AFTER(error).
foobar(A, B, C) ->
foobar_body.
The actual code that is going to be executed when calling my_mod2:foobar(1, 2, 3)
will be equivalent to:
Result = foobar(1, 2, 3),
after:log_after(error, my_mod2, foobar, foobar_body).