Product architecture and engineering
An architecture serves a product. Never the other way round.
A project does not start by picking a database, a framework or a cloud architecture. It starts by understanding what the product has to make possible, the constraints it will meet, and the trajectory it has to be able to follow.
The architecture comes after.
Our aim is neither to build the simplest system possible nor the most sophisticated one. It is to find the minimal architecture that brings the guarantees the product needs to work today and to keep evolving tomorrow without losing its speed.
Building exactly what is needed
An application used by fifty people and a platform serving several million users do not need the same architecture. Nor do a product handling public data and software processing sensitive information.
Anticipating every possible need quickly leads to building complexity nobody yet knows will be of any use. Anticipating nothing creates the opposite problem: every change costs more than the one before it.
The work of architecture is therefore to find the right level of preparation.
Some decisions can wait, because they will be easy to revisit. Others have to be made properly from the start, because they concern the data, security, the boundaries of the system, or dependencies that will become hard to replace.
We invest where the cost of future change justifies it.
Standards, without a default architecture
Starting from the product does not mean starting from a blank page on every project.
Build enough applications and certain problems keep coming back: authentication, secret management, isolating components, deployment, storage, backups, logs, monitoring, environment management, scaling.
We have gradually turned that experience into reusable technical foundations. They let us start from practices that have already been proven, without imposing the same architecture on every product. The foundation is shared; what gets built on top of it depends on the context.
We work regularly on AWS, but also on infrastructure such as OVHcloud or Scaleway when hosting or sovereignty constraints — or simply the client’s context — call for it.
The provider is an implementation choice. The properties we are after stay the same.
The product
- Specific features
- Business data and usage
- Particular constraints
Technical foundation
- Security
- Authentication
- Deployment
- Observability
- Data and backups
- Environment management
An architecture has to be handed over
A system turns fragile when an important part of how it works exists only in the head of the person who built it.
That is particularly visible in infrastructure. Infrastructure configured by hand can work perfectly well for years. The trouble starts when it has to be reproduced, when someone needs to understand why a rule exists, rebuild an environment, or take responsibility for it.
Which is why we favour Infrastructure as Code: the infrastructure is described in versioned code that can be read and reproduced, rather than in a sequence of manual actions nobody can reconstruct.
The same principle applies elsewhere. The code should let a reader understand the structure of the system. Important decisions should be findable. Non-obvious business rules should be written down. Deployments should be reproducible.
The point is not to document everything. We document what would be expensive to rediscover.
Good documentation does not try to replace reading the code. It carries the context the code cannot explain on its own.
Quality as an accelerator
Tests, documentation, CI/CD, logs, typing and code review are often presented as rules of good conduct. We prefer to start from the reason they exist.
A test is useful when it prevents a regression that would be costly. A log is useful when it explains a problem that would otherwise be hard to reproduce. A CI pipeline is useful when it removes a fragile manual step from the release process. Documentation is useful when it spares the next developer from mentally rebuilding several weeks of context.
So these practices have no universal metric. 95% test coverage is not necessarily better than 60%. It depends on what is actually covered: testing trivial code heavily while leaving a critical business rule unprotected gives an excellent number and a poor guarantee.
The question is always the same: which risk are we trying to reduce?
| The practice | What it has to bring |
|---|---|
| Tests | Confidence in changes |
| CI/CD | Repeatable releases |
| Infrastructure as Code | Reproducible infrastructure |
| Logs and monitoring | Understanding incidents |
| Documentation | Passing on the context |
| Typing and conventions | Safer changes |
What AI changes in the way we build
LLM-based tools now speed up certain development tasks considerably: exploring a codebase, producing a first implementation, refactoring, writing documentation, generating tests. We use them daily.
But that acceleration makes the principles above more important, not less. Producing code has become cheaper. Deciding whether that code deserves to exist, whether it fits the system properly and whether it actually answers the need remains an engineering decision.
Automated test generation is a good example: producing hundreds of tests and reaching excellent coverage has become easy. It does not guarantee that the behaviours that matter are protected.
AI increases how much can be produced. It does not take over responsibility for the architecture.
Designing for change rather than predicting the future
An architecture has to prepare for change without trying to guess exactly which change will come.
Nobody knows for certain what a product will be in three years: which feature will grow in importance, which provider will have to be replaced, which piece of data will change role, or which regulatory constraint will appear.
So we spend less effort forecasting all of that than avoiding the decisions that would make some of it needlessly difficult. That means clear responsibilities between components, explicit interfaces, dependencies kept under control, and particular attention to the decisions that are hard to reverse.
Modularity is not a goal in itself. It becomes interesting when it lets one part of the system be changed without having to understand or rebuild all the others.
The same logic applies to microservices, event-driven architectures or any other pattern: their complexity has to be paid for by a real benefit.
UI
Library
Provider
Data model
System boundaries
Invest progressively more in the decision
Working with what already exists
Engineering is not only about designing new systems. A large part of the work is arriving on a product already in use, built under different constraints and sometimes by another team.
In that situation we do not set out to rebuild the product the way we would have built it ourselves. We start by understanding where the friction actually is.
Is it the architecture slowing the team down? A dependency that has become a blocker? A data model that no longer matches the business? Infrastructure that is hard to operate? A lack of observability? Or simply a part of the system that needs clarifying?
A difference in technical preference is not reason enough to rewrite. As with a prototype, what works stays until there is a concrete reason to change it.
When the state of an existing system has to be understood first, we start with a technology audit.
When a first version works but now has to carry the constraints of a real product, the subject becomes moving from prototype to production.
An architecture the team can keep alive
Ultimately we judge an architecture less by its diagram than by what it lets the team do.
Can they ship a new version without anxiety? Can they understand an incident quickly? Can they change a feature without unpredictable side effects? Can a new developer understand the system without depending for weeks on the people who built it?
And above all: can the product keep evolving at the pace of the company?
That is what engineering is for.
Our principle of quality as an accelerator, and how it fits into the way we build products, is set out in detail in the Vezero Method.