Consider an application with two entities:
User
(contains basic user data, such as name)Passport
(contains authentication credentials, i.e. password)
And two internal microservices:
UserService
(responsible for creating and managing users and their basic data)AuthService
(responsible for user authentication and password handling)
The User
entity belongs to the UserService
and Passport
entity belongs to the AuthService
.
Those two services should be separated, because they solve very different tasks: profile data and authentication.
Also, consider we have a registration form with three fields:
- Name
- Password
This form will trigger HTTP-request to the GatewayService
, which intercepts all requests to the application and routes them to internal microservices (or composes/aggregates them).
Now, when gateway service receives the request with all the form data it needs to do the following:
- Call
UserService
to create new user (it will respond with generateduserId
). - Call
AuthService
to create a passport for newly created user. It will need theuserId
received in step #1 and apassword
field from the original request.
This looks pretty straightforward, but what will happen if AuthService
is unavailable on step #2? We will need to somehow separate those requests!
The classic approach is to use the eventual consistency and to create Passport
entity via asynchronous call (we can place this request to the queue and process it in separate service). In order to do this we will send an asynchronous request to the AuthService
passing userId
and password
to it, instead of step #2, so the step #1 will immediately return response to the client.
However, what if password
field is not properly formatted (breaks validation rules)? The validation logic is only present in the AuthService
, so we can't know if password is correct until the call is made to it. And now, the request is processed asynchronously, so we can't get back to user and tell him to correct the password.
SO, how do you properly handle validation in distributed composite requests to microservice application?
The naive solution is to move validation logic to the
GatewayService
itself, but it's a terrible idea, because it will make it fat and will leak business logic fromAuthService
.The other idea is to provide an additional method for password validation and to call it prior to steps #1 and #2. It looks like a viable solution, but it will force us to have two methods for each business method in our microservices, one for prior validation and one for actual operation. Also, there is a time space between validation and operation, so the earlier correct value could become incorrect when operation is actually performed.
We could split the form in two to avoid composite requests and ask user for password after asking for personal data and creating an account for him. However, this could lead to security problems, where user account could be intercepted by some other party who could guess the next
userId
. We could, use some additional security token, but it will introduce odd functionality to services and will make the whole setup more complex.Also, this approach looks like an attempt to escape the problem, you can't always avoid composite requests.
We could use full-scale distributed transactions, e.g. 2PC, but it will make the system dramatically complex and will mitigate the use of MSA in the first place.
And the final idea is to merge those two services together, but it will make no sense in microservice architecture to do so.