The Limitations of “Horizontal” Platforms
Layered platforms don’t scale, and microservices aren’t much better. They are anti-agile and keep organizations from adapting to change.
This is the first article in a series on how the “verticalization” of large e-commerce platforms, and the migration to a microfrontend architecture, enables IT organizations to deliver value faster and with higher quality. The following articles will be published (and linked here) sometime in the near future.
Let’s assume we’re an international enterprise that sells a broad portfolio of goods via an e-commerce platform. The platform didn’t come out of nowhere, it has grown over the years. From a small online shop in the late nineties to the backbone of a “digital-first” enterprise that it is today. And the workforce around the platform has grown as well: From a small group of enthusiasts to an IT department of around 100 people across several teams and locations.
The platform works great and is profitable; customers, management and IT people are generally happy. However, not everything is perfect: There’s a lot of communication overhead, product decisions tend to get very technical, onboarding is tedious, features take forever to be delivered.
Why is that?
The answer lies in the technology of the platform and the effects it has on the organization and its workflows.
Agility and Adaptivity
Agile development is certainly the prevalent paradigm in most areas of IT these days, especially in e-commerce. Markets change rapidly, and if you can’t respond to changes, you’re out. While there are very different interpretations of what “agile” means and how to do it, it is always useful to remember the agile principles, and especially Martin Fowler’s true words:
“Agile development is adaptive rather than predictive.”
In regard to technology, adaptivity is the key characteristic of agility. A fair metric of adaptivity could be the combination of two factors:
- How short are your delivery cycles? (A month? A week? A day? An hour?)
- How much net increase in customer value can you deliver per cycle, assuming a constant size of your workforce?
Obviously, the shorter the delivery cycle is, and the higher the value delivery per cycle is, the more adaptive is a platform.
Unfortunately, many platforms that have grown over the years are not really adaptive, due to the underlying architecture.
A typical web application is built as a so-called multi-tier (or, “layered”) application. As the name suggests, it consists of layers, where each layer implements a set of similar technical concerns, and is decoupled from other layers with different concerns. Each layer consists of modules, i.e. building blocks which implement the functionality of a layer. (Some environments use different terms than layer/module, but they mean the same.)
For example, a “presentation” layer will have modules for displaying texts, images, forms and other user interface features. A “business logic” layer contains modules to calculate prices, check inventory, process orders, manage user accounts.
An important aspect of the layer concept is that modules in higher layers may have dependencies on modules in their own or in lower layers, but not on modules in higher layers. This way, you can significantly reduce interdependencies in your ecosystem and minimize the risks of something breaking.
The Evolution of E-Commerce Platforms
At the beginning, an e-commerce platform has only few layers and it is usually “monolithic”, meaning that there is exactly one deployable piece of software.
As the platform grows, the next natural step is to separate the frontend from the backend. The frontend is a Single Page Application (SPA) which runs purely in the browser, while the backend still runs in a datacenter. Between the both, you have a webservice API, for example REST or GraphQL.
As the platform grows even bigger, the backend is starting to „dissolve“, i.e. more and more functionality is extracted from the previously monolithic backend into isolated microservices. These are self-contained, very small applications with exactly one purpose which they expose through a webservice API.
At some point, the old backend has disappeared, and we now have a pure microservice backend, usually with an aggregation layer and/or API gateway in front of it.
There are variations and extensions of this concept, but the basic principle is always the same: There’s a tight horizontal coupling (of modules within one layer), and a loose vertical coupling (of modules across layer boundaries). Therefore, we will subsume multi-tier, backend/frontend, and microservices architectures as horizontal ones.
A horizontal platform is one that has strong boundaries between modules in different layers, while it has weak boundaries between modules in the same layer.
There’s nothing bad or wrong with horizontal systems. Up to a certain size of the organization and the platform, it is a natural way of building things. But it has inherent limitations, as we will see later.
Wait, Did You Just Say Microservice Architectures Are Horizontal?
There, I said it: Even an SPA + microservice architecture is horizontal. Just look at it! The frontend is one big layered monolith, especially from a delivery perspective. Below that you have multiple layers of services, where some are public and some are shared or private. If you draw a dependency graph between these components, it will resemble a layered architecture.
The are, however, two big advantages of microservices: 1. They can be maintained and deployed autonomously. 2. You have less of a technology lock-in (we’ll get back to this later). On the other hand, an enterprise-level microservice ecosystem is much more complex to maintain (think of service discovery, monitoring, tracing, etc.), and each microservice needs versioned APIs plus a set of internal resilience features such as circuit breakers.
And even if the delivery of a single microservice is fast, the delivery of a feature requires a cascade of steps upwards through the platform layers until it is accessible to a customer.
The Hidden Layers
Software ecosystems are often being described only from a runtime application perspective, meaning that the supporting functionality around the application is out of scope. But when we talk about dependencies and complexity, there are many hidden “layers”, which have similar behaviour and effects as the runtime layers. For example:
- CI/CD pipelines are often built “one for all”: All modules within one layer use the same build/test/deploy pipeline. Sometimes even the entire platform uses one monolithic mega-pipeline which is able to deliver all parts of the platform. And, of course, this leads to software being bent to fit the pipeline — especially when the pipeline is managed by a dedicated team.
- Administration tools are usually “layered” as well. While you could argue that they usually have a rather narrow scope, like CMS, ERP or CRM tools, they are similar to microservices in that they have no implicit restriction on “incoming” dependencies, and therefore cannot simply adapt to changing requirements.
- The same is true for external dependencies. An e-commerce platform is connected to a plurality of other services such as payment providers, suppliers, shipment, performance marketing, web analytics and many more.
- Runtime infrastructure is often set up in a way that all modules within the same layer use the same infrastructure: one database for all data, one S3 bucket for all frontend artifacts, one EC2 + EKS cluster for all microservices. Fixing or updating parts of the infrastructure is complex and risky.
To understand the effects of such an architecture on the organization, let’s have a closer look at the development and delivery workflows.
Horizontal Architecture, Tedious Workflow
For our scenario, we will assume an imaginary IT department and look at a typical workflow to implement a feature. Of course, this is simplified and hypothetical, but it is derived from real-world organizations, and the insights are well applicable in practice.
As long as the platform is small, it is managable by one team with one backlog. But as it grows, frontend/backend/microservices each get their own backlogs and are assigned to different teams. The overall feature roadmap is maintained by a team of product managers (or whatever you call them), supported by architects and designers.
Now imagine what happens when business has a new feature requirement:
- A product manager discusses the feature with business, then makes a detailled description of the desired functionality.
- An architect specifies the necessary technical changes in the platform and how they are are integrated across all layers and/or microservices. A designer makes pixel-perfect mock-ups of every possible UI state.
- After product managers, architects and designers agree on the specifications, the feature request is approved for implementation.
- Each development team derives a set of tasks and start working on them in accordance with their backlog priorization. As soon as the last team has finished their implementation, the new feature is ready to be rolled out.
- Because we have changes in different parts of the platform, we do a lot of testing before the final customer roll-out. And a lot of bug-fixing. And re-testing again.
- Finally, we’re ready to release!
Even if we assume that all of the above runs smoothly, the whole process takes several steps and feedback loops. Changes are quite risky, as the affected code base is very large, especially in the frontend.
In such an enironment, nobody can take ownership or responsibility for the platform: product managers, architects and designers have no real influence on the outcome, because they are too far from what’s happening. And developers, testers and operators have no real influence on the specifications.
Do Designers Specify or Implement?
By the way, there’s an interesting observation to make when we look at the UX/UI team, and this is something we see far too often, sadly: Why do the designers make detailled UI mockups before the implementation? And shouldn’t the UI designers be part of the frontend team?
This is a very common, and nevertheless flawed, organizational setup when you have a grown horizontal platform. The UI is being designed as part of the specification process, and is submitted to the developers as a … uh, what is it, actually? Is it a user story? No. Is it a non-functional requirement? No, again. It’s in fact an implementation task which has been finished before the implementation started.
The reason for this workflow is that the product management is too far away from the development and doesn’t trust the developers to implement the feature correctly — or, to put it differently, product management reduces the risk of a never-ending feedback cycle by using UI design as a common specification language.
But pushing a finished UI design as a “requirement” is a serious mistake: A designer might have overlooked technical implications which, in the worst case, render the designs worthless.
Also, in many cases, pixel-perfect UI designs are a waste of time. If you have styleguide, a design system, and/or a UI component library, it’s better to add the designers to the frontend team and let them, in collaboration with the developers, come up with a good UI during the implementation.
Adaptivity vs. Technology Lock-in
The tedious workflow aside, large-scale horizontal platforms are not at all adaptive. The more monolithic parts it has, the worse they suffer from the technology lock-in.
- The company has already invested a lot into the code base and the underlying frameworks, and there’s an entangled web of dependencies between layers and/or microservices.
- You can’t pick the optimal tool/framework/library for the job, because it is incompatible with the existing ecosystem, and migration costs and risk would too high.
- The platform is full of pseudo-features (i.e. features which don’t have any customer value by itself, but instead allow business to deal with historical quirks and the long delivery cycles) and anemic code (code has no business value and is only there to keep different development teams from stepping on each other’s feet).
Technology lock-in, platform complexity and waterfall organization are “adaptivity killers”: They increase the “time to market”, and they diminish the possible outcome.
A possible solution to overcome the problems with horizontal architectures is verticalization, which will be covered in the next article of this series. Stay tuned!