There are many articles about AOP. Developers have discussed whether or not Python needs an AOP library.
The key goal that AOP strives to solve is writing crosscutting solutions in places where OOP can't. OOP solves problems where the solution can be inherited. AOP is transversal whilst OOP is vertical. Either way, the goals are the same: "DRY (Don't Repeat Yourself)" and "obey the SRP". This also is described by the 1:1 Principle, which states that one requirement has one and only one manifestation in the implemented code. This is a combination of the DRY and SRP principles.
Developers resort to copy-and-paste style coding when it comes to adding logging, caching, transactions, etc. This code which spans across multiple service classes is repetitive and represents a single strategy. Whenever there is a change the change must be repeated everywhere, and this is risky and costly.
In our example, we used Spring Python to code an interceptor and applied it using pointcuts. This allowed us to avoid repetitive code while still keeping things simple and nicely encapsulated. This has the following benefits.
WikiService
, we can check the IoC configuration to see if the CachingInterceptor
applies. There is only one place to check, meaning it is easy to check the pointcuts CachingInterceptor
. This demonstrates how the advice is cleanly decoupled from the pointcuts PerformanceInterceptor
, where we observed which methods are taking the longest and evaluate them for caching. This was relatively easy to add, because it required no interference with the other interceptors or their pointcuts. This suggests that future updates should be relatively easy to makeWith advice decoupled from pointcuts, it is easy to add new pointcuts and new advice, thereby updating our crosscutting behavior. And all of this can be done without impacting the core API we are enhancing.
We have shown that AOP supports loosely coupling solutions. We have also looked at how it is easy to easily apply advice with precision, offering high cohesion. These factors will help us by making it easy to respond to changes we know are coming.
The following diagram shows a high-level view of how the components are wired together.
Each interceptor has its code is cleanly wrapped by an advisor with its pointcuts, forming well defined aspects. Each aspect is also separated from the business logic of the WikiService
. With this separation of concerns, it is easy to make changes with minimal risk. It is also easy to add new advisors and change the order of execution.
Spring Python isn't the only means to code an AOP solution. There are other libraries available, such as Aspyct
and aspects.py
. Python's rich feature set also provides the ability to code in an AOP-like fashion, using things like meta-class programming and decorators.
It is important to note what makes Spring Python's AOP solution distinct. While I have tried to include some high-level comparison with Aspyct
and aspects.py
, the reader is encouraged to visit these projects and compare the features in more detail.
Difference |
Description |
---|---|
Advice applicable to instances of objects. |
Spring Python gives the developer the option of applying advice to individual instances of objects, without requiring all instances to adopt the behavior. This gives the developer maximum flexibility. The tradeoff is that if this behavior is desired for all instances, then the developer is responsible to apply it to all instances.
The ability to wrap functions around methods is offered by |
No metaclass programming. |
For certain problems, metaclass programming may be the easiest solution. For other problems, metaclass programming isn't what is needed. It is an alternative paradigm from standard OOP and procedural development practices. In OOP, classes are templates for creating objects. Metaclasses are templates for creating classes. This means that some crosscutting problems may easily fit metaclasses programming, while other problems do not. Spring Python's AOP solution doesn't require learning a complex concept. Instead, it is based on creating classes and using IoC to define pointcuts that apply the advice. |
Don't need access to source code. |
Metaclass programming requires access to the source code. So does applying decorators defined in an AOP library. Both of these options make it harder to apply advice to a 3rd party library, since developers are less likely to take on maintaining patches.
Considering how Spring Python leverages its IoC container to apply advice, it is easy to utilize classes from any library and apply independent advisors. |
No monkey patching. |
Monkey patching is where an object's functions are added, removed, or replaced at runtime. While some people consider monkey patching a valuable tool, it must be used with care. There is a certain level of associated risk with altering the class definitions of libraries, which may result in unpredictable bugs. Spring Python utilizes proxies to merge target objects with interceptors. This makes it easy to add and remove advice, while maintaining a nice separation between services and target APIs. Later on in this chapter, automated testing of aspects will be discussed. |
AOP carries its own risks. When some of the functionality (in our case, caching), is moved out of our core code and into an interceptor, it may not be apparent when the caching logic is active. We may not even be aware that there is caching in the system. But the same could be said about OOP practices, where commonly used functionality is moved to other classes up or down the inheritance hierarchy. All of these coding styles are better served by the right set of tools, developer training, documentation, automated test suites, and communication amongst the development team. Since AOP is relatively new compared to OOP, the tool sets aren't as mature and developers are not as familiar with the concepts.
But this doesn't mean AOP should be abandoned. Instead, it should be evaluated just like any other technology, language, and tool suite used by the team.
The following diagram—known as the Spring triangle—encompasses the key principles used by Spring Python:
Using Spring Python's IoC container we have been able to take a simple WikiService
object and layer on a CachingService
through AOP. By using Dependency Injection, we have kept WikiService
clear of any Spring Python dependencies. To read the details of this, we just look up the blue prints of our IoC container in WikiProductionAppConfig
. Spring also utilized Portable Service Abstractions, such as DatabaseTemplate
, to reduce the need to work with low level APIs. Later on in this book, we will revisit the Spring triangle to discuss this in more detail.