This content originally appeared on DEV Community and was authored by Ian hamilton
Why ask?
This post stems from reading yet another post by some developer explaining how to use inter-service communications in macro-services. He called them micro-services, but, as usual, they were not.
A brief history of services
Firstly, I should start with identifying what is understood by the term micro-services, as it basically has two meanings, the dotNet version and the original. Service-based systems have been around for over 30 years and were originally called exactly that, Service-Based Systems. These were large services, usually domain based, although that was not a requirement, rather just a convenient way of setting service boundaries. I have been doing domain based services since 2002, but they have certainly been around since the mid 90s and possibly a lot longer.
Back then, we also did distributed monoliths, if the process was small. That is creating a single server module for the whole application, effectively by taking the database connections out of the monolith and replacing them with network calls to a server module into which the database stuff was placed. These were intentional distributed monoliths, which are not bad, just small. For a while there was also a monstrosity called a Service-Oriented Architecture, or SOA for short, which was characterised by and Enterprise Service Bus (ESB). The aim of the SOA was to connect various legacy systems together as a sort of bunch of enterprise services, held together by the ESB.
In 2012 a bunch of developers got together and proposed breaking up domain services into much smaller units, which would be easier to manage. They gave this idea the name “micro-services”, where micro is usually taken mean very small. This was a simple enough idea, so what went wrong?
The mix up
By 2014, micro-services had become trendy, the thing everyone wanted to do, but it was not something people did in Visual Studio; basically it was a Java thing. Not wanting to be left out, from about 2015, Microsoft docs all started to describe any service based system as micro-services, which has lead to the Microsoft architectural dichotomy of monoliths and micro-services, where everything is either one or the other.
In the real world, there are monoliths and distributed systems, but not all distributed systems are service based and not all service based systems are micro-services.
Those marked with a red cross are deprecated types, so unless you are managing a legacy implementation, you can safely forget about them. Also please note that not everyone even distinguishes between monoliths and distributed systems; there is a variety of opinion available on the internet.
The original name for service-based systems was exactly that, which was okay when it was the only type around. Traditional large services are often referred to as Domain-based services, but the term macro-services is a much better way of distinguishing between large, domain-based services and function-level, real micro-services. As a practical example, DoorDash have stated that when they moved from a monolith to micro-services, they created over 500 services, and making an order touches over 100 services. These are real micro-services, not domain services mislabelled. Each of those 500+ services is individually created and independently deployable, which is one of the principal aims of micro-services.
The problem
To make matters worse, devs building macro-services, but calling them micro-services, have started to import the bounded context idea to the macro-services space. The bounded context was introduced to control who could make changes to which bits of the underlying database and to prevent unplanned changes causing problems in other services. It was an HR exercise to manage developers and keep them on track, it was never a technical requirement. Generally speaking, this is not an issue with macro-services development in the first place.
Introducing the bounded context also means introducing the idea of inter-service communications. This is the weak point of micro-services (function-level services) and has been the downfall of some systems. It may be necessary in real micro-service based systems, but should always be kept to a minimum. It is completely unnecessary in macro-services (domain services), and importing it is simply wrong. If you are doing this, then you have completely missed the point.
Worse still, some have even misunderstood micro-services to the point where they have actually created separate databases for each domain. This is a misunderstanding which comes from the way micro-services are usually drawn, with separate database symbols for each service. This was never meant to mean separate databases, instead it indicates a bounded context, which, in a relational database, should be implemented as separate schema. I suppose people who have done this could claim to have been implementing vertical sharding, but that would only make sense if each database was deployed on a separate server, and even then the load would not be very evenly split in most cases. No, separate databases indicates a complete misunderstanding of just about everything related to micro-services.
Does it matter?
Probably not much. The two-tier understanding of what is meant by micro-services has been around for 10 years and it is too late to fix it. It also only becomes apparent when operating in both worlds, or when switching from one to the other. It is to be hoped, however, that by understanding the whole picture, developers building domain services would not import bad ideas from the function-level services arena. What may be a necessary evil there is a completely unnecessary set of additional complications in the domain services context.
On software architecture
For anyone even slightly interested in software architecture, the best site I have found, and I have looked at a lot, is here: https://developertoarchitect.com/lessons/ and, with particular relevance to this discussion, here is a 10 minute video summary: https://developertoarchitect.com/lessons/lesson114.html If you do not like what you have read so far, you may benefit from watching it. As a general rule, you are better off listening to people with grey hair and 30 or more years programming experience, rather than those who have not been alive for that long.
Is there a better solution?
That will always be a matter of opinion. I have been doing macro-services on and off for over 20 years, and for the last 5 years I have been using compound requests in some situations. For example, on start up, most apps need to load some data. I typically run 3, 4 or 5 queries on the database and wrap up the whole lot up in a single json array. To write an order for a new customer, I wrap up the customer details, with 1 or 2 addresses, the order details and a bunch of order lines, possibly payment details, and probably a stock update. I send this whole lot as a single lump of json, to be unpacked and processed by the server and written to the database as either 1 or 2 transactions. It works for me.
Does it scale? I really do not know. At the moment, I only deploy small scale systems, so have never been in a situation where limits are tested. Are TPS (transactions per second) a relevant measure? Who knows. 1,000 TPS in a typical services application would load 1,000 data sets, but my system can load that with 200 to 300 TPS. Writing the order as I have described above would take 5 to 8 transactions per order in a typical situation, each posting an order fragment, so 1,000 order-fragment TPS would barely handle 200 actual orders, which my system would do with 200 TPS. TPS measures network activity and effort, but it does not really measure useful work done.
And …
Moving on, earlier this year, following discussions about services with some other developers, I set out to write a script-based server, as an alternative to the hard-coded services I usually create. The server will simply load and execute scripts. Each script will contain instructions to handle 1 endpoint. The endpoints will be exactly the same as the existing compiled endpoints, but there will be no hard-coding to handle any aspect other than script execution.
The scripts are text in json format, although xml, or any other suitable text formatting would do. They are all completely independent of any other script and can be added, changed or removed in isolation, without affecting any other script. This is one of the goals of micro-services, and I have not had to resort to micro-services to achieve it. In fact, by using only a single server process, I have actually reverted the application(s) from macro-services to distributed monolith(s). Yes, it is possible to deploy multiple instances of the server, to scale up, but architecturally it is now a distributed monolith.
In my world, the distributed monolith is now better than both macro-services and micro-services, for database stuff anyway.
Please leave comments below.
This content originally appeared on DEV Community and was authored by Ian hamilton