How to model User behaviour that can be shared between microservices in Event Driven architecture?
https://softwareengineering.stackexchange.com/questions/389992
-
23-02-2021 - |
Question
I have an Event Sourced system that has 3 micro services (simplified):
- User management - Registering a user, change password etc
- User account - manages debiting/crediting of the user account balance
- Notifications - email notifications when balance changes
I have a requirement that we want to have Demo users that can use an app like any other user but we don't want to send any notifications to them.
Of course this is an example but you can see how other services can also have different behaviour based on this fact (demo or not).
I'd like to ask you what can be the best option to implement this. Of course it might be that i've got the micro services part wrong.
Potential Implementation 1
- When registering user i send
demo
flag within event so it looks like this:
UserRegistered(email, demo)
- I listen for this event in
Notifications
service and then decide to add the users withoutdemo
flag to it's internal store - Then when
Notifications
service listens toAccountBalanceDebited(user, debit)
event it just ignores the ones for user it does not have in internal store
Drawback is that i might end up adding more stuff to UserRegistered
event
Potential Implementation 2
- When registering user i create either
UserRegistered
orDemoUserRegistered
event - I listen for
UserRegistered
event only in myNotifications
service - When
Notifications
service receivesAccountBalanceDebited
event i check whether we have user recorded (same as in Potential Implementation 1)
Drawback is that we have a lot of common behaviour for user and demo user so we need to listen for both events in most cases.
Potential Implementation 3
- When registering users i create
UserRegistered(email)
event and if this is demo user then i createUserDemoModeActivated(user_id)
- In
Notifications
service i listen to both events, onUserDemoModeActivated
i just remove user record from internal store and proceed as in previous implementations
Implementation that i don't want to use
In Notifications
service get the User entity (basically shared model) and check demo flag here.
Summary
The more i think of it the more i'm into 3rd solution and what i like about it is that it is more an addition than a change in the system.
This is my first approach to ES so i would be glad to get help with it.
Solution
When a user registers I assume they specifically opt-in to receive notifications due to legal reqiuirements such as the GDPR regulations in the EU. Therefore you could have two events during user account registration: UserRegistered
and UserOptedInToReceiveNotifications
.
The opt-in event would be consumed by the notification service to configure their account to receive emails. For demo users you wouldn’t create the second opt-in event, so these users won't receive emails. This ensures the notification service only deals with users who have specifically opted-in to receive notifications from you.
Another benefit of this approach is that a demo user could later choose to opt-in to notifications, via the existing UserOptedInToReceiveNotifications
event, which would then kick-off the notification service as before.
OTHER TIPS
Duplicating all the users on the Notification service has got to be a mistake.
The approach I normally take is summed up as "Fat Events". Instead of sending:
UserRegistered
Email
BalanceUpdated
UserId
NewBalance
I would send:
UserRegistered
User : { id, email, address, category (demo)....}
BalanceUpdated
User : { id, email, address, category (demo)....}
BalanceChange : {id, date, amount, currency... }
This way services listening to the events can take a wide variety of actions without having to query other services or hold their own versions of data.
Your Notification service can simply check the properties of the User object contained in the message and apply its logic as to whether this user should be sent a message.
Alternatively. Notification settings such as "don't receive marketing emails" etc are clearly part of the Notification service. You could have the demo users set their settings on the notification service so as not to receive notifications.
I would go completely the opposite way with inter-microservice notifications - no payload at all, just type, event occurring and id.
Registered { "User", userId }
That way any listening service can make a decision "do I care about this event" and if so can ask the source service for the latest state. That way if either of the two communicating services are offline they can synch back up again easily.
However in terms of a demo or test user you really want as much of the system to be exactly the same regardless and only have the bit you absolutely need to work differently coded differently.
To my mind this means having a "do not send" list of emails in the Notifications service and give your demo users those email addresses.