Micro-Services and Macro-Problems
Software development technology and trends move quickly, and right now not many trends are hotter in the startup and corporate IT space than microservices. Like most of these ‘trendy’ development terms there’s some debate as to what exactly constitutes microservices, and especially whether and how microservices differ from SOA and REST based services.
In my day-job I’ve spent a lot of time in the last two years developing, deploying, and debugging code that was originally referred to as “services” and then mysteriously morphed into “micro”-services. Our organization operates in the digital-health space, and although some try to claim we’re a “startup”, in reality we’re a marginally-autonomous tentacle of a massive health insurance conglomerate. Crucially, all of our back-end data is in fact not our own, and our dependencies on the ‘mothership’ are deep, twisted, and inescapable.
So maybe our organization isn't the best fit for “small, independently deployable, autonomous services”. But hey what engineering team ever let such concerns dissuade them from being on the cutting edge! So as the title implies, here in no particular order is a list of reasons why you and your team might want to think long and hard before you go down the microservices road.
This might seem obvious, but discussions around the issue of network overhead were thin on the ground when we were considering adopting microservices. In reality, the amount of time spent serializing to/from the network interfaces of 2o-plus services and aggregating and collecting the data necessary to render a particular user-view is significant. In the traditional “monolithic” server application you only needed to worry about latency between the client-browser or app and your servers, and maybe a little between server and database. Now multiply that by ten. At least. HTTP is not a particularly efficient protocol, so now instead of going to/from HTTP once per client request/response cycle, you’re doing it twenty times?
Now maybe you’ll say “hey, shouldn’t all your microservices be independent? Why are you making these long chains of service calls to generate a client response?” Thats a valid point, but if you want to deliver an experience that is more complex than a to-do app, you’re going to end up composing that view from a bunch of different microservices.”
This is another one that should probably be obvious, but debugging problems across twenty-plus runtime services is much, much harder than debugging a single server-side application. A mix of languages and technologies in your microservices “stack” makes this problem worse. If your team makes use of Visual type development tools or debuggers, throw those away because tracing thru that many layers and interactions using tools is nigh impossible. I hope you kept all your grizzled “printf” debugging experts because you’ll need them.
Infrastructure as Code
This is touted as one of the “benefits” of microservices. That your system infrastructure is explicit and understandable, and system complexity is moved from the “unknowable” source-code into “configured” service-interactions. That sounds great in theory, in reality it means the reliability and “knowability” of your system is entirely in the hands of your dev-ops or sys-admin teams. Whereas in a single-service app if you run into trouble with bugs in production your can simply “roll-back” the server code to a known stable-state, in microservices land the state of your server system is a complex amalgamation of different codebases that in a new system is constantly churning and changing independently. Also, you know how easy it is to hire and keep top-notch devops guys!? I mean I can’t turn around without tripping over one!!
Testing and CI
Unlike integration testing a single-service app, testing the entire flow across a suite of microservices requires more complex tools and processes. Continuous integration means not just ensuring that changes to a particular service wont break its tests and contracts, but that it works with all the things everywhere. The interaction between related microservices can be subtle and a change to one service can break something three or four codebases removed from the source. Again, I’m sure you wont have any trouble hiring and keeping a team of top-flight QA engineers who also can program those sort of tests and maintain all the infrastructure necessary to run that sort of regression testing.
Knowability of the System
This is a big one. Most likely you could take a whiteboard and draw a decent diagram of the data flows within your traditional server system. You might even have visual tools that will draw interaction diagrams of all the classes in your OO system. In a microservices system that kind of knowledge is distributed across both the infrastructure and all the services code. Keeping yourself and your team up-to-date on changes to the system means staying on top of way more moving parts.
So What Should I Do?
Proponents of microservices tout a laundry list of purported benefits to breaking up your application stack into tiny independent moving parts. Supposedly it makes it easier to make changes to the individual pieces without changing the rest. It’s “easier to understand” the small services vs a large monolithic app. Individual services can be scaled up and down independently. All these things sound wonderful, but they all come with costs.
The problems microservices supposedly solve are also problems with well-established, proven solutions within object-oriented programming and cloud services development. Adding more instances to your AWS cluster is significantly cheaper than hiring extra teams of programmers and devops to manage 100 micro-services. Code-complexity is as old as programming itself and putting an HTTP interface between your classes doesn't strike me as the most efficient solution.
In reality microservices is just a different way of expressing good object-oriented design principles. Being excited that you have a service written in Go and three in Elixir and six in Ruby is just dumb. Unless you are writing code for fun adding that sort of complexity to the system is adding costs without value. Pick a language and use it. If you interview somebody and they say “oh I only code in Piet”, wish them a nice day and show them the door.
Sometimes particular parts of your system might make sense to factor out into a separate “service”. These sort of decisions should be made use-case by use-case and as a response to pressures within the production system, not up-front because it’s trendy.