mgechev / aspect.js

JavaScript library for aspect-oriented programming using modern syntax.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Proposal: Replace "MethodMetadata" with "BeforeMethodMetadata" & "AfterMethodMetadata"

matthewadams opened this issue · comments

The reuse of MethodMetadata in both before & after advice doesn't make sense. In particular, MethodMetadata given to after advice shouldn't contain the properties proceed or invoke. Further, changing the value of proceed has no effect, and calling invoke doesn't make sense in after advice, because the method being intercepted has already been called. Also, neither result nor exception make sense in before advice.

IMHO, there should be two classes:

export class MethodMetadata {
  public name: string;
  public args: any[];
  public context: any;
}

export class BeforeMethodMetadata extends MethodMetadata {
  public proceed: boolean;
  public invoke: (...args: any[]) => any;
}

export class AfterMethodMetadata extends MethodMetadata {
  public result: any;
  public exception: any;
}

AroundMethodMetadata should be considered for around advice.

Efforts to remain backward-compatible should be made where possible.

Discussion welcome on this topic.

This refactoring would bring a better semantics of the API. I like the proposal.

I spent some time working on this issue. Not sure if the change is worth it at the moment. In the test cases, in the after advice once in a while there's a test which verifies if we can invoke the target's method.

I agree it improves the semantic to some extent, not sure if this won't be with the cost of reduced flexibility. Let us hold off a little and see if anyone else requests this.

@mgechev "there's a test which verifies if we can invoke the target's method"

This doesn't make sense in after advice. To me (and AspectJ), after advice inherently means "after the target has been invoked", so it doesn't make sense to invoke it again. If you want that kind of functionality, you should be using around advice.

It's similar for before advice: the only way you should be able to prevent the target method's execution should be to throw, otherwise, you should be using around advice. Having said that, I'd revise my earlier recommendation for the different advice types to the following:

export class MethodMetadata {
  public name: string;
  public args: any[];
  public context: any;
}

export class BeforeMethodMetadata extends MethodMetadata {}

export class AfterMethodMetadata extends MethodMetadata {
  public result: any;
  public error: any;
}

export class AroundMethodMetadata extends MethodMetadata {
  public proceed: boolean;
  public invoke: (...args: any[]) => any;
  public complete: (...args: any[]) => any; // added in https://github.com/mgechev/aspect.js/commit/1c03a1296513e4514171833c7ba1709b3540978d
}

In fact, if we're fixing things for 1.0.0, I'd make the argument that AroundMethodMetadata should really only be

export class AroundMethodMetadata extends MethodMetadata {
  public proceed: () => any;
  public invoke: (...args: any[]) => any;
}

Invoking proceed conveniently allows the invocation to proceed with the original arguments, and the user must return its return value: calling return meta.method.proceed() is semantically equivalent to called return meta.method.invoke(...meta.method.args). Otherwise, the user should use invoke to invoke the target method with whatever arguments he wants to, and return whatever he wants to, which will probably be the return value of invoke.