-
Notifications
You must be signed in to change notification settings - Fork 24
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Feature]: Class Decorator support #34
Comments
Hi there. Thanks for the good suggestion. I didn't add the class decorator feature because human error can cause AOP to be applied unintentionally. There are options like excludeMethodNames, but it seems like it could be missed if the amount of code written is large or if you don't know the exact context. |
I totally agree with your concern. But there are cases where it is not possible appying AOP with method decorator. For example, @Injectable()
export class SomeRepository extends Repository<SomeEntity> {
constructor(private readonly dataSource: DataSource) {
super(SomeEntity, dataSource.createEntityManager());
}
// @SomeAopDecorator({
// // ...options(metadata value)
// })
// async find() {}
} When there is a class that extends another class from external library like above, there is no way to apply AOP to a method from parent class. We could apply AOP to the method by wrapping in another method as a workaround. @SomeAopDecorator({
// ...options(metadata value)
})
async wrapMethod() {
await this.find();
} But this could be annoying sometimes. I'm pretty sure you've already thought about cases like this, so any ideas would be appreciated. |
If you are using a common class, I believe you can eliminate duplicate code by using the decorator pattern or implementing a separate custom class in the middle layer of the common class.
class CacheRepository<T> implements Repository {
consturcotr(someRepository: SomeRepository)
@Cachable()
find(...) {
this.someRepository.find(...)
}
....
}
class CacheRepository<T> extends Repository {
@Cachable()
find(...) { // override
this.someRepository.find(...)
}
}
class SomeService {
constructor(private readonly repositry: CacheRepository)
...
} Additionally, if you need to cache your repository in bulk, as in the example code, you may want to consider using the caching settings provided by your ORM. |
Thank you for your suggestion! Apart from the code quality, I would like to think about the purpose of a library itself. The major reason why I think libraries should not force developers' code style or structure is because the developer who makes a library would never know the exact context or environment in which the users use. So, the more features the maker provides, the better the library would be as long as the features don't contaminate the primary purpose of the library. So my conclusion is, if this library code is only used internally, I wouldn't add the class decorator feature, but if it's a library for anyone, I think it's better adding the class decorator feature and more features if possible. My opinion may be wrong because I'm a developer with a short experience. So could you share your thoughts on this? Thanks! :) |
Thank you for your valuable feedback. I'm curious to know if other languages offer similar capabilities. Java has class decorators, but they operate at compile time. In AOP, this approach seems to have a high potential for side effects, and it appears that the actual need for it is not significant. |
I failed to understand "Java decorators operate at compile time" because as far as I know, Java annotations are not functions and in Java/Spring environment the proxy beans for AOP are created on initialization of spring container at runtime. I might have misunderstood your word, so could you provide a little more details including the difference between Java and JavaScript environment for applying AOP please? Of course the mechanism of applying AOP for this library and spring-aop would be different but I still don't see the reason why it has a high potential for side effects in JavaScript but not in Java. The only side effect that comes to my mind is applying AOP to the unexpected methods. Could you provide any other example/scenario of side effects? Sorry for my ignorance! |
I came up with an example. In circumstances where there are various AOP decorators to be applied in a certain order, class decorator could be problematic because AOP with method decorator will always be applied before class decorator. |
Sorry I am not good at English. as far as I understand it
I was wondering if there is a case where Annotation that applies to Class affects all methods at runtime :) |
I believe Lombok works with annotation processor which is totally different from AOP. And spring-aop does support the feature! |
I am not good at Java. |
This is a basic logging example with spring-aop.
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Logging {
}
@Slf4j
@Aspect
@Component
@Order(0)
public class LoggingAspect {
@Pointcut("@annotation(your.annotation.package.path.Logging)")
private void methodPointcut() {}
@Pointcut("@within(your.annotation.package.path.Logging)")
private void classPointcut() {}
@Around("methodPointcut() || classPointcut()")
public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
log.debug("before");
Object result = joinPoint.proceed();
log.debug("after");
return result;
}
}
// @Logging
@Service
public class SomeService {
// @Logging
public void some() {
// ...
}
} Spring-aop supports even more features such as applying AOP without annotation like below. (high flexibility to define targets) @Pointcut("execution(public * *(..))")
private void anyPublicOperationPointcut() {}
@Pointcut("execution(* your.service.package.path.SomeService.*(..))")
private void someServicePointcut() {} I'm not so sure if this level of flexibility is possible in JavaScript though. |
Thank you for the detailed example |
It took some time for discussion 🙏 |
hello. I'm not sure if nestjs-aop should be considered a library, I think a library should have a lifecycle that runs independently of the outside world, and nestjs-aop is more of a framework because it basically runs on top of nestjs' lifecycle. Also, providing this functionality out of convenience makes it hard to defend against inheritance if it happens. ex) library lifecycle const someLibrary = new SomeLibray(...)
const someLibrary = someLibrary.getBuilder().add().build() Also, I'm not sure if there's anything that should be enforced at the class level for cache or transactions, which are typically used for AOP - I think they both require different policies depending on what they do, and in the case of cache, which you asked about in your first question, I think you'd need to approach it in a different way. For something like logging, I think you might want to consider an interceptor or middleware. |
Oh, the code block about the cache that I mentioned in my first comment was just an example that I brought from the README of this project. I agree that this project could be considered more as a part of a framework. |
지나가는길에 한마디 거들자면,, |
Overview
It would be great if
Class Decorator
for AOP is supported like the code block above.What the class decorator does is basically wrap all the methods of the class.
Describe the solution you'd like
I'm currently using the code below.
It would be appreciated if you could point out any side effect that could occur by using the code above.
Thank you for this awesome library!
Additional context
The text was updated successfully, but these errors were encountered: