The Architecture Was Correct. The Software Wasn't.¶
What happens when we build a perfectly well-architected system that nobody actually wants?
Software architects have a peculiar talent.
Give us a sufficiently complicated problem, and we'll find a way to turn it into boxes and arrows. We'll identify bounded contexts, establish dependency rules, define interfaces, and debate whether two services should communicate synchronously or asynchronously.
Eventually, someone will ask the question we've been carefully avoiding.
"Does it actually work?"
Of course it works. Look at the diagram.
The boundaries are clean. The dependencies flow in the right direction. The services are independently deployable. There's even a message bus.
It's beautiful.
Unfortunately, the users hate it.
The Comfort of Being Correct¶
There's something deeply satisfying about architectural correctness.
Unlike many aspects of software engineering, architecture gives us rules we can reason about. We can define which components may depend on others. We can prohibit direct database access across service boundaries. We can enforce separation of concerns and identify unwanted coupling.
Better still, we can automate these checks.
Instead of relying on an architect to review every pull request, we can use tools to validate architectural constraints. A dependency points in the wrong direction? Fail the build. A service accesses another service's database directly? Violation. An implementation contradicts an accepted architectural decision? Flag it.
This is genuinely valuable.
But here's the uncomfortable part.
A system can satisfy every architectural rule and still be terrible software.
Imagine an application where every operation takes twelve seconds.
The services are correctly separated. API contracts are respected. Data ownership is unambiguous. Events are published reliably. Every component has a clearly defined responsibility.
The architecture passes validation.
The user, meanwhile, is wondering whether the application has frozen.
Nothing about a clean dependency graph guarantees acceptable latency. Nothing about respecting service boundaries guarantees a useful workflow.
We have proven that the system obeys its architectural rules.
We haven't proven that the rules were particularly helpful.
Three Different Kinds of Correct¶
I think we sometimes use the word correct too casually in software engineering.
Consider three very different questions.
1. Is the architecture correct?
Does the system respect its intended structure?
Are responsibilities assigned appropriately? Are dependencies permitted? Are components communicating through the intended interfaces?
These are questions about conformance to architectural intent.
2. Is the implementation correct?
Does the software behave as required?
Can users complete their tasks? Are calculations accurate? Are failures handled properly? Does the system satisfy its functional and non-functional requirements?
These are questions about behaviour.
3. Is the engineering approach effective?
Did our decisions actually help us deliver the system?
Can developers understand it? Can they change it safely? Did the architectural boundaries reduce complexity, or did they create an elaborate ceremony around something that could have been simpler?
These are questions about engineering value.
The three are related, but they're not interchangeable.
You can pass the first and fail the second.
You can pass the first two and still discover that maintaining the software requires a small army of engineers and an unusually generous cloud budget.
And sometimes you can violate an architectural preference while making a perfectly reasonable engineering trade-off.
That's not an argument against architectural discipline.
It's an argument against mistaking conformance for success.
The Danger of Making Architecture Executable¶
I've been exploring what happens when software architecture moves beyond documentation and becomes something machines can understand.
This is an exciting direction.
Traditionally, architecture documents have been advisory. We draw diagrams, write decisions, describe constraints, and hope the implementation continues to resemble what we intended.
Sometimes it does.
Sometimes the diagrams become historical fiction.
Making architecture machine-readable changes that relationship. Architectural constraints can be validated. Differences between intention and implementation can be detected. AI coding agents can be given structured architectural context instead of discovering everything through repository exploration.
It creates the possibility of architecture becoming an active part of software development rather than an artifact we consult occasionally.
But it also creates a new risk.
When architecture becomes enforceable, our assumptions become enforceable too.
Suppose we've decided that two components must communicate through an asynchronous message broker.
Perhaps that decision was well justified. Maybe it provides necessary decoupling, resilience, or independent processing.
Or perhaps we simply thought asynchronous messaging was the more sophisticated choice.
If we encode that decision as an architectural constraint, our tools can ensure the implementation follows it.
What they cannot automatically establish is whether the decision was wise.
We can make a questionable architectural decision and then build an extraordinarily reliable system for preventing anyone from questioning it.
That's quite an achievement.
Not necessarily a useful one.
Enter the AI Coding Agent¶
AI coding agents make this problem more interesting.
An experienced developer doesn't simply translate requirements into code. They question assumptions, recognize unnecessary complexity, notice when abstractions aren't helping, and occasionally say something every architect needs to hear:
"Why are we doing it this way?"
A capable AI agent can potentially do the same.
But what happens when we give that agent an extensive collection of architectural instructions?
Follow these boundaries. Use this pattern. Execute these steps. Create these artifacts. Validate these constraints. Don't deviate from the approved design.
On one hand, we're giving the agent valuable context.
On the other, we might be quietly removing its opportunity to exercise judgment.
There's an important difference between telling an agent:
"This service owns customer data. Other services must not modify its database directly because we need to preserve data ownership."
And telling it:
"Implement this feature using these seven steps, these three abstractions, and this prescribed interaction pattern."
The first communicates architectural intent and its rationale.
The second prescribes a solution.
Both may be appropriate under different circumstances. But the distinction matters.
If we treat every architectural preference as an unquestionable rule, we shouldn't be surprised when our agent produces implementations that faithfully reproduce our preferences.
Even the bad ones.
And if we judge the agent primarily by whether it followed those instructions, we've created an interesting evaluation problem.
The agent gets a perfect score for doing exactly what we told it to do.
Whether we should have told it to do that is apparently someone else's problem.
Architecture Should Explain, Not Just Constrain¶
This has led me to think differently about what useful architectural knowledge looks like.
A rule tells a developer what is permitted.
A rationale explains why the rule exists.
That difference is enormously important.
Consider an architectural constraint that prohibits direct access to another service's database.
Without context, it's simply a restriction.
With context, it becomes an engineering decision: the service owns its data, consumers should depend on its published contract, and internal schema changes should not require coordinated changes throughout the system.
Now the developer understands what we're trying to protect.
That understanding also creates room for intelligent discussion.
What if the proposed feature requires thousands of synchronous calls and produces unacceptable latency? What if the existing service boundary makes a simple operation unnecessarily complicated? What if the original reason for a decision no longer applies?
A useful architecture should help us recognize those situations, not make them harder to discuss.
I increasingly think the goal should be to preserve architectural intent, not merely architectural instructions.
The model should help explain what decisions were made, why they were made, what evidence supported them, and what consequences changing them might have.
Then engineers, human or AI, can reason about those decisions with an understanding of what matters.
Some constraints absolutely need to remain hard constraints. Security boundaries, contractual obligations, regulatory requirements, and critical safety properties aren't suggestions.
But not every design preference deserves the same treatment.
Architecture should distinguish between the things we must protect and the things we're simply accustomed to doing.
The Missing Feedback Loop¶
There's another problem with making architecture verifiable.
We tend to verify what our tools can observe.
A structural architecture model can establish whether a dependency is allowed. It can detect missing relationships, invalid ownership, or violations of declared rules.
Those are useful facts.
But they're not the complete set of facts that determine whether software succeeds.
Users don't care whether a dependency graph is acyclic.
They care whether they can finish their work.
Developers don't celebrate because a system has seventeen perfectly separated services.
They care whether they can understand the system, diagnose failures, and make changes without triggering an archaeological expedition.
And businesses don't measure success by the number of architectural constraints satisfied.
At least, I hope they don't.
The missing piece is feedback from the implementation and its actual use.
Did the decision produce the benefits we expected? Did the system meet its performance requirements? Did the abstraction reduce complexity? Did developers find the architecture helpful during implementation, or did they spend most of their time working around it?
Those observations should influence our understanding of the architecture.
Otherwise, we have a one-way relationship.
Architecture tells implementation what to do.
Implementation never gets to tell architecture that it was wrong.
That's not engineering discipline. That's a very polite dictatorship.
A Better Definition of Architectural Success¶
I don't think the answer is to abandon architecture, weaken validation, or let every developer reinvent the system according to personal preference.
That would solve one problem by enthusiastically recreating several others.
Instead, I think we need to be more precise about what architecture is responsible for.
Architecture should make important decisions explicit. It should communicate intent. It should preserve knowledge that would otherwise disappear when people leave a project. It should make significant trade-offs visible and help teams reason about the consequences of change.
And yes, it should enforce constraints where violating them would create unacceptable risks.
But the ultimate measure of architectural success shouldn't be how consistently the implementation follows the architecture.
It should be how effectively the architecture helps us deliver software that works.
That may sound like a minor distinction.
I don't think it is.
It changes what we optimize for.
Instead of asking only whether our implementation conforms to the architecture, we begin asking whether the architecture is helping the implementation succeed.
Instead of measuring the number of violations prevented, we also consider the complexity avoided, the decisions clarified, and the changes made easier.
Instead of treating architectural decisions as permanently correct once accepted, we treat them as reasoned decisions that should remain open to new evidence.
And instead of building tools that merely enforce our understanding of the system, we can build tools that help improve that understanding.
The Perfect Architecture¶
Somewhere, there's probably a system with beautiful diagrams, impeccable service boundaries, comprehensive automated architectural validation, and a CI pipeline that refuses to permit even the slightest structural deviation.
Every architectural check is green.
Every design decision has been recorded.
Every dependency is exactly where it belongs.
The system is perfectly compliant.
And somewhere else, a developer is looking at a five-line feature request, wondering why it requires changes to six repositories, three message contracts, and an infrastructure deployment.
Both things can be true.
That's what makes this problem worth thinking about.
I've spent a lot of time trying to make architecture more precise, more verifiable, and more useful to automated development. I still believe that's the right direction.
But precision isn't wisdom. Validation isn't usefulness. And compliance isn't success.
We should absolutely ask whether the software follows the architecture.
We should also be willing to ask a more uncomfortable question.
What if the software is doing exactly what the architecture told it to do, and that's the problem?