Skip to content
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

Local event bus (mediator pattern) #15

Merged
merged 11 commits into from
Mar 16, 2024
Merged

Local event bus (mediator pattern) #15

merged 11 commits into from
Mar 16, 2024

Conversation

lucagiove
Copy link
Contributor

No description provided.

Copy link

codecov bot commented Mar 2, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 70.83%. Comparing base (8bb2d9c) to head (7554505).
Report is 3 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main      #15      +/-   ##
==========================================
+ Coverage   62.41%   70.83%   +8.41%     
==========================================
  Files           7       10       +3     
  Lines         149      192      +43     
  Branches       29       37       +8     
==========================================
+ Hits           93      136      +43     
  Misses         53       53              
  Partials        3        3              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@lucagiove lucagiove added this to the 0.1.0 milestone Mar 2, 2024
@lucagiove lucagiove requested a review from gtoselli March 2, 2024 00:28
@lucagiove
Copy link
Contributor Author

lucagiove commented Mar 2, 2024

  • add test case to cover 2 missing lines when no subscribers are set
    Thanks to the test I spot a bug 💣

@lucagiove lucagiove self-assigned this Mar 2, 2024
@lucagiove
Copy link
Contributor Author

@gtoselli I preferred to use a class like we did in the past with PublicEvent anyway the library is compatible with a plain object that matches the interface, what do you think?

@gtoselli
Copy link
Contributor

gtoselli commented Mar 2, 2024

@lucagiove Did you get the code from Golee?

The names could be better.
The bus is a message bus. Events are a type of message.

Messages passing from the bus must be deserialized to json. So even if we used classes they would go parsed. Does it make sense?

@lucagiove
Copy link
Contributor Author

@lucagiove Did you get the code from Golee?

Partially yes.
I compared it with other similar kotlin solutions seen in viteSicure.
Is it a problem?

The names could be better. The bus is a message bus. Events are a type of message.

Since this is a ddd toolkit I think we should use domain names like "event" instead of message.
Publish / Subscribe is a standard and known pattern that's why I opted for it.

Moreover this could be an external interface, internally there might be a more infrastructural layer where events are converted to messages.

Since it's not a real bus maybe we could use something like:

  • EventsMediator <- probably my favorite choice
  • EventsDispatcher
    but the same interface could be transformed to a bus easily.

Messages passing from the bus must be deserialized to json. So even if we used classes they would go parsed. Does it make sense?

Mah it's a matter of taste, I prefer classes to enforce different type not based on name strings.
I wouldn't serialize anything for this local implementation, types are shared, easy.
In case a different bus is needed there will be an adapter that does the serialization but only of the payload so no matter if is a plain object or a class, payload will stay a plain object.

I'm wondering whether we should enforce an event field for the AggregateId or Id, what do you think?
Following ddd make sense, in case of more generic usage probably not.

@gtoselli
Copy link
Contributor

gtoselli commented Mar 2, 2024

Since this is a ddd toolkit I think we should use domain names like "event" instead of message.
Publish / Subscribe is a standard and known pattern that's why I opted for it.

That makes sense, but my point is another.
Conceptually, this is a message bus that is now used for events but can also be used for commands. The mediator pattern is the implementation chosen for the local implementation of the message bus. Do you agree?

I'm wondering whether we should enforce an event field for the AggregateId or Id, what do you think?
Following ddd make sense, in case of more generic usage probably not.

The application could probably have a DomainEvent class (which is a special kind of message) that requires an aggregateId field.

Since this library is a toolkit, it should offer things as generic as possible that everyone can use as they wish.
If one day we want to implement an improved version of this bus e.g. by implementing retry or something like 'dead letter queue' I would like it to be generic, so that I can use it both for input (commands) and output (events) from my bounded context.

@gtoselli
Copy link
Contributor

gtoselli commented Mar 9, 2024

@lucagiove I found a little error that I fix with commit b57a0dd.

The problem was that the subscribe method takes an instance of the Event class and not the class itself as second argument:

Before

eventBus.subscribe(new FooEventHandlerOk(), new FooEvent({ foo: 'bar' }));

After

eventBus.subscribe(new FooEventHandlerOk(), FooEvent);

@gtoselli
Copy link
Contributor

gtoselli commented Mar 9, 2024

With commit 3a2c794 I made asynchronous handlers execution towards publish method.
Publish method now doesn't wait for handlers fulfillment (or rejection).
If one of the handlers fail, the publish method does not fail (like a real non-local event bus).

@gtoselli
Copy link
Contributor

gtoselli commented Mar 9, 2024

With commit 2d18c84 I introduce a simple retry mechanism. All attempts are logged with warn level. If all attempts fail a message is logged with level error. This allows us to configure log-side alarms (like dead-letter-queue tecnique).

In the commit 08de05d I added a super simple exponential backoff mechanism.

If you read these pr comments first you might be worried that this local-event-bus is getting too complicated.
If you read the code first, however, you will be surprised at the simplicity of the implementation!

@lucagiove
Copy link
Contributor Author

With commit 2d18c84 I introduce a simple retry mechanism. All attempts are logged with warn level. If all attempts fail a message is logged with level error. This allows us to configure log-side alarms (like dead-letter-queue tecnique).

In the commit 08de05d I added a super simple exponential backoff mechanism.

If you read these pr comments first you might be worried that this local-event-bus is getting too complicated. If you read the code first, however, you will be surprised at the simplicity of the implementation!

It is getting too complicated but code is simple.
I would finalize this version and publish the Event only code, then we will introduce the new abstraction layer for generic message what do you think? So we can start using this immediately.

@lucagiove
Copy link
Contributor Author

@lucagiove I found a little error that I fix with commit b57a0dd.

The problem was that the subscribe method takes an instance of the Event class and not the class itself as second argument:

Before

eventBus.subscribe(new FooEventHandlerOk(), new FooEvent({ foo: 'bar' }));

After

eventBus.subscribe(new FooEventHandlerOk(), FooEvent);

Right but this forces to use the class :(

Doing something like this does not make sense anyway?

subscribe<E extends IEvent<unknown>>(handler: IEventHandler<E>, event: Pick<E, 'name'>): void;

Other option is to rely on a string for the event name, maybe more generic..

@lucagiove
Copy link
Contributor Author

lucagiove commented Mar 16, 2024

I implemented publishAndWaitForHandlers after some discussions I opted not to introduce exceptions in case of handlers failure but only the wait part to avoid async drawbacks without spoiling the event idea.
In cases where locally you want to use mediator and throw exceptions probably you better use a command pattern (that we develop in the future).

@lucagiove lucagiove merged commit aa61a43 into main Mar 16, 2024
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

2 participants