Thoughts on Microservices, Part 3, Throw Away Ware
Microservices can get over used as an architectural pattern sometimes. One architect proposes a solution in the following article.
Join the DZone community and get the full member experience.Join For Free
Microservices are everywhere! In my previous post, I argued that sometimes just a monolith can do the required task without being cut into microservices. But it is wonderful if microservices are the most suitable solution! This is definitely lovely!
First, there're a lot of benefits microservices provide, second, we’ve got great technological support to implement them – there are a lot of frameworks and platforms helping us develop some really good microservices.
Most of the conference talks I have recently seen are basically “Hello world” talks. The main message is “Hey, look how easy it is to create a microservice!”, “Just few annotations, and you are up and running!”. How great is this?!
But when it comes to real life, usually between the start of a project and the first really useful microservice, there is at least one Sprint. More often, three Sprints. You’ll say – “That is ok! We are serious people doing serious work.” I still ask myself – “What can be so huge in one microservice that it may require several weeks to be created? Why is this service is called MICRO?”
I’m not sure if there is an official definition of a microservice, but a well-established idea is that it is a piece of software which is designed to perform only one single function, but in the best possible way. It should communicate with the rest of the world with the most lightweight protocol possible, like REST. So, if we have a login functionality, a microservice should be able to login a user in the fastest, easies, most reliable and secure way. Only logging in, and nothing else. For logging out there should be another service. The size and complexity of such a service is not necessarily 'micro' in scope — they can be of any scale. Technically, there should be only one function exposed, but what happens under the curtains are just implementation detalils.
This is why such services can become really complicated, often even over engineered. From my experience, at the end of the day, each microservice in enterprise web apps becomes a three-tier application. It has its “UI” simplified to REST endpoints, it has its business layer with several service interfaces and their implementations, and an infrastructure layer to talk to the DB and other services. The interlayer communication is done via value objects. As a result, a usual function call in this microservice ends up with several data coping mechanisms from one value object to another (in one such microservice, I’ve seen up to 14 such hops). Why would you do this? The typical answer is if some part of the service changes, we will change only this part of it. Fair enough. If, in return, I ask, how many times in your life have you ever changed one of the tiers without affecting the others? Тhe answer is usually never.
Once again, nothing wrong here. The complexity of the implementation of a microservice is orthogonal to the interface it provides.
Another question: how many times have you ever received a fully functional “freezed” interface definition? The spec that never changes? A guaranteed never to change document? In my case, it's never!
The changes are coming constantly or with increasing speed. Even in terms of one API version. You may say: You are doing it wrong! You should plan more carefully, you should rethink your spec versioning policy, etc. And you're completely right!
But... there is something called real life. There are business needs that require quick reaction, quick change, and quick time to market!
Looks like this constant change is just inevitable. In my 13 years in software development, there have been practically no projects that had a really smooth and stable development process. A least every Sprint there were some “tiny” spec changes, causing the whole codebase to suddenly go red. I’m sure I’m not the only “lucky one” in this regard.
How should we solve this kind of problem in the microservices world? It's quite time consuming to create beautiful, complex, nicely architectured, (at least) three-tier microservices without a frozen spec. As I already said, those spec changes usually require changes in all of the layers. This is hard! Most of us thought that’s just the way it is. Software development is hard.
But this situation kept bothering me. Maybe there is a way to expedite this change reaction? Maybe a microservice should be micro in all aspects? I mean, really tiny, with as little internal abstractions as possible, and really tightly coupled inside. As I’m writing this, I have a feeling I’m breaking the law. It is like I’m cancelling everything I’ve learned from the CS courses at university. Why don’t we throw away all of the … tiers? Make it a really tiny one-tied micro monolith? A microlith? So that we transform the object, or even a direct line with the data we receive, from the infrastructure to a JSON object in one single class, with all the business logic also included in this one and the same class. You are absolutely right to say – “are you nuts?” What if the infrastructure changes? What if the REST API changes? What if the business logic changes? You have to rewrite everything!
And then I suddenly say – “Yes! We will rewrite this service from scratch!” Luckily, a lot of tje code in the service can be just generated. So, yes, we will rewrite it from scratch! We copy/paste. We do all the possible antipatterns, only to make it work according to the spec and pass the tests. “But isn’t it a lot of effort?” My answer is – “Pretty much the same as changing all of the classes in all of the three (or more) tiers!” And people usually say – “Hmm…”
This may sound really strange, but through several projects, writing some really ugly, small, one tier microservices with really almost no architecture and no refactoring, and then rewriting this from scratch saved me a lot of effort. I have even created a name for this piece of software – “Throwawayware.” Try to say it quick!
The next question that usually comes is – “Do you put this ugly thing in production?”
My answer is – “Ok, you got me! No, this does not go to production! May be sometimes...” What actually goes there?
Putting this ugly little thing in production will be just catastrophic. That’s why I’ve tried some mixed approaches.
First of all, we usually develop the tests to follow the contract. Yep, although I’m not a big fan of TDD, I think this is a good place to use it. Adjusting the tests to follow the spec and the contract is always the first priority to us!
Then we usually have 4 to 10 iterations on each microservice in this ridiculous “throw away” way. Funny thing is that up to 50% of the services do not even survive those iterations! At some point the service may become obsolete, maybe even before reaching production. This means that we usually save a lot of effort by not creating something complex for something that will never be used (“Bingo!”).
Then when the service gets through those several iterations, we consider this service a “survivor.” At this time, usually, the spec stabilizes as well. And we do the same for each API version.
Just like the garbage collector in te JVM. Haha! Yep, like the GC! Lovely!
For the survivors, we usually make some really complex refactoring, or rewrite it completely new. We try to make their code really readable, expandable, and maintainable. Quite often from the use of those “throw away” implementations we can see some errors before an unpredicted use case occurs, like the pressure it should hold, or security, or fault times. etc. This may end in three-tier architecture, but with really nicely designed layers.
Looks like this “two stages approach” works really well! Funny thing is that it fits perfectly in our Scrum cycle. Maybe I’m wrong, but this approach saved us a lot of effort and helped us establish good quality implementations in a shorter time in three projects so far. We’ll see how it goes!
Any critiques are welcome though.
Published at DZone with permission of Dmitry Alexandrov. See the original article here.
Opinions expressed by DZone contributors are their own.