Thursday, October 25, 2018

Separation of Concerns in Microservices World

When developing microservices, we need to make design decisions like

How is the service getting exposed?. Example: HTTP, Queues

How inter-service communication going to happen?. Example: HTTP, Thrift, gRPC, Socket, Queues

How does service A know what is the service to call next, its transport mode, version, url .etc?

How to deploy service?. Example: Serverless, Containerize service, deploy services in a single instance and autoscale, each service in the separate instance

Decision making should be based on factors like ease of coding, performance improvement, ease of deployment, maintainability.etc. A better approach is to have a clearer separation in your code (separation of concerns) explained below.





(You can substitute with whatever name you think relevant for - Handler, Executor, Service Caller )

Handler

Handler is the piece of code which receives the request in microservice. It can be either through HTTP, Queue etc. It will call the Service Logic / Executor for the business logic to be executed.

Executor

Treat your microservice as a set of functions which does some work. Don't tie up transport layers, inter-service dependency in the executor

It should be a set of functions like this.

doSomething1() { return response;)

doSomething2()...

Service Caller

Executor calls the Service caller for all inter-service communication dependencies. Service caller will decide what is service to call, resolve its URL, version.etc. We may not need a service registry for all use cases.

Let's take a scenario to explain a typical microservice communication.

A, B, C, D, E are microservices.

A->B ->C-->D ( Data flows from A to D)

B->E ( B internally has a dependency on E)

A, B, C should not hard-code its calling/dependent services. It just has to call the Service Caller.

Service Caller can have a function like callNextService( context, data) {}

Context object contains details related to calling service, its version, business specific data
Data contains the payload and other relevant data like tokens essential for the service call
Depending on the context and data, Service Caller resolve the next service and handles the inter service communication.

Write a generic function for Controller and Service Caller (wherever possible) and have a shared library exposed to all microservices.

By having this separation of concerns, changes are easy to accommodate. For ease of understanding, I'm sharing some examples for changes we may come across during development


  • Introduce queue between services
  • Deploy services in AWS Lambda, containerize services .etc
  • Introduce Thrift instead of HTTP for inter-service communication
  • Change microservice communication flow from A->B->C->D to A->B->D


I hope with this examples it is clearer why the separation of concerns is important.

I just want to end this post with some basic design principles which I think more important for microservices development

  • Ability to independently develop, test and deploy
  • Avoid sharing the data model between services
  • Make your Service boundary right by having high cohesion and single responsibility principle
  • Loosely coupled

1 comment:

  1. Information provided by you is very helpful and informative. Keep On updating such information.

    prestige elysian


    ReplyDelete