will the ‘real micro-service’ please stand up!
Every few weeks, I find myself in a conversation about micro-services being a fad and I don’t think they are.
Micro-services are a significant step-up in building highly-scalable systems for fast changing products. Specifically, micro-services allow you to rewrite instead of refactor code and write it differently thus increasing velocity and scale.
When people talk about micro-services they often contrast it with monoliths. Thats not the best way to understand the problems that micro-services solve. The notion that from monolith to micro-service is a one step change is one reason that people fail when making this switch. Please don’t move from monoliths to micro-services — there is another step in between and if you miss it you will fall — it will be painful. The amount of change is massive and none of your operational support staff or even your programmers are geared to handle it.
From a monolith, first move to a service oriented architecture(SOA) with service discovery that is facilitated by a centralised (and highly available) service registry. While SOA traditionally features a message bus to manage message routing and format — it isn’t required and often turns out to be a poor choice. Its possible to use some simple standard for messaging like HTTP.
Once you have SOA then pause and think, maybe you can now build fast enough and scale well enough to meet your product needs. If not, then maybe you should consider moving to micro-services.
Microservices are about making the services smaller (single-purposed) and switching from a centralised service registry to a decentralised one in order to make them elastic — which means letting them scale or shrink automatically. Yes, this is important and if you aren’t doing this with your micro-services you aren’t doing this right. I wish the phrase that had caught on was ‘elastic micro-services’ and not just micro-services. Without elasticity, the Amazon S3 kind, you really haven’t gained anything beyond SOA apart from writing smaller services — which in itself isn’t much at all.
Now back to my original point about the real advantages of micro-services — velocity and scale or shall we say elasticity.
When your system is monolithic your velocity is low because of coupling between code. This coupling manifests itself both during development and runtime.
In development, there is very little friction in using another module or building on top of something that wasn’t meant to be re-used or tweaking another module’s API to meet your needs. This begins to blur boundaries, creates leaky abstractions, adding new code to the right module becomes difficult and changes in one module have unintended consequences for other modules. Database schemas start serving more than one master and changes to them can lead to bugs.
At runtime, if one module has an error the whole system suffers. Features are usually shipped in bunches and not individually. The whole product needs to be retested at every release, all caches emptied and the whole juggernaut rolled back if something goes wrong. Monoliths make for fragile software.
Services are an improvement. They make coupling much more difficult to do. The contracts between services are separated by a network and using them requires considerably more effort and tweaking them to suit your needs is non-trivial. This is good because you have reduced coupling and probably increased cohesion. A way to see this clearly is to draw out all module dependencies using boxes and arrows before you move to a service based architecture and then repeat the exercise with services instead of modules. Also, its easier to refactor a smaller codebase which means lesser technical debt and more velocity.
Services make deployment easier too, you test only what you change, you certainly don’t empty all caches and only roll back the specific service that was deployed if there is a problem. Actually you can do one better, you can deploy the new version without turning off the older one and slowly increase traffic to your newer version as you develop confidence. This is a big win but may not be possible for every update.
When your system is a monolith it is also difficult to scale. Primarily, because all of it is running as one process per instance. Which means you aren’t in a position to use system resources such as CPU and IO more effectively. Also its not easy, though certainly possible, to parallelise steps in a complex operation.
Services are easier to scale in some ways and much easier to optimize in isolation. And some of them may lend themselves to stateless design more easily than a whole monolith. Some things get harder. Debugging and high availability requires more effort than monoliths.
Real micro-services are micro as compared to services. For example, if you had UserService you might break that down into two or more microservices such as AuthService, ProfileService, etc. This means rewrites are as trivial as they are ever going to get. This also helps with scaling in some cases because it allows you more granular control over technology choices such as programming language, kind of data store(SQL or NoSQL, row or column, structured or unstructured) and even things like more precise cache invalidation — all of this adds up at scale. Some things become harder as services get smaller such as transactions and atomic writes.
Real micro-services architecture is completely distributed because unlike their older cousin they don’t have centralised service registries. That means they don’t have a single point of failure. In SOA the registry is somewhat like a general which guides its army of services. Kill(failure) or kidnap(network-partition) the general and the army falls into disarray. Micro-services are like an ant colony — any specific one dies the others can still go about their business. This is possible by using consensus algorithms for enabling a distributed service registry. If you aren’t using something like Consul or etcd for your service registry then you aren’t doing microservices — just services.
Real micro-services may be eventually consistent unlike regular services which are usually highly consistent. This stems from micro-services being partition tolerant and is actually not as big a deal as it may first appear for most use cases.
Real micro-services are independently deployable. Just as they ship with agents for a distributed service registry they also ship with some kind of distributed or elastic datastore and cache for shared state. So if you run your own datastore cluster, for say MySQL, you can’t just add a micro-service instance willy nilly. You must think about capacity, load and connectivity. You will be better advised to use a highly available and elastic datastore like Amazon’s DynamoDB or S3 — this means you can just spawn a new micro-service instance automatically when the load increases and kill it when load reduces using things like AWS Auto-Scaling.
Real micro-services are capable of using immutable infrastructure. You don’t ship an update to all instances of a micro-service, you just launch new ones and kill the old ones. Much simpler and possible because launching and killing many instances does not create load at any one centralised point.
Real micro-services have dumb pipes. They don’t have a service bus. Message passing and routing is not domain specific or regulated as that would create coupling between services. Instead, standards such HTTP are used to keep things agnostic and generally work well.
Real microservices are not a fad. They are different because of their pervasive distributed nature and small size. They are ants.