Microservices architecture consists of small and independent domain driven services. Each service is bounded by a context, independent and has its own database. Every service is independently deployed and scaled. Each microservice solves a business problem and the service has complete ownership in that business domain.
A web application or a mobile application can be developed by consuming the underlying microservices. The components are loosely stitched together with layers of abstraction to achieve the business features. Each layer can vary independently and help in keeping the service simple and easily deployable to production.
Microservices can be developed, deployed, maintained and scaled Independently
There are going to be multiple services that a user interface needs to consume in achieving the functionality. It becomes complex if the frond end applications consume individual endpoints of microservices. In order to resolve this problem API Gate way pattern is widely adopted in Microservices architecture.
When there are multiple services the endpoints will vary for every service. API Gateway acts as a single point of entry into the system and insulates the API layer from outside world. It translates from a standard public web-friendly API protocol to protocols used internally. In addition, it helps in addressing the cross-cutting concerns like authorization, API throttling, logging, auditing.
Amazon providers amazon API gateway as a service. It is highly scalable. AWS lambda functions can be used at API Gateway to perform some validations, logging and API throttling. Netflix Zuul is a very popular choice for API Gateway. It is an open source project and has many advanced features.
Communication between services
Every microservice is independent hence the communication across the services becomes critical. The important consideration in microservices architecture is the way different services are integrated. Ideally communication between different services should be minimized. There should be a strategy defined to gracefully terminate the request when the request from participating microservice is not received. Cache strategy is going to play an important role. Assume the microservice to microservice call will fail and define a default or a cached response to the request.
Restful services are an obvious choice for cross service calls. The problem with Restful calls is they are synchronous. As much as possible ensure the communication between services is asynchronous. To resolve this problem, one option is to adopt a message service bus approach using any of the ESB’s like RabitMQ or an ActiveMQ.
The best communication mechanism between microservices is using an event streaming platform like Kafka. Kafka acts as the middleman between the services calls. Service calls don't directly depend on each other. If two services communicate the calling service places a message in a Topic. The responding service listens to the topic and places a response to another topic that the receiving service is listening to. This approach makes the architecture future proof. Changes to the existing services or new services depends on Kafka messages. It is easy to introduce a new service anytime in future that subscribes to any of the available topics in Kafka.
Kafka can be setup in a clustered environment spanning multiple zones and regions hence it will not become a single point of failure for the architecture. More details on Kafka would be covered in a separate article.
The team working on a microservice will chooses the technology suitable to solve the problem. Teams are independent and accountable for the outcomes
Since there is no dependency on service, each service can vary independently. Team can choose appropriate stack that suites the business problem they are addressing.
For developing the microservices the option lies between Spring boot using Java, Flask using python or .NET WebAPI using C#. These three are popular choices but there are many technology stacks to develop microservices.
At the service layer each service can choose the technology appropriate for it. Service should be stateless, and the actions should be idempotent as much as possible. Commits to multiple services can not be managed through a transaction. Idempotent services will make things easy in scenarios like transactions. The API layer should follow versioning which helps in integration and backward compatibility.
Services layer should have a load balancer on top, helping the service to scale up or down. Use tools like Eureka, Zookeper or Consul for service registry and service discovery. Feign client developed by Netflix is a popular choice for Rest client. It helps the developers to expose the services as Restful services through annotations. Use Ribbon as client side load balancer.
There are many patters that can be used in developing each service. Use CQRS and event sourcing patterns to isolate the write and read operations in order management. There can be two services one for read and one for write. Since the services are different we could choose MySql, Aurora RDS, Azure SQL for a write service and opt for a Casandra or MongoDB for a read service.
If there are relations between the users like a social networking site, MySQL is a good choice for write and any graph database for the read service. Write and read in social networking sites is not proportional, normally it varies in the ratio of 1:10 with more reads than writes. Isolating the services will help us to solve the problem independently and scale those services separately.
Unit & Integration tests:
To take full advantage of cloud application, it is mandatory to have unit testing at all layers. It helps in cutting down the delivery timelines and gives the developers more confidence to take up code refactoring. Unit tests and integrations tests should cover 100% of the code. Inorder to achieve CI & CD code coverage with unit tests is mandatory. Places where there are integrations with external service unit tests will help us quickly alert the developer. In case Kafka is the event stream, Unit tests should cove the message formats written to a topic. Addition of new attributes will impact other services but modifying the contract for the existing attributed will have adverse impact on other services consuming that topic. These errors will be catastrophic.
Docker is a virtualized application container. It packages the application service with all the libraries, configuration files and dependencies. It helps in building the container once and deploy it in multiple environments. Since we are not building the code for every environment it becomes easy to test the container in different environments (Dev, Integ, QA, Stage & Production) and release it to production.
Docker has immensely helped to define and manage infrastructure as code. Scale up and scale down can be easily automated by provisioning more servers and add more containers. We shall discuss about it in the DevOps section.
We shall continue our discussion on microservices in detailed in the next blog resilient microservices. We shall take a closer look at different components of a service and how this architecture help applications to be resilient. Continue reading and share your feedback. In case you want to have a conversation about any of the topics you can drop a comment and we shall get in touch.