Object-Oriented Programming (OOP) manages code by grouping it into independent modules known as objects, emphasizing the crucial principle of Separation of Concerns. This means each object should focus on its specific responsibilities. However, real-world applications often feature functionalities that are common across multiple objects or modules, such as logging, security, transaction management, and performance monitoring. These functionalities are called Cross-cutting Concerns.
Challenge of Cross-Cutting Concerns
Scattering
These cross-cutting concerns, when handled solely with OOP, create two major problems. The first problem is Scattering, which is when code for a specific functionality is spread across multiple places through copying and pasting. For instance, imagine adding user permission checks and logging code to every function. The same logging and permission checking code would repeatedly appear within each method.
Tangling
The other issue is Tangling. This refers to the phenomenon where business logic and cross-cutting concerns become tangled and complex within a single module or function.
The primary purpose of the get_book_info function is to find book information, but logging, permission checks, and performance measurement code are all entangled within it. This reduces the method's readability and makes it difficult to discern the core business logic. It's like a messy desk; while a familiar person might quickly know where everything is, an unfamiliar one will struggle to find what they're looking for.The primary purpose of the get_book_info
function is to find book information, but logging, permission checks, and performance measurement code are all entangled within it. This reduces the method's readability and makes it difficult to discern the business logic. It's like a messy desk. You will struggle to find what you're looking for.
Aspect-Oriented Programming
Aspect-Oriented Programming (AOP) aims to solve the problem of separating business logic from cross-cutting concerns. AOP was first proposed in the late 90s by Gregor Kiczales and his team at Xerox PARC. Kiczales referred to code that could be modularized using existing paradigms, such asprocedures and objects, as components. However, he observed that certain features cannot be adequately modularized using those paradigms alone. He defined these functionalities as cross-cutting concerns, and argued that they could be cleanly designed by separating them into independent implementations called aspects.
Key Concepts of Aspect-Oriented Programming
AOP works by separating cross-cutting concerns from business logic, encapsulating them into Aspects. These Aspects then get inserted into the business logic as needed, without altering the business logic code itself. This approach is called non-invasive, meaning you can add or change auxiliary functionalities without directly modifying the original code. For example, you can encapsulate all logging-related logic within a LoggingAspect.
Join Point
A Join Point is a specific point in the program's execution flow where an Aspect's functionality can be inserted. This can include method calls, object creation, field access, or even exception occurrences. The process of actually applying and executing an Aspect at a Join Point is called Weaving.
Weaving
Kiczales believed that Weaving could happen at both compile-time and runtime. However, most modern AOP libraries provide only runtime Weaving, using proxy objects. Spring AOP is a prime example of this. This method involves creating a proxy object that intercepts calls to the target object, rather than directly modifying the existing implementation during execution.
Advice
Generally, Advice is categorized into three types based on its execution timing: before advice, which executes before the target; after advice, which executes after the target; and around advice, which wraps the Join Point. Some AOP frameworks, such as AspectJ, also provide more refined versions of After Advice, such as after returning advice, which executes only when the function completes normally, and after throwing advice, which executes when an exception occurs.
Pointcut
While Advice executes at a Join Point, it doesn't necessarily mean it runs at every Join Point. You can define a Pointcut to execute Advice only at specific, matching Join Points. A Pointcut is a conditional expression that specifies a subset of Join Points, allowing you to control where an Aspect intervenes. For example, a Pointcut like execution(* get_*(*))
would dictate that the Advice only runs when a method starting with get_
is executed. This method allows you to maintain a loose coupling between business logic and cross-cutting concerns, while also declaratively controlling the scope of the cross-cutting concern's application. This helps reduce the system's structural complexity and improves the reusability and maintainability of Aspects.
예제
It's common to use Aspects with the Spring framework, but for this article, I've used Python's aspectlib to create the examples. I chose aspectlib because it's simpler than Spring, which I believe will help illustrate AOP's core ideas more intuitively.
Example 1: Logging
The most common use case for AOP is logging. Logging often needs to behave differently between development and production environments. For instance, in a development setting, you typically log all requests and responses in detail for debugging, while in production, you usually only log errors to avoid performance degradation. If you embed these logging codes directly within your business logic, you'll face the hassle of modifying the implementation every time the logging policy changes. This leads to unnecessary code modification, increasing the chance of human errors and making maintenance difficult. By separating logging into a dedicated Aspect, you can ensure that business logic and logging logic remain independent, allowing flexible configuration changes to suit the operational environment. This structure also significantly boosts code readability and reusability.
Example 2: Authorization
The second example is authorization. Most modern services are built for multiple users. Therefore, managing user permissions for accessing resources is important. However, authorization isn't strictly business logic; it's a logic applied across various business logics - in other words, a cross-cutting concern. AOP can also be a solution in such cases. By separating authorization as Aspects, you can insert the necessary validations without modifying the core logic. Furthermore, if permission policies change, you can create a structure that applies those changes consistently across the entire system.
Example 3: Performance Measurement
The Aspect used in this example is an around advice that measures the execution time of the function. Auxiliary functionalities like performance measurement are cross-cutting concerns that aren't directly related to an application's business logic. AOP allows you to manage these cross-cutting concerns by separating them independently from the business logic implementation. Besides performance measurement, around advice is highly effective when implementing various auxiliary jobs, such as caching or transaction management, without intruding on the business logic.
Example 4: Error Handling
This example features a type of after throwing advice, which executes when an exception occurs. In service development, ensuring service stability is important. To maintain service stability, you need to know what kinds of errors are occurred. However, if you write exception handling code individually within each business logics, all functions become intertwined with the exception handling code, significantly reducing readability. This type of task is also prone to repetition in the same manner across all functions, leading to much code duplication and the hassle of modifying every single function if policies change. In such cases, AOP can be an effective solution. By separating the exception reporting logic into a dedicated Aspect, you can insert the desired behavior only when an exception occurs, without affecting the core logic at all. This allows you to maintain consistent exception handling policies and easily change reporting methods.
Conclusion
Advantages of Aspect-Oriented Programming
As these examples shows, AOP is a paradigm that allows you to effectively separate and manage cross-cutting concerns in your code. By using AOP, you can encapsulate functionalities that repeatedly appear across multiple modules (like logging, authorization, performance measurement, and exception handling) as independent Aspects, separate from the business logic. This helps maintain the purity of your business logic, enhancing readability and reducing code duplication, which in turn minimizes the potential for human errors. Furthermore, by separating concerns, maintaining individual functionalities becomes easier, and changing specific features has minimal impact on the overall codebase.
Disadvantages of Aspect-Oriented Programming
While AOP offers many advantages, it hasn't become a mainstream paradigm compared to others. This is due to several drawbacks. The biggest disadvantage is that it can make it difficult to grasp the code's flow. Aspects are defined outside the business logic, and the management of code insertion happens in places unrelated to the business logic's declaration or call sites. This makes it challenging to quickly understand the side effects of a function call, complicating debugging and potentially leading to unintended behavior or performance degradation. Additionally, the criteria for distinguishing between business logic and cross-cutting concerns often rely on subjective decision. If an improper separation occurs, it could lead to complex dependencies between Aspects, or the Aspects themselves might become as complicated.
Does this mean AOP is obsolete?
No! Even though AOP isn't widely used as a mainstream paradigm, the approach of separating business logic from cross-cutting concerns remains valid and powerful. AOP should be viewed as a mindset, not just a tool. Even if you don't directly use an AOP framework, you can achieve similar effects by using design patterns and programming techniques like the Proxy pattern or a Higher-Order Function. This allows you to separate cross-cutting concerns without intruding on the business logic, gaining benefits similar to AOP.
This article is a translation of the article written in Korean. Please see this link to see the original post.
Comments
Post a Comment