Microservices is a buzzword with multiple perspectives. Essentially, it represents an autonomous set of services that can be deployed independently in a repeatable and consistent way. As services become fine-grained, complexity spreads out to service-to-service communication layer. Synchronous mechanisms (REST over HTTP) do not suit all requirements. In fact, it creates a cascading dependency graph wherein one blocking service call has the potential to impact latency. That is where asynchronous, event-based communication patterns can simplify integration needs.
Cloud native infrastructure - Cloud native constructs like Kubernetes and Kafka accelerate creation and composition of independent services with stateless characteristics. Kubernetes simplifies container orchestration by making it declarative e.g. using yaml files. The Kafka ecosystem consisting of Kafka streams, kSQL DB, kafka connect etc. enables a multitude of pub-sub patterns thereby presenting an asynchronous mode of solving problems.
Multi-core processors - As we approach the end of Moore’s law, utilization of every CPU core matters. It requires software design that can distribute computation across components to be run autonomously with minimal or zero external dependency. It's no wonder these hardware trends are reflected in emerging software architectures. For example, containerization (Docker) and bounded context (Domain Driven Design) define isolation components that are similar to micro-services. Functional languages like Clojure and Scala too, provide language level support to implement patterns like event storming, Command Query Responsibility Segregation(CQRS) and data streams.
DevOps - has enabled collaboration across the dev and ops teams by supplying a robust set of tools required to transition legacy apps to microservices. The tool set includes service discovery, rate limiting, load balancing and request routing that offer solutions to common problems posed by traditional microservices architecture.
Below is the diagrammatic representation depicting two different architectural views for a shopping cart related scenario of a retail system. It shows different interaction patterns for multiple services, like billing, inventory and shopping cart, to implement the functionality.
As seen in this architecture view, request based architecture shows an API gateway that routes incoming requests to appropriate services. On the other hand, event based architecture consists of loosely coupled services that communicate through message queues. Event driven architecture represents a different way of thinking when compared to request based architecture.
Temporal view: Event driven architecture is a notable departure from the temporal view of n-tire application wherein a typical request has a complex call graph flowing through multiple services.
Scalability: Request based architecture implicitly has blocking interactions which makes it difficult to scale. In the case of event driven paradigm, non-blocking style is emphasized which promotes publish-subscribe pattern to decouple services in time. Such loosely coupled services can independently evolve to scale on demand.
Agility: It makes individual teams autonomous to make local decisions with less need for coordination requirements. That improves agility of teams as code can be promoted to higher environments based on automated verification of event contracts with upstream and downstream services.
Cloud Based Offerings
Some of the cloud based components that help implement events based microservices are described here.
AWS EventBridge - is a serverless event bus that can glue micro-services. It offers schema registry for events as a built-in feature where events are considered as first class citizens. It also supports rule based filtering to route events to targets. Target can be microservices that can subscribe to different events or it can be an AWS service like SNS to send notifications.
Azure Event Grid -- simplifies event delivery for serverless apps and microservices. It supports events from Azure services as well as custom events. Further, it also offers topics where a publisher app can send events and event handlers can subscribe for specific types of topics.Azure event grid natively supports cloud events specification mentioned above.
Google Pub/Sub - is a messaging service to enable asynchronous integration of microservices. It supports third party integration like Confluent cloud for Kafka and Kubernetes based serverless offering as Knative.
Event structure gains prominence as services communicate using events as contracts. It helps to define and document event schema, which can be text-based (JSON, XML etc) or binary, as part of the design exercise.
As services evolve over multiple releases, it's recommended to consider versioning aspects to maintain backward and forward compatibility. Version specific attributes provide wiggle room for each team to upgrade at different times depending on team specific priorities .
Tolerant reader patterns help to avoid breaking changes only by reading attributes that matter to consuming service and ignore others.
At a large scale, encoding can make an impact on performance as it decides time taken for serialization. Notable binary encoding for event schema are Avro, Protocol buffers etc.
Applicability for IoT
Internet of Things is characterized by high velocity and volume of data transmitted by devices. The design of core IoT services is primarily governed by technologies employed for data ingestion and processing, to manage such high scalability expected from IoT systems. Event driven microservices are particularly useful for architecting IoT solutions as the loosely coupled services are independently scalable. Moreover, IoT solutions integrate with a lot of enterprise systems to bring contextual information to the IoT platform and event based asynchronous integration enables it, in a scalable and performant manner.
Consistency: Event driven microservices can’t support a strong form of consistency across multiple service instances. Consistency boundary needs careful attention as part of event driven design. That is why, it offers limited support to transactions and usually needs a compensation mechanism to handle transaction rollbacks.
ESB Anti-pattern: It can lead to ESB anti-pattern wherein integration middleware starts absorbing business logic thereby making all services dumb. It is considered as an anti-pattern because of the potential to create vendor lock-in. Architectural wisdom says ‘Smart endpoints and dumb pipe’
Immutability: Immutable events support “replay” capability but demand different kinds of data stores like event journals which are uncommon. Architects need to dial down immutability aspects depending on applicable use cases else it can lead to data duplication across multiple data stores.
Event driven microservices are easy to scale but need careful attention to identify boundaries as part of design. If services are not independent and autonomous, teams may not achieve all the benefits of agility. Additionally, observability tools like distributed tracing, monitoring alarms and service health dashboard are required to simplify troubleshooting for development and operation teams.
In essence, event driven microservices contribute to achieving long term requirements for reliability, maintenance and scalability but may add complexity to the design. If managed properly in the design phase, event driven microservices have tremendous benefits.