The Context
It was 2008. J2EE was the enterprise standard, the term “microservices” didn’t exist yet, and enterprise middleware platforms were what serious organizations used to build serious systems. I was the lead software architect embedded with China’s leading telecommunications equipment manufacturer to help architect a service-oriented integration layer for their OSS and network management solutions.
The context shaped the technical stakes immediately. A leading telecommunications equipment manufacturer in 2008 was building solutions that would ultimately run inside networks serving subscriber bases in the hundreds of millions, integrating with billing systems processing transactions continuously and network provisioning workflows that had accumulated years of technical debt on the operator side. The mandate was to architect the integration layer for those solutions — service-oriented, composable, and capable of bridging the new equipment layer with the legacy infrastructure it would have to coexist with.
What we built was ESB-based SOA — the dominant enterprise architecture approach of the era. The service decomposition thinking it embodied shares genuine lineage with what the industry would later call microservices, but the two are architecturally distinct in ways that go beyond tooling and vocabulary. Understanding what was the same — and what was fundamentally different — is worth the detour.
What the ESB Was Actually Doing
The Enterprise Service Bus was the heart of the new architecture. Its job was to decouple producers of data and services from consumers — so that a billing system didn’t need to know how subscriber management worked, and network provisioning didn’t need to care how orders were received. Everything talked to the bus; the bus handled routing, transformation, and delivery.
In practice, this meant defining a canonical message format that every service had to speak, and then writing adapters — mediations, in ESB terminology — for every system that didn’t already speak it. Legacy systems don’t give you clean APIs. They give you proprietary protocols, fixed-format batch files, and data schemas that made sense in 1994 and have been accumulating exceptions ever since. Getting a legacy billing system to participate in a service-oriented architecture means wrapping it in something that lies on its behalf — presenting a clean interface to the bus while absorbing all the messiness behind it.
We spent a significant portion of the project on those adapters. This is the unglamorous work that most architecture case studies skip. It is also where most integration projects actually fail. If the mediation layer is brittle, the whole system is brittle, regardless of how clean the service contracts look on paper.
The Service Decomposition Problem
The harder design question wasn’t technical — it was conceptual. What is a service? Where do the boundaries go?
The service boundaries had to be worked out from first principles — from domain analysis of what telecom operations actually required, not from any established industry framework. What bodies like TM Forum were still in the process of formalising through eTOM and related work, we were mapping empirically: subscriber management, billing, network provisioning, product catalog — domains with understood operational boundaries that needed to become explicit architectural ones. We exposed each as a set of services through the ESB: create subscriber, query account, activate service, generate invoice. The contracts were explicit. The implementations were hidden behind the adapters.
The value this created was in composition. A new product offering — say, a bundled mobile data and messaging plan with a promotional billing structure — didn’t require changes to the underlying systems. It required orchestrating existing services in a new sequence: check subscriber eligibility, apply product catalog rules, configure the network resource, set up the billing schedule. Time-to-market for new offerings dropped significantly once the core services were stable, because the work shifted from modifying systems to composing them.
The service decomposition insight — that complex systems benefit from explicit domain boundaries and composable interfaces — is one the microservices movement would later build on, though on a fundamentally different architectural foundation. In 2008, we arrived at it empirically, through the specific constraints of a large legacy telco environment. The constraint was the teacher.
Where It Got Hard
Legacy integration is the part that architects prefer not to dwell on, because it resists the kind of clean narrative that makes case studies satisfying. The reality of 2008-era telco infrastructure was systems that had been operational for a decade or more, running on hardware that had been depreciated and then kept running anyway, maintained by people who had left and whose documentation was incomplete or wrong.
The ESB could route a message cleanly from one modern service to another. The problem was that not everything was a modern service. Some systems could only be reached by writing to a specific database table and waiting for a batch job to pick it up. Some had APIs that documented one behavior and exhibited another under load. Some worked fine until a specific combination of transaction types occurred simultaneously, at which point they would silently produce incorrect output rather than fail with an error.
Finding these failure modes required systematic pressure testing and, in some cases, extended debugging sessions that went late into the night — not because we were heroically dedicated, but because the systems revealed their behavior gradually, under conditions that took time to construct. The Shenzhen skyline outside the window was consistent company. So was strong tea.
Each failure mode we found and documented became a rule in the mediation layer — a check, a retry, a compensating transaction. The adapters got smarter. The system got more reliable. It was incremental and unglamorous and essential.
What the Architecture Enabled
When the integration layer was stable, the value became visible. The product team could specify a new service offering in terms of service compositions — which subscriber rules applied, which network resources were needed, how billing should be structured. The architecture team could evaluate feasibility and estimate effort against a known service catalog. Implementation became a matter of orchestration and configuration rather than system modification.
This was the payoff for the months of adapter work. A clean integration layer doesn’t announce itself; it just makes things that were previously painful seem straightforward.
The broader observation — and one I’ve returned to repeatedly since — is that the value of a service-oriented architecture is a lagging indicator. The investment comes first: the service design work, the adapter development, the canonical message schemas, the governance of who can call what and how. The return comes later, when a product team asks for something new and the answer is “three weeks” instead of “six months and a system modification.”
What SOA Got Right — and Where Microservices Diverged
The 2008 SOA approach got the core decomposition insight right: complex systems benefit from being broken into services with explicit contracts. The composability argument holds. But several of the differences between ESB-based SOA and microservices are matters of architectural principle, not tooling — and the distinction is worth being precise about.
Centralization. The ESB concentrates routing, transformation, and orchestration logic in shared infrastructure. Every service talks to the bus; the bus decides what happens next. This is not a limitation that better tooling fixes — it is a design choice. Microservices inverts it deliberately: intelligence belongs in the endpoints, not the network. The bus, if it exists at all, becomes a dumb pipe. That inversion has consequences for resilience, governance, and how teams own their services independently.
Data ownership. SOA implementations characteristically shared databases across services. A billing service and a subscriber service writing to the same schema are not genuinely independent regardless of how clean their message contracts look — a schema change in one breaks the other at a layer the service interface cannot protect. Microservices treats per-service data ownership as a first-class constraint: integration happens through interfaces, not shared tables. This is a harder discipline to maintain, but it is what loose coupling actually requires at the data layer.
Independent deployability. In 2008, SOA services typically ran on shared application servers alongside other services, with shared infrastructure, shared configuration, and shared failure domains. True independent deployment — the ability to release one service without touching anything else — is a microservices principle that ESB-based SOA rarely achieved in practice. The shared runtime undermined the theoretical independence that the service contracts promised.
Protocol philosophy. WS-* standards — SOAP, WSDL, UDDI — were heavyweight by design, reflecting enterprise integration thinking of the era: rich contracts, formal discovery, stateful sessions. The move to REST and lightweight messaging in the microservices era was not simply a syntax preference. The underlying philosophy — that interface simplicity reduces coupling — is a principled position about how services should relate to each other.
What the ESB era established was that domain decomposition and explicit service contracts are the right way to reason about large system architecture. What it could not yet offer was the decentralised, team-aligned, independently deployable model that microservices made possible. The lineage between the two is real. So is the architectural distance. The Shenzhen work was ESB-based SOA done well — and understanding where that approach strained is precisely what makes the microservices pattern legible.
Three Things Worth Carrying Forward
Legacy systems are not obstacles to architecture; they are inputs to it. The design of your integration layer should be shaped by the actual behavior of the systems it has to connect, not the behavior you wish they had. Adapters that absorb legacy complexity are structural investments, not temporary hacks.
Service boundaries are a product decision as much as a technical one. Where you draw the line between services determines what the product team can compose independently and what requires cross-team coordination. Getting this wrong is expensive to fix later. It deserves more design time than it usually gets.
The value of a clean architecture is realized slowly. The teams that fund integration work often don’t see the return until a year or two later, when new products get built in weeks instead of months. Making this dynamic visible — communicating the lagging nature of the payoff — is as much a part of the architect’s job as the design itself.
The project ended. The architecture kept running. Somewhere in China, services that were first composed in 2008 are probably still executing, wrapped in more layers of abstraction than I can imagine. That’s the nature of infrastructure work: it outlasts the people who built it, which is either a comfort or a warning, depending on how well you built it.