When I join a team that is starting to build a new system, we often get to the database, the infrastructure and specific services very early. What we will write it in. Where it will run. What we will connect to what.
I understand the need for something solid to push off from. But at that moment I am also thinking about how much of the future solution we actually know.
On the projects I have been part of, the understanding of the business's needs kept changing during development. Some things only became clear on first use. Other requirements emerged only on top of what we had delivered.
That is why, for me, not every postponed decision is a sign of insufficient preparation. Sometimes it is the opposite: the result of having prepared for change.
For me, good architecture is not the one in which we decided everything correctly at the start. It is the one in which we can afford to decide later without stopping development.
When we had to start, but did not know where it would run
When delivering a project for the state treasury, we did not know the final production environment at the start. We had no contact with the people who would provide the infrastructure, and its specific form was gradually refined and changed.
From a development point of view, this was a fairly uncertain environment. At the same time, we needed to deliver the solution quickly and to a high standard.
I could have waited for answers. Or picked one of the options and hoped it would not change. Neither path seemed good to me.
I needed to separate the work we could do right away from the decisions for which we did not yet have enough information.
This is exactly where architecture stops being merely a picture of the resulting system for me. It starts to determine how we can get there.
What I needed to know and what could still stay open
That did not mean I did not need to know anything.
I needed to understand what the application was supposed to do, what responsibilities it would contain and what it expected from the surrounding services. I needed their contracts and a way to verify that our implementation matched them.
So I split the solution into small parts with separate component interfaces. The business logic could work with what it needed to obtain or pass on, without being tied directly to a specific technical connection.
Dependency Inversion helps me with this: dependencies point to abstractions that express the application's needs, and the specific infrastructure is plugged in through their implementations. [1]
So my work did not consist of simply not making the decision. I decided on the boundaries that allowed something else to stay open.
I consider this difference essential. If I cannot say what I have separated and how I will verify it, I have no reason yet to believe that a later change will be easy.
The surrounding services did not have to be finished for us to work
In Azure we set up a development and test environment with low running costs. We had synthetic data and simulated the required behaviour of the future state treasury services.
From data access according to the available contracts, through security monitoring, to log storage and search via Elasticsearch, we prepared an environment in which the application could be developed and verified.
I was not trying to build a copy of the customer's entire world. I needed our application to receive the kinds of responses, and be able to carry out the kinds of interactions, that it is supposed to work with according to the contracts.
The ports and adapters approach matches this separation: the application communicates through defined interfaces, and behind them there can be a production or a test implementation. [2]
This gave me two to three months to implement and fine-tune the business logic. I did not have to postpone this work until the moment the whole target environment was finally known.
As the architecture became more precise, we could change individual connections and the way the application was assembled. For example, create a different runner that wires up the components and runs them in a given environment.
What we had already clarified in the business rules did not have to be reopened because of it.
A simulation gives me a basis, not the final answer
Here, though, I need to keep a line between what we have already verified and what we still have to verify.
When the application works against our test implementations, I know something about its behaviour with respect to the contracts we expressed in those implementations. That does not yet tell me everything about the real service and the target operation. Even with ports and adapters, integration tests against the real systems remain a separate step. [2]
I need to verify, for example, behaviour on errors, permissions or the real way data is handled. If I change the storage, I also have to deal with data migration.
So when I say that separating the infrastructure makes change cheaper for me, I do not mean that change has no cost.
I mean that because of it I do not have to needlessly change things that have nothing to do with it in meaning.
That is also the limit of what I want to promise a customer. Not an effortless swap of any technology, but an understandable scope of change and a way to verify its result.
I also think about the system by which I build systems
Gradually I realised that when designing, I am not thinking only about the specific application. I am also interested in the way I build it.
For myself I call it a metasystem. A system on the basis of which I create other systems.
It is not one framework I would want to use everywhere. It is a set of connections and rules I keep coming back to. How I divide responsibilities. How I recognise that their meaning has changed. Which dependencies I need to separate. How I find out that a new change has not broken what already worked.
I can choose this way of working at the start, even when I do not yet know the exact shape of the final solution.
So I have a strategy. I develop the tactics during implementation.
I change how components are assembled, refine connections, respond to new information. For me this is not a contradiction of the original design. It is a way of gradually bringing it closer to what we actually need.
Sometimes this produces parts that can be used on another project too. I see that as a useful consequence, not as a reason to build a universal platform in advance.
I do not have to prepare for every possible future
Being able to replace the database does not mean I should immediately write support for several databases. Likewise, I do not want to maintain several operational variants just because one of them might come in handy some day.
I distinguish between the possibility of making a change and a finished implementation of every conceivable change.
This boundary reminds me of YAGNI: do not invest in advance in capabilities I do not need yet. It does not mean giving up on continuously improving the design, but not mistaking it for producing a hypothetical future. [3]
On the project with unclear infrastructure, the separation helped me immediately. Thanks to it we could work and test. It was not merely an investment in a migration that might come in a few years.
I want to think about every further layer in the same way. I am interested in what it lets me do independently and which specific uncertainty it helps me handle.
If I cannot answer that, I need to stop. It is possible that I am no longer creating room for change, but adding one more thing we will have to understand.
Later does not mean after the first problem
Deciding as late as possible does not mean, for me, waiting until the system stops coping in production.
I want to recognise the moment when I have enough information and still have time to make the change. For that I need to know not only what I am missing today, but also how long the move to another option might take.
I think differently about a connection I can adjust and test quickly, and about a change that involves approvals, environment preparation or data migration.
Equally, I do not postpone questions of security or the fundamental properties of data just because I do not yet know the final platform. They may determine how I am allowed to design the solution at all.
So I need to keep track of what remains open and why. Otherwise I could mistake the option to decide later for having stopped thinking about the decision.
So I can change direction in small steps
Experience with mature CI/CD practices showed me one more connection. It is much easier for me to accept the imperfection of a first version when I know we can adjust it continuously and safely.
Automated builds, tests and continuous integration (CI) provide the feedback needed for gradual refactoring. Willingness to improve the design on its own cannot replace that kind of verification. [4]
I can then start with a simpler solution and develop it further based on experience. Split a responsibility, sharpen naming, replace a component. I do not have to bundle all these changes into one big rebuild.
In this sense, calling something an MVP means to me that we know what limitation we are accepting. For example, that the current option suits the first use, but we will have to change it at a certain level of growth.
I need to have an idea of how we will recognise that moment. And a real ability to respond to it.
Without it, gradual improvement would remain just a wish we use to justify today's shortcut.
When my rule meets the needs of the business
Sometimes we decide not to follow a certain rule. I do not automatically see that as a problem.
What matters to me is whether we understand the consequences and whether we agreed on them in advance. For a bounded change, that can be ten or fifteen minutes with the product manager. What we gain now. What will be harder later. How much fixing it might cost.
Splitting an internal object is a different situation from renaming a public message that someone already depends on. I do not want to label both as technical debt and stop thinking about the difference.
There are also situations where a problem only becomes important with significant product growth. It may be acceptable for us to pay for solving it later, if we ever get to that stage. That does not mean we know the cost exactly or that we can forget about it.
Architecture must not stand in the way of the business. But the business must know the cost of its decision.
For me this is not a concession on technical quality. It is part of being responsible for the outcome. I need to understand not only how to build the solution, but also why to do a particular piece of work right now.
Calm that does not come from knowing the future
When I go back to the state treasury project, what matters most to me in the end is not which service we simulated or where the test environment ran.
It is the fact that I could be calm right at the start, when many things had not yet been decided.
I had no certainty about how everything would turn out. But I had a way of working I understood. I knew which part we could verify, where we were still working with an assumption and what we would change when more information arrived.
This is exactly the kind of predictability I want to create for the team and the customer too. We do not have to pretend that we know the whole future system. We need to agree on what should be built now, where the limits of our certainty are and how we will verify the next steps.
The more I think about it, the less architecture means to me the need to have all the answers early. I am more interested in the ability to keep working and gradually bring new answers into it.
I can only afford to postpone a decision if I keep the ability to handle the change.
And it is for creating that ability that I hold the architect responsible.
Applying it to your own project
For your own reflection, I would not start by choosing a different framework. I would take one decision you need to make on your project today and try to separate what you already know about it, what you are assuming and what you still need to find out.
Then I would look at what work you can do independently of the missing answer. That may be where the first practical step lies.
I will gladly go through such a situation with you. Together we can look for what needs a decision now and where it makes sense to first create the conditions for further work.
Sources and further reading
The situations described and the way of making decisions come from my own practice. For the technical context, I include the following materials.
Sources verified on 14 October 2026.
[1] Microsoft. Architectural principles. Separation of concerns and Dependency Inversion.
[2] Alistair Cockburn. Hexagonal architecture / ports and adapters. Separating the application from surrounding technologies, test implementations and integration verification.
[3] Martin Fowler. Yagni. The difference between continuous care for the design and premature implementation of future capabilities.
[4] Martin Fowler. Continuous Integration. Continuous integration and automated verification as the foundation of evolutionary design.

