One well known benefit of utilizing the microservice design pattern is to decouple business concepts into distinct domains which enable teams to quickly release business value incrementally. The advantages of doing this are paramount in extremely competitive markets where enterprises who can pivot the fastest often produce the most amazing things. Some enterprises don't quite realize that by investing in a microservices based architecture you may, in fact, be prepping your company to go to the cloud. Let's take a run through some specific characteristics of a microservice approach that enhances cloud compatibility.

Macro single responsibility principle

A well known principle in software engineering is the Single Responsibility Principle (SRP), which is, simply put: entities in your system should have only one job. The more functionality a body of code takes on the more complex it is by definition. Microservices encourage us to break our business concerns into smallish domains of functionality with a simple, known set of operations against it. Using REST principles an example set of APIs for a customer service might be:

#GET
http://www.myco.com/customers/jduv

#PUT
http://www.myco.com/customers/jduv

#POST
http://www.myco.com/customers

#DELETE
http://www.myco.com/customers/jduv

The single responsibility of this microservice at a macro level could be stated as follows:

This microservice will handle read and write operations for customer entities inside our domain.

By breaking up business domains and limiting the operations to be performed on them we build small units of reusable functionality--which is exactly the spirit of what microservices is attempting to achieve. While we are technically handling four operations with this implementation, the domain context is limited to CRUD and validation operations against the domain object--nothing more.

Self contained, small code-bases

A few years ago shipping client libraries for monoliths was common practice. The valiant driving principle behind doing this, and many other good behaviors, was Do Not Repeat Yourself (DRY). In large, distributed systems of microservices shipping client libraries becomes increasingly more difficult as the number of services increases. Additionally the maintainability of client libraries tends to be very high, and you force other services--and consequently other teams--to be coupled to the library's release cycle. In a microservices architecture we may reinvent the wheel occasionally but the benefits of remaining decoupled from other teams outweigh the trade offs of having duplicated code.

If a team owns the complete life-cycle of a particular service then there are zero dependencies outside of the team in order to ship value; this is quite liberating and allows engineering efforts on multiple projects to execute completely asynchronously. There many challenges in maintaining a consistent contract for services as their complexity increases and as they take on additional functionality, but there are novel techniques to help ease this pain like consumer driven contracts.

Stateless designs, idempotency, & avoiding session affinity

Two of the most important tenants of microservices design are idempotency of operations, and that the overall implementation should be as stateless as possible. A common example of this is dealing with session state which can, for example, be offloaded to a key value store like Redis. Outside of databases and clustered systems, which are inherently stateful, there should be few reasons why a service implementation should hold state that you cannot offload to another platform. Exceptions to this rule should be isolated as much as possible. The concept of idempotent operations is not new and is a part of the HTTP specification. Idempotency buys us the guarantee that executing the same message N times always produces a predictable result. Via this guarantee coupled with a stateless design we can assume that any given message may be processed by any given instance of our service will produce the same result.

Through these two design techniques, we can build APIs with minimal dependencies and that guarantee no variability in how messages are processed. As such, we can scale services trivially by "rubber stamping" deployments to containers or virtual machines. This decoupled design lends itself perfectly for cloud systems--many of which already provide auto-scaling and self-healing facilities out of the box. The result can be staggeringly high availability.

Thanks to the popularity of web sockets and similar technologies session affinity might creep into your design--perhaps even unintentionally. When building interactions like this we must assume that any host can handle the clients request or series of requests else your application may break if you lose that server.

Summary

All of these design patterns inherently help solve for common non-functional requirements in ways that map directly to modern cloud frameworks. Focusing on rolling out these design patterns on-prem can produce scalable architectures that may be fork-lifted into IaaS/PaaS solutions when you're ready.

  • Focus on building services that do only one thing. Embrace the single responsibility principle at a macro level and break domain contexts into discreet bits to reduce complexity of implementation.
  • Violate DRY where appropriate. The time for client libraries is past and shared code should be kept at a minimum--especially in teams running Agile software development processes. Teams who own discreet, sufficiently small pieces of functionality usually release more often--which translates into less lost opportunity costs.
  • Offload state into platforms as much as possible, and embrace idempotency as a non-functional requirement. Doing so will build scalability into your implementation and allow you to take advantage of built-in auto-scaling and container frameworks.
  • Be careful with web sockets and similar two way connections, as they can introduce indirect session affinity into your service. Try to build connections of these types assuming that any server may field the request or series of requests.