The industry keeps changing its technical shapes
Over the past thirty years, software architecture has undergone multiple revolutions. We have moved from monoliths to service-oriented architectures, from services to microservices, from containers to serverless platforms, from synchronous APIs to event-driven systems. Every few years the industry discovers a new way to organise software and, for a time, it feels like we have found the answer.
Yet despite all this change, I have become increasingly convinced that we may be missing something fundamental. Not a framework. Not a platform. Not a deployment model. A unit of architecture.
The disconnect between business language and software structure
The more systems I have worked on, the more I have noticed an odd disconnect between how businesses describe themselves and how software is ultimately implemented.
At the highest levels of an organisation, strategy is often discussed in terms of capabilities. Enterprise architects create capability maps. Business architects model organisational capabilities. Transformation programmes are funded around capabilities. Leaders discuss where capabilities need to improve, where they need to be modernised, and where they provide competitive advantage.
Capabilities are often the language of strategy.
A business rarely says: “We need a new service.”
It says:
- “We need to onboard customers faster.”
- “We need to process payments more efficiently.”
- “We need to improve fraud detection.”
- “We need to support self-service account management.”
These are capabilities. They describe what an organisation is responsible for doing.
How capabilities disappear during delivery
Yet something curious happens as work progresses from strategy into delivery. The capability begins a long journey.
- A business capability becomes an initiative.
- The initiative becomes an epic.
- The epic becomes a collection of user stories.
- The stories become designs.
- The designs become APIs, services, handlers, repositories, entities, commands, and queries.
At every step we gain implementation detail. But at every step we seem to lose sight of the capability itself. By the time we reach the code, the thing the business originally cared about has often disappeared entirely.
Open a typical codebase, and you will find controllers, services, handlers, repositories, aggregates, entities, and DTOs. What you rarely find is a first-class representation of the capability that justified all of those things existing in the first place.
The capability survives only as tribal knowledge. It exists in conversations, diagrams, documentation, and people’s heads. But it is rarely represented directly in the software itself. This has always struck me as strange.
The capability is arguably the reason the system exists, yet it becomes one of the least visible concepts in the implementation.
Why Domain Driven Design felt like the answer
For many years, I believed Domain-Driven Design was the answer to this problem.
I still believe Domain Driven Design was one of the most important advances in software architecture. It encouraged us to think in terms of business concepts rather than technical layers. It introduced a shared language between technical and non-technical stakeholders. It challenged us to model the problem instead of merely modelling data.
For a long time I advocated this approach. Then I had a conversation that forced me to reconsider some assumptions. While discussing a design approach with an engineering leader, I was told:
“The team are happy to follow whatever approach you recommend. Just please don’t do Domain Driven Design.”
When good intentions turn into accidental complexity
The comment genuinely surprised me. The explanation was not that the business concepts were wrong. The problem was that previous attempts had resulted in an explosion of types, abstractions, and complexity. Estimates grew. Development slowed. The resulting model became difficult to understand and maintain.
The easy response would be to argue that Domain Driven Design had been applied incorrectly. Perhaps it had. But I found myself asking a different question.
Why are we working so hard to bend general-purpose programming languages into representing business concepts in the first place?
Modern business systems revolve around concepts such as responsibilities, capabilities, outcomes, policies, rules, and processes.
Yet our programming languages provide classes, functions, methods, interfaces, and modules.
There is an obvious mismatch.The business describes responsibilities.The language describes implementation mechanics.
Perhaps Domain Driven Design was never the destination. Perhaps it was evidence of a deeper problem.
A long history of bridging the gap
For two decades, software engineers have been inventing architectural patterns that attempt to bridge the gap between business meaning and implementation structure.
- Layered architecture.
- Hexagonal architecture.
- Clean architecture.
- CQRS.
- Domain Driven Design.
- Event sourcing.
- Vertical slice architecture.
Each represents an attempt to move software closer to the problem it is trying to solve.
Why vertical slices get closer
Among these approaches, I have always found vertical slice architecture particularly interesting.
Unlike many earlier styles, it does not organise software primarily around objects or technical layers. Instead, it begins to organise software around behaviour.
- Not Customer, but Register Customer.
- Not Order, but Place Order.
- Not Invoice, but Generate Invoice.
This feels natural because change requests rarely arrive in the form of object modifications. They arrive as changes to capabilities. The business wants to:
- improve onboarding.
- Reduce payment failures.
- Introduce social login.
- Add automated verification.
The discussion is almost always capability-centric.
The capability is still missing
Vertical slices move closer to this reality. Yet even there, the capability itself remains implicit.
We still ultimately implement it through handlers, commands, queries, services, repositories, and infrastructure concerns. The capability is present in spirit, but not as a first-class architectural construct. This led me to a realisation. Perhaps the reason capabilities disappear is that our implementation languages have no native way to represent them.
We begin with capabilities. We end with technical constructs. Everything in between is translation.
The thing that survives every rewrite
The capability survives organisational change. It survives technology shifts. It survives rewrites. It survives migrations from monoliths to microservices, from on-premise systems to cloud-native platforms, and perhaps now from traditional applications to AI-enabled systems.
The implementation changes. The capability remains. Which raises a question I have found increasingly difficult to ignore.
If capability is the fundamental unit of business responsibility, why is it not also a first-class unit of software architecture?
And if it were, what would that change about how we design, build, analyse, and evolve systems?
A final question
That question ultimately led me down a path I was not expecting. A path that started not with a new framework or architecture pattern, but with a much simpler idea:
What if the capability never had to be lost in translation?
Leave a Reply