Def: Microservices is a software development technique—a variant of the service-oriented architecture (SOA) architectural style that structures an application as a collection of loosely coupled services. – wikipedia
“A collection on loosely coupled services”. Bit of a brain twist. Ok, loosely coupled we get from the get-go, it’s good software practice and design, but what do they mean by “services”.
Well, first and foremost, to understand this development technique we have to peel off all the layers and go to the core. It’s a very specific type of software development that, in my opinion, is meant to handle a very specific problem – accessing more libraries from more than one language. In other words, having the capability to evolve the technology stack.
In a traditionally styled monolithic app you can (but shouldn’t) chunk everything inside one place. And that would work just fine so long as you never have to touch or change the code ever again after you’re done with it. Sometimes you can see this in practice in hackathons.
But people are smarter than this. They realized long ago that this method is inefficient and came about with rules that govern software architecture and good coding practices. And as such they learned through practice that it’s good to apply the SOLID principles to the code base, and to make it all more palpable to human readers, have a file tree structure that separates code structure in a modular design pattern. And this works just fine. But what happens when you’ve grown and grown and now you’ve got over 50 developers working on your monolithic app?
Things get hard. Constraints start becoming obvious. You can only use one language and are limited to it’s libraries. It’s harder to separate work done into teams. One team’s work that is seemingly finished might be dependent on another team’s separate work and as such they cannot push and merge their tickets separately. Asynchronous development becomes impossible.
So what can you do in this situation? One popular solution is to split up your app into several servers or “Microservices” that can have their own specific language each, specific access to libraries, own development team that can fully concentrate on just one part of the business, and of course – to be truly a microservice – it’s own database.
Obviously drawbacks exist when doing it this way.
– You can either go about having the separate services communicate between each other synchronously – meaning that each separate service can call the other directly and as such are more tightly coupled (harder to change in the future).
– Or you can go the asynchronous way where you have a PUB/SUB style of message queues where a receiver takes a request, does something with it and returns a result to the caller. Some popular ways of achieving this is by using RabbitMq or Apache Kafka.
The second method is unavoidable if you truly want to go the microservice way. Yet oftentimes I’ve spoken with developers at meetups and conferences and they’ve told me that they use microservices, but without a PUB/SUB style of communication between them, or that they have 6 separate servers up and running, but only one common database between them.
In recent months I’ve come to an idea as to why so many people would go the microservice way, but not in it’s entirety like in above examples.
You see, having two or three or more separate servers forces you to write code inside them in a very specific way. For instance you can’t chunk everything inside one place. Another thing is that it forces you – at least in the beginning – to write decoupled code. And that’s good and all, but it never lasts, it never ends up fulfilling what it sets out to do. They set out to do it this way as a means to force themselves to write code in a good structured and orderly fashion. But that is kind of like donating your personal car so that you walk around more and get more exercise. It works in theory, but not so much in practice. What I’m saying is that only a fraction on businesses that set out to use microservices actually need it for objective reasons.
So what would be the best route to go? I for one strongly advocate a strong separation of logic in your app. Grab a pen and paper, write down the essential elements of your app and take a step back and look at the bigger picture.
Let’s say for instructional purposes you have a business that offers publishing capabilities for your customers who are accountants. That would be your USP or unique selling proposition. Other that that you have to offer log in and log out capabilities – thus a users class – a good database solution, some simple and intuitive UI and also a possible means of that said user to read and write posts. And you decide to offer all this over the Internet and as such you choose to do a webapp.
So you have a User class, and Posts class. The MVC for User and Posts is very straight forward and at least for Users you have third party out of the box solutions, “Devise” for rails apps being a pretty good choice. Up until now your customers love the simplicity of the design, but they want to do even more. They want to be able to upload CSV’s directly onto a new Post.
How should you go about it? You could just clump the logic inside the PostsController?
You can go about it that way, but it’s bad practice and here’s why:
– It overcrowds the controller. It should only hold CRUD actions in the public interface and if need be, more specific methods in the private interface.
– It sets a bad example to your other coworkers. If they have to add other features related to Posts, they’ll just follow your example and extend the PostsController until probably one day soon it will be too cluttered and tightly coupled, impossible to extend, refactor or change.
So what are you gonna do instead? You’ll create a separate file in your app directory called data_importers. In it you’ll place your posts_importer.rb file. You’ll start with TDD and create a posts_importer_spec.rb. By doing red, green, refactor you’ll soon end up having the route, the UI link and necessary pages and good test coverage.
The idea behind this is simple. Act as if what you want to add to your base functionality is “external logic”, and implement it as such. Do it in a modular way that is individually tested and has integrations tests on top. If down the line this functionality needs to be expanded, changed or even tossed out, it will be very simple to find the place to do the change. And you can change with confidence knowing that what you touch upon is in it’s entirety in one place and you won’t break something somewhere else.
Doing this you’re file directory will start to look like a microservice oriented application, but instead of separate servers, you have separate folders. Any developer can – at a quick glance and without checking the code in depth – understand what the app does and how it goes about to do it. Business logic becomes apparent, custom logic is easy to find and read, and core structures of your app are left as dumb and as simple as possible.
And if one day your business grows to the point where a monolithic app just doesn’t cut it anymore, you can charge with confidence in the microservices direction knowing that you can structure your code in a intuitive way that is simple to maintain and straight forrward to extend.