Can anyone please explain the difference between Aspects, concerns and cross-cutting concerns in Spring AOP with example? I have gone through a lot of tutorial sites but I didn't get any good explanation.
Don't worry, it's pretty simple, and your question, once answered, unlocks everything you need to know about AOP, so it's a great question! :)
TL/DR; In AOP, a concern cannot be distributed by an aspect, and a cross-cutting concern can, and that's all an aspect does in the first place:
"Aspects let you distribute cross-cutting concerns from a single place in code"!
The reason AOP was formulated is because people realized that in programming, there is a special type of concern called a "cross-cutting concern", and it's really hard to do these things in a single place. So they came up with "aspects", to encapsulate them and think about the problem differently. So now, with AOP (which are just aspects really), you can now do these slippery "cross-cutting concerns" in a single place too, just like you can normal concerns. That's the fundamental reasoning and purpose for AOP, and nothing else. That's all it is!
Here is a more detailed breakdown between the two types of concerns...
Concerns (aka "core-concerns")
- Fundamental to the purpose of your software. i.e. the core business logic of what you're doing.
- Can always be optimized to occur in a single place in code. This is always true in AOP, so if you fundamentally cannot get it to occur once, you're either not actually dealing with a "concern", or you just haven't thought through the problem correctly.
- Super easy to optimize. Often can be totally or partially abstracted or computed ahead of time, i.e. precompiled or precomputed. For example, you can pre-compute look up tables to complex formulas if your input ranges are known, etc. This is an indicator that you're working with a concern and not a cross-cutting concern.
- Cross-cutting concerns should never call concerns. For example a logger should never try to compute tax in a transaction.
- Cannot be encapsulated by an aspect (this is absolutely fundamental to AOP).
Cross-cutting Concerns
- Non-fundamental, i.e. scrubbing, reformatting, auth, logging, etc. (of course, you can say logging is fundamental, but unless you're writting a logger, you're not thinking in terms of AOP).
- Cannot be optimized to occur in a single place. i.e. logging, while it can be called form a single function, that function must be used multiple times in your code. Therefore, "logging" is a "cross-cutting concern" and not a "concern" to most software.
- Often cannot be abstracted, optimized or computed ahead of time; must always occur at runtime/just-in-time. For example, the act of logging itself must fundamentally occur at runtime because of what is happening; you cannot pre-compute logging of something that has not yet occurred.
- Concerns should be the only way to invoke cross-cutting concerns. For example your main API endpoint can call your logger, but a logger should not call your main endpoint.
- Can be encapsulated by an aspect (this is absolutely fundamental to AOP).
- Can be called through "advice" (executed at "join points"; well-defined points in the program execution), introductions (methods or fields to classes), and inter-type declarations (altering the program's type system).
Detailed example
If you wrote an API in AOP that took and image and returned vectors representing it for downstream processing in your platform, core concerns would be:
- The model used.
- The vector shape (8 bit, 32 bit, int or float, etc).
- The embedding size (how many vectors, i.e. 192, 385, 1024, etc.).
- The image format required.
- The actual application of the transformer and utility functions/libraries (i.e. within the model in #1 above).
- The connectivity from input to output, i.e. all the glue/boilerplate code.
Most of these concerns can be injected into your application through a configuration object (except for #5 and #6). It's totally ripe for optimization. Let's reduce this thing to maybe 20 lines of code if we can!
In contrast, cross-cutting concerns are something that can only be applied in multiple places. Oh crap... we have to do logging, we have to format the input, we have to format the output... and shoot, we have to do these things in multiple places... that means we have to repeat ourselves... write the same line more than once... This is going to get Ugly... Aha! This is where AOP comes in!
Aspects
Your question is so wonderful because AOP doesn't force you to do things right; it just lets you. Most people miss the key point of AOP; Do the things you can't optimize into a single place, into a single place! In fact most AOP code I read is poor. Probably 9 out of 10 projects in AOP are just fundamentally missing the point. Most people I teach AOP don't get it. Your question really stabs at it's heart! Lets now cover aspects...
Aspects let you deeply cut into the SOC principle (Separation of Concerns) by instead of calling your "cross-cutting" concerns, like repeted function calls to logging and formatting in the same way you would call your regular concerns, you instead abstract them into aspects.
There are various ways to do this, mainly 3, but whatever way people dream up, the ways themselves are referred to as "advice".
The main 3 types of advice are:
- "join-points" - well-defined points in your program, such as function names.
- "introductions" - methods or fields to classes.
- "inter-type declarations" altering the program's type system.
Example Code
Let's define a service...
package com.example.demo.service;
public interface UserService {
void createUser(String name);
}
// Note: No AOP yet! :)
Let's implement the service...
package com.example.demo.service.impl;
import com.example.demo.service.UserService;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
@Override
public void createUser(String name) {
// Core business logic to create a user goes here...
System.out.println("Creating user: " + name);
}
}
// Note: Still no AOP yet! :)
Now lets create an aspect for logging...
package com.example.demo.aspect;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LoggingAspect {
// Here is our aspect... This is all called "advice"...
// Here we define a "pointcut", which in this case is just saying whenever "UserService.createUser" is executed...
@Pointcut("execution(* com.example.demo.service.UserService.createUser(..))")
public void createUserPointcut() {}
// Here we're saying do something before that pointcut...
@Before("createUserPointcut()")
public void logBeforeCreateUser(JoinPoint joinPoint) {
System.out.println("Before creating user: " + joinPoint.getSignature().getName());
}
// Here we're saying do something after that pointcut...
@AfterReturning("createUserPointcut()")
public void logAfterReturningCreateUser(JoinPoint joinPoint) {
System.out.println("After successfully creating user: " + joinPoint.getSignature().getName());
}
}
For completeness, you have to enable Auto-Proxy support (nothing of concern here, just particular to AspectJ)...
package com.example.demo.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
// This class can be empty if you're just enabling AspectJ auto-proxy support
}
Isn't this just a hook?
Yes it is! But it's a hook with extra steps, and those steps yield something useful. In fact AOP can also be thought of as proxies or, at least at some level, middleware (although middleware is considered an antipattern for good reason now).
Lifecycle hooks are similar to AOP in that, in the framework you're using that includes hooks, if they thought of hooks deeply, you can often shift most of your cross-cutting concerns into hooks! However, AOP implementations, like AspectJ, provide these more fundamental hooks in your code, so you can create any lifecycle hook you want.
Do I have to use AspectJ / Spring?
Heck no! In fact, the whole point of AOP is to reduce lines of code! So, AspectJ/Spring, and Java for that matter, are actually poor implementations of AOP, they're just the most known.
"We have 1.7 million lines of code!" "Sure, but in AOP it would be 30k."
There is an AOP implementation for almost every language. Check out Aspect.js for JavaScript, or search for your favorite language and get optimizing!
Is Aspect Oriented Programming the best way to program now days?
Yes and no. Fundamentally, programming is all about configuration, logic, and branching, and then encoding those things. No matter what language you use, you're just doing those 3 things, and the 4th thing, encoding, is where AOP tries to improve our lives.
AOP just helps you separate your cross-cutting concerns into aspects.
However, You don't actually need to implement this in code (encoding). You can do it in your config more effecently, and bake it into your platform. This is actually how DNA works; competent layers of configuration, instead of actually encoding logic.
That is one step above AOP; it's perhaps AOA (Aspect Oriented Architecture) or "Competent Substrates", and probably even easier to build than AOP code, and doesn't require you learn a new way of coding.
Since you've started down this rabbithole of optimization and abstraction... why not take AOP a step further and try to address SOC at a deeper level... at your platform level, at the settings level? What would things look like if you incorporated aspects, i.e. advice, into your configuration using layered settings?
Platform-level AOP, or perhaps AOA/Competent substrates, is a much nicer way to implement AOP, because SOC occurs in settings, which gives you a nice clean implementation layer, that is, your business logic doesn't need to implement AOP in the first place; it's been done ahead of time.
What this results in is many encoding tasks that traditionally require logic, simply don't even need to exist.
Since this is the route nature takes, and all known life follows it (through the use of codons and DNA/RNA to simply encode layers that are implemented in different orders), this is perhaps the best way (but we may even find something better some day!).
This movement I'm talking about is very new and is called "configuration as code" and similar. Some developers, in 2024, are referring to this as "competent substrates", including Michael Leven, a prolific engineer who is applying software to life. You can watch his Lex Friedman podcast where he talks about this in detail here: https://www.youtube.com/watch?v=p3lsYlod5OU
- Aspects: These are modular units of cross-cutting concerns. An aspect is a class that encapsulates behaviors affecting multiple classes or methods, like logging, security, or transaction management.
- Concerns: These refer to specific functionalities or behaviors in your application, like logging, data validation, or security. They can be core concerns (like business logic) or cross-cutting concerns.
- Cross-Cutting Concerns: These are concerns that affect multiple parts of an application and are usually unrelated to the core business logic. Examples include logging, error handling, and security. Aspects are used to modularize these cross-cutting concerns.
© 2022 - 2024 — McMap. All rights reserved.