0

As per the microservice architecure, there should be one codebase per microservice with no shared API whatsoever. This enforces a high level of decoupling and every microservice is bounded within its own context.

But these microservices, over time, will communicate with each other. Service A talks to Service B and Service B talks to Service C, etc...

Given this scenario:

  • -> means request

Client -> Service A -> Service B

Think of Service A being an Account Service and Service B being a User Service. In order for Service A to create an account, it asks Service B if the user exists.

In the case where Service B fails (no user found, system error), how should Service A expose this error to the Client? I could only think of two choices:

Solution 1: Expose the error as is, use Service B's error code

Pros: Easy to do. Just catch and rethrow the error thrown by Service B

Cons: If every microservice would need it own codebase, that means a microservice has its own definition of errors. Solution 1 breaks the principles of having microservices since now it appears like Service A knows the error codes of Service B.

Solution 2: Catch the error in Service A and produce a much more service specific error

Pros: This produces a much clearer error for the client

Cons: Service A will need to catch and expect that Server B is capable of returning a bunch of errors, adding coupling between Service A and B.

The way I see it, both solutions couple each microservice further.

  • Microservice are meant to be autist. They don't speak each other directly. They produce events (messages) and other Microservices are subscribed to these events (messages). The sort of request/response synchronized communication generates the sort of coupling the MS try to run away from. If this's your MS inter-comunication startegy, I encourage you to build a monolith. – Laiv Jun 16 '17 at 21:38
  • @Laiv I like this idea of microservices only communicating via events. I'm very new to microservices and have never heard this aspect of them before. Do you have a source for this or maybe a link to a blog post that elaborates on it? – MetaFight Jun 16 '17 at 21:54
  • Anyways, looking for: Microservices, communication, asynchronous,message broker, pub/sub. It should provide you with enough material. Google. The Nginx blog is just briliant. They have some good posts related to MS. Also look for: Microservices, Choreographed vs Orchested – Laiv Jun 16 '17 at 22:11
1

Exceptions should be treated just like domain models. Each service works with their own domain models and should have their own set of exception models as well. When communicating with external systems, the service should convert external exceptions to its domain exceptions as soon as possible. Basically I'm saying go with solution #2.

Lets consider the communication from service A -> B. Service A should first of all have an interface defined to decouple the business logic from the implementation of requests to B. In your example A is an account service and B is a user service. So let's call the interface UserService. This interface would have a set of (ideally) compiler-checked exceptions.

interface UserService
    def getUser(id): User throws UserNotFoundException, UserServiceException

You should implement HTTP client for service B so that any service that needs to depend on service B imports the common HTTP client. The error responses from requests to service B will be defined in this HTTP client component. That way they're only defined once.

class BHttpClient
    def getUserById(id) = 
        response = http.get("/users/${id}").send
        if (response.status == 404) throw new UnknownUserException
        else if (response.status == 500) throw new InternalServerException
        else return json.parse[User](response.content)

The implementation of UserService, HttpUserService will use that HTTP client to communicate with B, should catch HTTP and transport exceptions from the client and wrap them in the appropriate "domain" exception.

class HttpUserService(client: BHttpClient) implements UserService
    def getUser(id) = 
        try {
            client.getUserById(id)
        } catch {
            case e: UnknownUserException => throw new UserNotFoundException(e)
            case e: InternalServerException => throw new UserServiceException(e)
        }

Cons: Service A will need to catch and expect that Server B is capable of returning a bunch of errors, adding coupling between Service A and B.

Service A will catch errors from the http client in HttpUserService and wrap them in meaningful errors for service A. The business logic in service A is decoupled from service B through the UserService interface. HttpUserService is coupled to BHttpClient, but decoupled from service B because you can mock service B at the transport level.

Even if you choose to use a different architecture like @Laiv describes in the comments, you'll still want to decouple yourself from message and events you receive by converting the message models and exceptions into domain exceptions in each service. I don't agree with @Laiv, that it's as cut and dry as asynchronous message architecture or you might as well implement a monolith. There are still big gains that can be made by a synchronous, distributed service oriented architecture like you've described. The first and hardest step of getting the right architecture is to decouple the components. By dividing into microservices early, you can more easily adopt an asynchronous approach later if you need it.

  • By dividing into microservices early, you can more easily adopt an asynchronous approach later if you need it. this is a false statement which anybody who has worked with trully MS architectecture, would disagree with. To move from one paradigm to the other is anything but simple and easy. The OP is implementation anything but a MS application. One reason is the lack of architectural components for inter-comunication support. The second is that Service A is so dependent of B that both are tightly coupled technically and operationally. – Laiv Jun 17 '17 at 13:05
  • It's different if the OP is just integratating a 3rd party service. Then, there's​ no alternative. Implementing synchronous calls among services is the easier implementation but doing so, you are just implementing a monolith in hardcore mode. You will get all its headaches and none of the benefits. – Laiv Jun 17 '17 at 13:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.