Reactive Microservices
Up to this point we have been talking in very general terms about microservices. We've discussed the importance of isolation, had an introduction to the laws of scalability, investigated the basics of Domain Driven Design and had a whirlwind tour of Hexagonal Architecture.

But I'm here to tell you there is something more...
A world of never ending happiness
You can always see the sun, day or night

--Prince- Lets Go Crazy


The Reactive Manifesto

In 2013 Jonas Bonér and a group of like-minded developers released the Reactive Manifesto. The reasons for the manifesto were described in a blog post excepted here:

"Application requirements have changed dramatically in recent years. Both from a runtime environment perspective, with multicore and cloud computing architectures nowadays being the norm, as well as from a user requirements perspective, with tighter SLAs in terms of lower latency, higher throughput, availability and close to linear scalability. This all demands writing applications in a fundamentally different way than what most programmers are used to.”

-- Jonas Bonér: Why Do We Need a Reactive Manifesto"


In addressing these challenges, the manifesto identifies four major aspects of Reactive Systems:

Responsive

User expectations have been set by large-scale applications like Amazon, Facebook, Google, and a host of other well-known applications. Each of these applications are designed to respond quickly to UI interactions. Applications that deliver anything less than this level of responsiveness may cause users to become frustrated with an application. Reactive systems prioritize the importance of this responsiveness.

Resilient

As applications increase in complexity, the probability of a failure increases proportionally. Failures lead to service availability issues which undermine user expectations. Failure is inevitable. Either due to faulty hardware, networks or unforeseen edges cases in the application software it is practical to assume that every application will encounter some form of failure. As the complexity of the application and its deployment across multiple hosts increase, there is a corresponding increase in the frequency of failures. Reactive systems must continue providing service to users even in the event of partial failures. By isolating the components from each other, individual component failures can be compartmentalized to prevent much larger systemic failures. Resilience is achieved through three primary mechanisms: Replication, Delegation, and Isolation .

  • Replication

    In a reactive system, it is common to deploy replicas of each service to manage a changing load. This ability to run multiple service replicas enables a subset of the service replicas to fail without a total loss of functionality while providing elasticity to match the application load.

  • Delegation

    Reactive systems are generally built around asynchronous messaging instead of synchronous messaging. Through delegation, we distribute the responsibility of work to other more-specialized components to allow the caller to continue servicing incoming requests. In much the same way that a waiter takes an order in a restaurant and delegates the preparation to the cook who in turn delegates the delivery to the server, reactive microservice delegate the work across specialized components, each executing in a separate context (e.g., thread, process, or different network node). This approach allows the load to be distributed across multiple services.

  • Isolation

    Isolation in reactive systems focus on decoupling components across two dimensions:

    Time- by decoupling the sender from the receiver via asynchronous messaging, each service has an independent lifecycle. The ability to queue messages removes the requirement that both services be available at the same time.

    Space- by executing services in separate processes, we can provision services more efficiently. Service deployment location can change dynamically over the lifetime of the application. A failing service may restart in a different host than it was originally deployed to. A service running on a resource-bound (CPU or memory) host can be terminated and started on a different host machine with better resource availability.

Elastic

Because the load on most services is inconsistent, reactive systems promote the notion of elasticity to manage resources to handle changes in load. As the load grows, a reactive system deploys more instances of the service to provide more capacity. As load decreases, the now idle services are terminated to free up resources. This elasticity prevents a service from becoming overloaded and also prevents the service's resources from being wasted when underutilized.

Message-Driven

Reactive Systems rely on asynchronous messaging to minimize coupling, increase isolation, and provide location transparency. Asynchronous messaging promotes loose coupling by abstracting the message receiver from the message producer. Decoupling is accomplished using a message broker responsible for inter-process communication functions including message queueing, delivery, and persistence. In message-driven systems, the producer only needs to specify the message's delivery address and send it. Once the message has been sent, the producer no longer needs to wait for a consumer to acknowledge receipt or wait for the request to be processed.

Reactive Microservice Traits

From these four major aspects we can tease out the essence of Reactive Microservices into the following:

  1. Isolation

    At the core of the reactive microservice model is increased isolation between components. Isolation provides services with the flexibility to:

    • Change the internals of a service without the need to coordinate with other services.
    • Allow each service to live in a separate process.
    • Decouple the consuming service from the producing service.
    • Contain failures to a single service.
  2. Asynchronicity

    Moving from synchronous to asynchronous messaging allows us to decouple the participating components both in space and in time. A message bus/message broker makes this possible. By mediating the message delivery, the message producer no longer needs to be aware of the location of the message consumer allowing the consumer to be replicated or moved. Additionally, message producers no longer have to wait for a consumer to finish working before they can return to useful service.

  3. Autonomous

    Each microservice is autonomous. It is part of a broader application but is no longer shackled to the lifecycle of a master process. It can start and stop independent of its companion services allowing each service to be managed and scaled independently.

    It is autonomous in its implementation, persistence, and performance characteristics. This autonomy provides the service implementors with the flexibility to decide how best to implement the service. Components can choose the most appropriate implementation languages, frameworks, and persistence to fulfill the service's requirements.

  4. Single Responsibility

    Each Service should follow the Single Responsibility Principle and focus on that responsibility alone. By limiting the responsibilities of each service, it is easier to understand, scale, extend and maintain each component and the application as a whole.

  5. Exclusive State

    Each microservice must be responsible for its own state, and that state is persisted according to the needs of the service. Each service has its own separate persistence store, and all access to the service's state must be mediated by the service's API.

    Because of this, we have to abandon the notion of transactions that span multiple services and must rely on other mechanisms. Each service's internal persistence can still employ traditional transactional semantics, but we must rely on other consistency models for data spanning multiple services.

  6. Mobility

    Every service must be mobile. Processes crash, Containers crash, Servers crash. A crash may require the service to start up in another location, so we can no longer depend on static addressing for services. Additionally, elastic scaling leads to multiple addresses for a given service. Reactive microsystems embrace this and abstract away the physical location of services from the caller.

Summary

We have seen that Reactive Microservices extend the basic distributed service model to address responsiveness, resilience, elasticity, and a message-driven communication model. At this point you should now have a solid, if not fully complete picture of the power that reactive microservice architectures wield.

Coming up

So far, we have been focused on the benefits that microservices can deliver. In the spirit of full disclosure we turn our attention to the dark side of microservices in the next post: Microservice Disadvantages.