SOA vs Microservices
Should you happen to be researching “Service-Oriented Architecture” (SOA) on the Internet today, the latest articles you are likely to find will typically be titled: “SOA vs microservices”. Complicating the battle somewhat is that you will also find respected industry experts, such as Martin Fowler, stating that some “consider microservices to be one form of SOA, perhaps service orientation done right”.
Martin goes on to declare: “The problem…is that SOA means too many different things and that most of the time we come across something called ‘SOA’ [there is]…a focus on ESBs (Enterprise Service Buses) used to integrate monolithic applications. This common manifestation of SOA has led some microservice advocates to reject the SOA label entirely”. As such, for the remainder of this article, I will assume the use of ESB middleware as a fundamental characteristic of SOA landscapes, as well as representing the most fundamental distinction there is between SOA and REST.
Given that ESBs work via messaging, those messages are always passed from one fixed source to one or more fixed receivers: communication is always point-to-point. If a called Service API is to be changed, it can be reasonably expected that coordinated changes will also be required in the caller, as well as possibly in the ESB, where transformations are usually performed. This is because point-to-point communication requires a tight coupling between source and target: the API name is known, but which version of the API is to be used, and what is its unique signature?
We should probably also discuss the new API on-the-block; GraphQL, announced publicly by Facebook in January 2015 more than a decade after the arrival of SOA. Although GraphQL represents little more than a Query Language “specification” today, what I find most notable about it is that it solves few of the original problems of SOA. GraphQL norms dictate the use of a “GraphQL Server”, where various transformations can be performed before making calls to one or more – fixed – backend systems in order to serve requests. It is even difficult to see how this is different from the use of an ESB to perform transformations, call one or more – fixed – backend systems, and serve requests. In both cases, all of the intelligence resides in the hub server, whose responsible development team, and we can be sure this will not be the same as the backend teams, must be fully coordinated with for any and all changes to API Service signatures. Goodbye DevOps.
There is also an ongoing fiction on the web that SOA’s API-versioning dilemma has been solved by GraphQL, but what one instead reads on GraphQL.org is that “There’s nothing that will prevent a GraphQL service from being versioned like any other REST API. That said, GraphQL avoids versioning by design… GraphQL only returns the data that’s explicitly requested. This means that you can add new features (and all the associated types and fields) without creating a breaking change”. What’s disturbing about this claim, is that it is difficult even to imagine software that is so poorly written that “the data that’s returned from an API…can be…a breaking change”. “Why do most APIs version? When there’s limited control over the data that’s returned from an API endpoint, any change can be considered a breaking change”.
Pardon? The idea that the data returned in the body of a SOAP/JSON Payload is likely to cause “a breaking change” and that we should accept this to be the key reason “Why…most APIs version” is so far removed from reality I have trouble believing what I have read. What breaks APIs is almost never the data that is returned in the (schema-less JSON) payloads but, instead, changes to the signatures needed to call those APIs: something that is often required to expose new API features, and which is by far the number one reason why new API versions must be created.
To push our myth-busting to its conclusion, version 2 of the OData Protocol; “The Best Way to REST”, provided the ‘$select’ URI Query Option more than 10 years ago. This option allows the caller to specify “that a response from an OData service should return a subset of the Properties which would have been returned had the URI not included a $select query option”. That is to say, to demand that only a subset of the queried OData Service properties are returned to the caller (e.g.
…/OData.svc/Products?$select=Price,Name). Furthermore, “A URI with a $expand System Query Option allows you to identify related entries with a single URI such that a graph of Entries could be retrieved with a single HTTP request” (e.g.
These two OData v2 options combined (and today we have far more options with v4) provide you with exactly the same benefits touted by GraphQL.org as revolutionary, without any need for a standalone server to manage the requests.
Adding further complexity to our SOA-microservices-GraphQL discourse is that, if you continue your research on the web, you will eventually stumble upon fairly recent tweets declaring that the Uber ‘Payments Experience Platform’ is “moving many of our microservices to macroservices (well-sized services). Exactly because testing and maintaining thousands of microservices is not only hard, but it can cause more trouble long-term than it solves the short-term”. This Tweet by @GergelyOrosz of Uber, on 6 April 2020, triggered some near-violent responses arguing that “Macroservices’ are nothing more than ‘SOA-again”.
Given this confusion, if ‘Macroservices’ are to describe an architectural pattern other than simply “SOA-again”, then this new term should be applied to architectural patterns that do not depend upon either point-to-point communication or rigid API signatures. That’s a lucky coincidence because exactly such a term is needed today in the context of a rapidly growing new paradigm: ‘Event-Driven Architecture’ (EDA). EDA not only makes no use whatsoever of point-to-point communication but, in this Architecture, the publisher doesn’t even know whether or not there is a Subscriber(s): the publisher has no chance whatsoever of guessing at the particular API signature(s) that might be invoked in any service calls that may or may not follow on subscriber systems, each of which could use different API versions.
For those who claimed in the past that API-versioning is difficult to coordinate in the tightly-bound, point-to-point communication paths of SOA, and even more so in the (tech-savvy) world of microservices, I suspect that tears may already be beginning to form when they consider the far greater API-versioning complexity across disparate, unknown subscribers. In this newly dawning era of EDA, it is necessary to co-opt and claim ownership of the recently coined term ‘Macroservices’ to describe the exposed Asynchronous Services of those decoupled and distributed architectures that absolutely cannot be founded upon rigid API signatures or point-to-point communication, and which as such represent the antithesis of SOA and GraphQL. The best news of all is that Asynchronous Macroservices can be easily and flexibly exposed using the ‘Requested’ Event Pattern.
Like SOA, well-sized Macroservices should operate at Entity-Level (e.g. SalesOrder); this is exactly where the “Macro” becomes important. If these new services are to pass the ACID test in the example just given, the SalesOrder must be either fully created/changed/deleted or be left completely unchanged (at which point in time the Entity should be unlocked by its host). Given that Macroservices will necessarily operate Asynchronously, all associated entities should be owned by the same Software Component host; the very same host charged with publishing any resultant entity, created/changed/deleted, events.
The birth of microservices was due, in very large part, to a need to provide elasticity for, Synchronous, Web Services; REST Service Endpoints can quickly become overwhelmed without expensive API-Management middleware and rarely support more than 10-20 parallel resources on a monolith. However, microservices, just like SOA, almost always have a synchronous entry point, meaning that when more resources are needed, more synchronous endpoints are being added: the problem isn’t solved, we just throw more resources at it. What is interesting in this regard is that it is far cheaper and far easier to support elasticity by simply making existing services asynchronous. If there are not enough resources available to serve all requests at any given moment, the caller will need to wait a little longer, but they will be served!
A second major factor that popularized the use of the microservices software pattern is their support for modern DevOps practices. Yet, once again, if Macroservices are built to use ‘Version-Stacking’ in the way I have proposed, they actually provide far greater support for DevOps than (synchronous) microservices, as they become far less API-rigid, as well as entirely decoupled from one another.
To conclude, I thoroughly agree with @GergelyOrosz of Uber: there is rarely any way to justify the cost and complexity of microservices today given the arrival of the brave new world of asynchronous “Macroservices”. There is likewise no need to spend any more time researching SOA or GraphQL, which can never support “Event-Driven Architecture”; the new, new-kid-on-the-block.