morganstanley / message-broker

The MessageBroker is a typescript library for providing asynchronous communication throughout your app
http://opensource.morganstanley.com/message-broker/
Apache License 2.0
12 stars 3 forks source link

Message Scoping #46

Open aidanm3341 opened 4 months ago

aidanm3341 commented 4 months ago

A message should be able to be published within a given scope, such that only a subscriber who has access to the messagebroker of that scope will be notified of the published message.

There should be a new instance of the broker for each scope, as opposed to reusing the same global reference.

How does a user get an instance?

An overload of the existing factory method?

const scopedMessageBroker = messagebroker<IContracts>('myScope');

How would the dependency injection work? Is there a feature of Needle that can help us here, such as injecting by token?

constructor(@Inject('myScope') private messagebroker: MessageBroker<IContracts>) {}

The trouble here would be registering the necessary instances against the tokens so that they're there when someone tries to inject it... Might need some input from you @Davidhanson90 on how this could work from the Needle point of view

Example

Note the scopes are the same

const broker1 = messagebroker<IContracts>('scope-1');
const broker2 = messagebroker<IContracts>('scope-1'); // should return the same instance

broker1.get('some-channel').subscribe(_msg => console.log('Loud and clear!'));
broker2.create('some-channel').publish('Is anyone receiving?');
// 'Loud and clear!' is logged to the console

now we use separate scopes

const broker1 = messagebroker<IContracts>('scope-1');
const broker2 = messagebroker<IContracts>('scope-2'); // returns a different instance

broker1.get('some-channel').subscribe(_msg => console.log('Loud and clear!'));
broker2.create('some-channel').publish('Is anyone receiving?');
// nothing is logged to the console
Davidhanson90 commented 1 month ago

I think you want to look at all scopes being created from root.

const child = messageBroker .createScope("scopeId")

const grandchild = child.createScope()

There should be a tree of scope which can be walked up and down via

messageBroker.scopes

Or

messageBroker.parent

DI should not matter here. You can construct using needle but it should be an internal implementation detail.

Resolving scopes should be done externally. Using whatever DI is in play. DI scopes and message broker scopes are mutuality exclusive.

Essentially though you can use same API pattern as needle does for hierarchical injection.

aidanm3341 commented 2 weeks ago

With it being a tree structure, I suppose it would make sense for the child scopes to see the messages from their parent, like this:

it('should publish messages to children from parent', () => {
    const parentMessages: Array<IMessage<string>> = [];
    const childMessages: Array<IMessage<string>> = [];
    const parent = getInstance();
    const child = parent.createScope('scope1');

    parent.get('channel').subscribe((message) => parentMessages.push(message));
    child.get('channel').subscribe((message) => childMessages.push(message));

    parent.create('channel').publish('both should get this');
    child.create('channel').publish('only the child should get this');

    expect(parentMessages.length).toEqual(1);
    verifyMessage(parentMessages[0], 'both should get this');

    expect(childMessages.length).toEqual(2);
    verifyMessage(childMessages[0], 'both should get this');
    verifyMessage(childMessages[1], 'only the child should get this');
});

would you agree @Davidhanson90?