I'm trying to get into using microservices, extracting functionality from a monolith into it's own (multitenant) service. However, one of the things i can't figure out how to deal with is this:
What if a microservice (triggered by an event) needs user interaction?
As an example, at the moment we have the following flow (simplified):
We have a donation module where someone can make a donation. The user enters the information (amount, payment method etc). The data gets saved in a database and a call to an external api (payment provider) will be made. The api returns a url where the user can finish the actual payment. We redirect the user to that url and when the payment has been completed, the user will be redirected back to us. We then, in case of success, activate the donation, send a confirmation email and show a message whether it succeeded or not.
Problems
There is a number of problems with this that i would like to get rid of:
- Tight coupling: The donation knows about the payment and the payment knows about the donation
- We can't easily switch payment providers because different providers need different parameters (I.E. provider a supports different payment methods than provider b)
- Fixing bugs is really hard because we need to roll out the fix to many different customers.
So what i think should happen is splitting this functionality into two seperate services:
When a user enters their information, an event 'donation-created' is beeing fired.
Donation service
The donation service creates the donation (of course in it's own data store etc) en then fires a 'payment-requested' event with the amount that needs to be payed. It also subscribes to 'payment-succeeded' and 'payment-failed' (and handles logic accordingly).
Payment service
The payment service subscribes to 'payment_requested', calls the external api, saves the data needed to complete the payment (I.E. url, external id etc). When the payment has been finished (let's keep the actual logic out of scope) it fires the 'payment-succeeded' or 'payment-failed' event.
This decouples the donation and payment implementations, however, because this is asynchronous, i have no idea how to redirect the user to the payment service in order to complete the process.
Solution 1
One of the solutions could be that the client is waiting for an event through long-polling or websockets but having possibly thousands of these concurrently might not be such a great idea.
Solution 2
Another one might be to forget about event based and use synchronous calls to these services but that means that the performance of the donation service is depending on the payment service which is in turn depending on the external api.