mgechev / aspect.js

JavaScript library for aspect-oriented programming using modern syntax.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Share `woveMetadata` between advices

SamVerschueren opened this issue · comments

Let me first explain my use case as to why I need this. Then I will explain the issue I have.

I have a service that processes a @ngrx/store Action. It creates a command and invokes it. I applied the same logic as in your scalable architecture blogpost. I want to implement an offline strategy and cache my actions first, invoke the command, and if the device is online, remove the action from the storage.

foo.service.ts

@Wove()
@Injectable()
export class FooService extends AsyncService {

    constructor(
        private _gateway: FooGateway,
    ) {
        super();
    }

    process(action: Action): Observable<any> {
        // Create command and invoke
    }
}

foo.service.aspect.ts

class AWSServiceAspect {

    @beforeMethod({
        classNamePattern: /^FooService$/,
        methodNamePattern: /^process$/
    })
    processBeforeMethod(meta: Metadata) {
        const key = new Date().toISOString();

        // Insert the action, being `meta.method.args[0]` into IndexedDB with `key`
    }

    @afterMethod({
        classNamePattern: /^FooService$/,
        methodNamePattern: /^process$/
    })
    processAfterMethod(meta: Metadata) {
        // After the invocation, I should grab the `key` and remove the record from IndexedDB
    }
}

So I store my Action in IndexedDB with the current timestamp as key. After the process method finishes, I have to know which key was used in order to remove it again. I though I could do it with the woveMetadata. Something like this:

    processBeforeMethod(meta: Metadata) {
        const key = new Date().toISOString();

        meta.woveMetadata = {key};
    }

That didn't work as meta.woveMetadata keeps being undefined in the @afterMethod method.

Current solution

Currently, you can solve this by passing an empty object in the @Wove() decorator like this

@Wove()
@Injectable()
export class FooService extends AsyncService {
}

And attach the key to the woveMetadata.

    processBeforeMethod(meta: Metadata) {
        const key = new Date().toISOString();

        meta.woveMetadata.key = key;
    }

I dived into the source code but couldn't find the reason why the first approach didn't work. Although I would think that the meta object is passed by reference, it doesn't look like that's the case.

Hey @SamVerschueren I'm happy you decided to give it a try! WoveMetadata mostly to provide framework specific metadata to the advices, for instance, using aspect.js-angular, you can access the injector.

I'd suggest to use the @aroundMethod decorator:

type Key = string;
const actionToKey = new Map<Action, Key>();

@aroundMethod({
    classNamePattern: /^FooService$/,
    methodNamePattern: /^process$/
})
processAroundMethod(meta: Metadata) {
    // We don't have a result yet.
    if (!meta.method.result) this.processBefore(meta);
    else this.processAfter(meta);
}

private processBefore(meta: Metadata) {
    actionToKey.set(meta.method.args[0], new Date().toISOString());
}

private processAfter(meta: Metadata) {
    // We have result now
    const key = actionToKey.get(meta.method.args[0]);
    meta.result.take(1).subscribe(data => { ... });
}

The solution you suggested can get tricky in case of async events. In such case, if we have a singleton as woveMetadata and we attach a property to it, another invocation can overwrite it's value. The solution that above should work as soon as we're not getting multiple references to the same action.

Let me know if this helps.

Nice to see how you can make use of the @aroundMethod! Thanks for the example.