Modular coding approaches offer many benefits. By writing code in modules, we can reduce the amount of code to write. We can reuse code between programs. Teams can divide programs more easily, with each team focusing on a smaller part of the entire code base. It’s easier to identify and fix errors. The list goes on. A potential downside of modular coding happens when concerns that affect multiple modules can’t be easily or cleanly separated from the rest of the code. These cross-cutting concerns can lead to code duplication (scattering), dependencies between systems (tangling), or both. Aspect-Oriented Programming (AOP) overcomes these challenges by enabling developers to address cross-cutting concerns using aspects (modules) and advice (additional behaviors that tell the code what to do) that isn’t central to the code’s business logic, without changing the code.
Logging is a common use case for Aspect-Oriented Programming, because logging is a cross-cutting concern. In this blog, we’ll show you how you can use AOP in Angular 2+ applications to achieve logging without interrupting the individual responsibilities of a component in a real-time use case.
How You Might Handle Logging in a Traditional Coding Approach
Let’s consider two business objects in a sample Angular 2+ application:
• EmployeeService, an object that serves employees
• DepartmentService, an object that serves departments
The data entities for each service are fetched from a JSON-based HTTP backend. (For this blog, we’re focused only on the application’s cross-cutting concerns, so we’ll ignore the presentation and integration layer components.)
The EmployeeService and DepartmentService objects accomplish these tasks:
• Build the HTTP URL to fetch data
• Perform the call to the built HTTP URL
• Map the response to the respective entities
To fetch the list of employees, the EmployeeService builds the respective HTTP URL and executes the call. Once the response is received, it is mapped to its own Employee entity from JSON.
.map((jsonResponse) => <Employee>jsonResponse.json());
In this scenario, the request HTTP URL is printed out in the console. (Note that we don’t recommend this approach in production, but it can guide us for this article.)
How the Traditional Coding Approach Creates Problems
In the example above, notice that logging happens inside the method. This violates the Single Responsibility Principle (SRP) we strive to follow. It also creates another problem. In real-time applications, it will be more important to track the request/response trace for a particular call. This might lead to having logging-related code scattered all over the application.
For these reasons, we need a way to generalize and isolate the logging functionality from the application. Aspect-Oriented Programming gives us a way to do this. Using AOP, we just need to decorate a class or a method with a specific attribute which needs logging functionality. By doing so, we can log the following information without any specific code.
• Class Name
• Method Name
How We Can Use Aspect-Oriented Programming for a Better Approach to Logging
Aspect-Oriented Programming provides developers a way to inject additional logic into an existing code base without changing the code. This removes the dependency between the additional logic and the actual business code base.
To do this for our logging example above, we reference the Aspect.js into our Angular 2+ application. This supports decorator-based syntax, and allows us to create aspect classes for each function, such as logging, caching, and more.
Returning to our sample application, now we can:
• Create a LoggingAspect, which will be consumed by the EmployeeService class. The LoggingAspect inspects the method execution and has the details about the inputs and outputs of a particular method.
• Access the method full name, as well as input arguments and output arguments of a particular executing method.
Let’s see what this looks like:
The @Wove() decorator in the example above has to be decorated over the classes for the *Aspect to access. We can see that when the Get() method in EmployeeService class is executed, the call first goes to the LoggingAspect and calls the invokeBeforeMethod() and logs the class name, method, and input arguments. Then the control goes back to the Get() method in EmployeeService class and continues the execution. Once the execution completes, the call goes back to the LoggingAspect, which calls the invokeAfterMethod() and logs the class name, method, and output arguments.
In this way, we infuse the logging code inside the EmployeeService class without disturbing the actual business logic, and the LoggingAspect and the other services are loosely coupled.
Depending on the use case, you can modify the aspect classes to suit your own context and needs. You can create many aspects in an application; you can move any logic that is generic to the entire application into an aspect. You can also extend the aspect code to handle exception scenarios. Aspect-Oriented Programming gives you a very flexible and elegant way to overcome the challenges of cross-cutting concerns and maintain your commitment to the SRP.