Programming paradigms
In the modern world of IT development, there are quite a lot of different approaches to writing programs. For example, someone likes to present a program as a sequence of actions, and someone thinks that a program should be a multitude of objects that communicate with each other. The combination of these ideas and concepts form a kind of writing program, which is commonly called
the programming paradigm .
Each paradigm has its own characteristics, however, the main factor distinguishing them is the concept of the basic unit of the program. Here are the most popular ones:
- instruction (imperative programming, FORTRAN / C / PHP),
- function (functional programming, Haskell / Lisp / F # / Scala),
- prototype (prototype programming, javascript),
- object (object-oriented programming, C ++ / Java),
- fact (logic programming, PROLOG).
It is worth noting that in the general case, the programming language definitely does not define the paradigm: you can write both imperative and object-oriented programs in the same PHP.
In this article I want to talk about a relatively young, but extremely, in my opinion, useful programming paradigm -
aspect-oriented programming .
')
Basics of AOP
Consider some spherical service in vacuum (for example, a web service) that implements the following method:
public BookDTO getBook(Integer bookId) {
BookDTO book = bookDAO.readBook(bookId);
return book;
}
The method is quite simple and obvious: reading information about a book by its identifier. But let's think about what's missing here? First of all, we should think about logging - without it, as you understand, the web-service is nowhere:
public BookDTO getBook(Integer bookId) {
LOG.debug( "Call method getBook with id " + bookId);
BookDTO book = bookDAO.readBook(bookId);
LOG.debug( "Book info is: " + book.toString());
return book;
}
Next, you need to implement exception handling (so that the services layer returns the corresponding exceptions, hiding the exceptions of the underlying layers):
public BookDTO getBook(Integer bookId) throws ServiceException {
LOG.debug( "Call method getBook with id " + bookId);
BookDTO book = null ;
try {
book = bookDAO.readBook(bookId);
} catch(SQLException e) {
throw new ServiceException(e);
}
LOG.debug( "Book info is: " + book.toString());
return book;
}
Also, do not forget about checking access rights:
public BookDTO getBook(Integer bookId) throws ServiceException, AuthException {
if (!SecurityContext.getUser().hasRight("GetBook"))
throw new AuthException("Permission Denied");
LOG.debug( "Call method getBook with id " + bookId);
BookDTO book = null ;
try {
book = bookDAO.readBook(bookId);
} catch (SQLException e) {
throw new ServiceException(e);
}
LOG.debug( "Book info is: " + book.toString());
return book;
}
In addition, it makes sense to cache the result of the work:
public BookDTO getBook(Integer bookId) throws ServiceException, AuthException {
if (!SecurityContext.getUser().hasRight( "GetBook" ))
throw new AuthException( "Permission Denied" );
LOG.debug( "Call method getBook with id " + bookId);
BookDTO book = null ;
String cacheKey = "getBook:" + bookId;
try {
if (cache.contains(cacheKey)) {
book = (BookDTO) cache.get(cacheKey);
} else {
book = bookDAO.readBook(bookId);
cache.put(cacheKey, book);
}
} catch (SQLException e) {
throw new ServiceException(e);
}
LOG.debug( "Book info is: " + book.toString());
return book;
}
You can continue to improve this method, but enough for a start. In the course of our refinements, we obtained a method 10 times (from 2 to 20 LOC) exceeding the original size. The most interesting thing is that the volume of business logic in it has not changed - it's all the same 1 line. The rest of the code implements some common service functionality of the application: logging, error handling, access control, caching, and so on.
In principle, the intertwining of business logic with official functionality is not as bad as your application is small. However, the more complex the program becomes, the more carefully it should be approached to its architecture in general and the allocation of general functionality in particular. That is why, watching the evolution of programming languages, we first see the emergence of functions, then modules, then objects. However, practice shows that to distinguish some common functionality, the paradigms mentioned above are not enough. This functionality is called “end-to-end” or “scattered”, since its implementation is really scattered across different parts of the application. Examples of end-to-end functionality, as we have seen above, can be:
- logging
- transaction processing
- error processing,
- authorization and verification of rights,
- caching
- elements of contract programming .
The main task of aspect-oriented programming (AOP) is the modularization of end-to-end functionality, highlighting it into aspects . To do this, languages ​​that support the concept of AOP, implement the following tools to highlight end-to-end functionality:
- aspect is a module or class that implements end-to-end functionality. The aspect changes the behavior of the rest of the code by applying the tip at the connection points defined by a certain slice. The same aspect can be used to introduce functionality;
- advice (advice) - additional logic - the code that should be called from the connection point. Advice can be performed before, after, or instead of the connection point;
- join point - a point in the executable program (method call, object creation, variable access), where the advice should be applied;
- cut (pointcut) - a set of connection points. The slice determines whether a given join point matches a given tip;
- introduction - a change in the class structure and / or a change in the inheritance hierarchy to add aspect functionality to the foreign code;
- target (target) - the object to which the tips will be applied;
- interweaving (weaving) - linking objects with relevant aspects (possibly at the stage of compilation, loading or execution of a program).
Usage Example (AspectJ)
AspectJ is an aspect-oriented extension / framework for the Java language. At the moment this is probably the most popular and developing AOP engine.
Consider the implementation of the aspect of logging with it:
@Aspect
public class WebServiceLogger {
private final static Logger LOG =
Logger.getLogger(WebServiceLogger. class );
@Pointcut( "execution(* example.WebService.*(..))" )
public void webServiceMethod() { }
@Pointcut( "@annotation(example.Loggable)" )
public void loggableMethod() { }
@Around( "webServiceMethod() && loggableMethod()" )
public Object logWebServiceCall(ProceedingJoinPoint thisJoinPoint) {
String methodName = thisJoinPoint.getSignature().getName();
Object[] methodArgs = thisJoinPoint.getArgs();
LOG.debug( "Call method " + methodName + " with args " + methodArgs);
Object result = thisJoinPoint.proceed();
LOG.debug( "Method " + methodName + " returns " + result);
return result;
}
}
First of all, the aspect of service method logging is created - the WebServiceLogger class, marked with the
Aspect annotation. Next, two slices of the connection points are defined: webServiceMethod (call to a method belonging to the class WebService) and loggableMethod (call to a method marked with the @Loggable annotation). At the end, a tip is announced (the logWebServiceCall method), which is executed instead of (
Around annotation) junction points that satisfy the slice (“webServiceMethod () && loggableMethod ()”).
The board code retrieves information about the current method (connection point), logs the start of the method execution, directly calls the requested method, logs and returns the result of the work.
AspectJ has a fairly large amount of supported slices of connection points. The following are the main ones:
- execution (static * com.xyz .. *. * (..)) - execution of the code of any static method in the com.xyz package;
- call (void MyInterface. * (..)) - a call to any method that returns a void, MyInterface interface;
- initialization (MyClass || MyOtherClass) - initialization of the class MyClass or MyOtherClass;
- staticinitialization (MyClass + &&! MyClass) - static initialization of a class whose name begins with MyClass, but not MyClass itself;
- handler (ArrayOutOfBoundsException) - executing the exception handler ArrayOutOfBoundsException;
- get / set (static int MyClass.x) - read / write property x of the class MyClass;
- this / target (MyClass) - execution of a connection point corresponding to an object of type MyClass;
- args (Integer) - execution of a junction in which an Integer argument is available;
- if (thisJoinPoint.getKind (). equals ("call")) - matches all connection points where the given expression is true;
- within / withincode (MyClass) - coincides with all junction points encountered in the code of the specified class;
- cflow / cflowbelow (call (void MyClass.test ())) - coincides with all connection points encountered in the execution stream of the specified slice;
- @annotation (MyAnnotation) - the execution of a connection point, the purpose of which is marked with the annotation @MyAnnotation.
As for the tips, their number is much smaller, but they fully cover all the necessary set of situations:
- before - launching the board before executing the junction,
- after returning - starting the board after the normal execution of the connection point,
- after throwing - starting the board after throwing an exception during the execution of the junction,
- after - launching the board after any variant of the junction,
- around - starting the board instead of executing the junction point (executing the junction point can be called inside the council)
More information about AspectJ constructions can be found in the corresponding section [
1 ,
2 ] of
official documentation .
In order to use aspects of AspectJ, they will have to be compiled and “
stitched ” into the main classes using a special
AJC compiler.
The product is free. Distributed under Eclipse License.
Usage Example (PostSharp)
PostSharp is an aspect-oriented framework for the .NET platform. There are other AOP implementations for .NET, however, judging by
comparisons from the PostSharp site, it is he who takes the lead.
Consider how to use it to describe the aspect of exception handling. The first step is to create a class that extends the corresponding aspect:
public class ExceptionDialogAttribute : OnExceptionAspect
{
public override void OnException(MethodExecutionEventArgs eventArgs)
{
string message = eventArgs.Exception.Message;
Window window = Window.GetWindow((DependencyObject)eventArgs.Instance);
MessageBox.Show(window, message, "Exception" );
eventArgs.FlowBehavior = FlowBehavior.Continue;
}
}
Strictly speaking, aspects in PostSharp terminology are, as we can see, aspect and advice in AOP terminology.
In order to specify the cut of intersection points for this aspect, the following line should be added to the assembly settings file (AssemblyInfo.cs):
[assembly: ExceptionDialog ( AttributeTargetTypes= "Example.WorkflowService.*" ,
AttributeTargetMemberAttributes = AttributeTargetElements.Public )]
Or explicitly mark the methods you are interested in with the ExceptionDialog attribute:
[ExceptionDialog]
public BookDTO GetBook(Integer bookId)
That's all: now all exceptions thrown in the corresponding methods will be handled by the created aspect.
In view of the fact that PostSharp partially glues the concepts of advice and aspect, the latter it turns out quite a lot. Details can be found in the
documentation . The following are the main ones:
- OnMethodBoundary / OnMethodInvocation - a call to the method (start, end, exit, exit with an exception);
- OnFieldAccess - access to the property;
- OnException - exception handling;
- Composition - code injection;
PostSharp requires a compiler and a library to be connected to the project. Aspect insertion is based on post-processing of bytecode during application assembly.
Product paid. There is a Community Edition.
From theory to practice
And so, we have just seen how beautifully and effectively it is possible to solve the problem of “putting out” the end-to-end functionality in your application. However, this is all theory. In practice, everything, of course, is a little different :)
First of all, in both cases, for compiling and “weaving” aspects, you will have to use a special compiler and drag along with the project additional libraries. It seems that this is not a problem: the compiler is easy to download and integrates into the environment (for example, using maven, the task will be reduced only to adding the aspectj-maven-plugin plug-in), and many dependencies are common, at least for Java applications (solved by the same maven'a). However, the need to include in the project something that requires a separate compilation, but still does not have wide distribution, often scares off developers, despite all the potential advantages.
In this case, the Spring Framework [
1 ,
2 ] can be a solution. This framework has many advantages, however, within the framework of this article, we are interested in its AOP component. The Spring Framework implements limited
AOP functionality on pure Java (C #) without using third-party libraries by creating proxy objects (JDK Dynamic Proxy, CGLIB). In other words, in Spring AOP, you can use only “execute method” junction points. However, as practice shows, this restriction does not play a significant role, since for solving most problems, it is necessary to connect points of this type.
In addition, the Spring Framework supports application configuration using @AspectJ annotations, as well as integrating aspects compiled directly with AspectJ.
We use Spring AOP in our company. Considering the other merits of the Spring Framework, in my opinion, it is the most accessible and convenient platform for working with AOP, making a significant contribution to its popularization and development.
Summary
Summing up, I would like to highlight three main thoughts regarding AOP:
- The main goal of AOP is to remove the “common” (through) functionality “beyond the brackets” (modularization of the through functionality);
- For Java, AOP is available through the AspectJ project, for .NET through PostSharp;
- The most simple and proven implementation of AOP - Spring AOP.
Related Links