Contents (11 sections)
- Three weeks to lay the core, two years to benefit from it
- Setback 1: the purchased template, thrown away twice
- Setback 2: migrations reset four times
- Setback 3: dependencies that change their terms under your feet
- Setback 4: the version upgrade that would not go through
- Setback 5: the work nobody sees, and that still has to be funded
- Setback 6: the team that changes
- What the core made possible: replacing half the stack in three weeks
- What replaced discipline: executable guardrails
- The most honest test: can you rebuild your project as it was six months ago?
- What we take away
No development project goes to plan. That is not an organisational failing, it is the nature of the work: over three years, the market moves, libraries change their licences, frameworks ship major versions, the team changes, and the product itself discovers who it is talking to. So the real question is not “how do we avoid surprises” but what, in an application, must stay standing when everything else moves.
We have a textbook case to hand. BuyStep, the B2B energy marketplace whose platform we have been building since summer 2024, has changed its framework, its database, its mapping engine, its e-mail provider, its billing system and its presentation technology. It has thrown away two purchased templates and reset its migrations four times. Today it runs on a .NET 10 API and a Next.js 16 portal, and its domain model is still the one from August 2024.
To write this article, we reconstructed nothing from memory: twenty-six real commits were extracted from the Git history, recompiled with the SDK of their time, restarted on a fresh database and photographed. Twenty-five started up again. The figures come from Git, not from an estimate.

Three weeks to lay the core, two years to benefit from it #
The repository starts on 1 August 2024. What was put in place in the first twenty days shaped everything that followed:
- A layered architecture (Domain, Application, Infrastructure, WebApi, Utils) laid down in the very first commits, before there was anything to display.
- A generic translation model: one entity, one associated translations table. No
nom_fr,nom_encolumns, no resource files duplicated per language. - Continuous integration from day one. The pipeline file was fixed eight times in the same day before it worked. It was tedious, and it was the best investment in the project: two years later, that pipeline still blocks anything that breaks.
- Database migrations in code, versioned with the application, rather than a schema administered by hand somewhere.
Setback 1: the purchased template, thrown away twice #

Starting from a commercial template is a reasonable decision at launch: it buys time. It costs in October 2024, when more than 240,000 lines of old presentation files have to be removed and replaced with a lighter stylesheet. And it costs again in January 2025, when a second template is imported wholesale, with the old page kept alongside under a name ending in OLD.
The key point: these two clean-ups touched only the presentation layer. No entity, no application service, no migration was affected. A project where the purchased template had seeped down into the data model would have paid for these two episodes with a rewrite.
Setback 2: migrations reset four times #
This is the most visible symptom of a young project: the database migration history is rebuilt from scratch four times. In December 2024, most of the October and November files disappear. In March 2025, thirteen new migrations replace the previous ones. In October 2025, 54 files are deleted in one go. In April 2026, the whole history is squashed into a single initial migration.
Should that be alarming? As long as the schema is derived from the model in code and not the other way round, no: a reset is a housekeeping operation, not a loss. What would be serious is resetting a schema whose truth lives in the production database and nowhere else.
Setback 3: dependencies that change their terms under your feet #
None of these decisions was in the original plan, and each one was forced from outside:
- The search client moves from an abandoned library to the official one in June 2025: a complete API change, carried out in a dedicated branch.
- The mapping library goes paid and starts checking a token at startup. In April 2026, it is replaced by a code generator that produces the same conversions at compile time: no more reflection at runtime, no more licence to renew.
- The API documentation drops the long-standing tool for the framework's native OpenAPI support, after a round trip reverted the same day.
- E-mail sending changes provider in December 2025; file storage moves to S3-compatible object storage with a CDN in April 2026; in-house billing (invoices, transfers, payment details) is removed in September 2025 in favour of a model fully delegated to the payment provider.
The common thread is always the same: these libraries were confined to the Infrastructure layer. You replace an adapter, not a system. The day a mapper goes paid, the team that let its calls spread into the business services rewrites three hundred files; the team that kept them behind an interface rewrites ten.
Setback 4: the version upgrade that would not go through #
March 2025, the move from .NET 8 to .NET 9. On paper, a routine operation: every project file, every package in the data access layer, the test suite. In practice, it was the busiest window of the first year (forty-eight commits, twenty-three of them between 10 pm and 6 am) and the only one we never managed to get running again.
A year later, the same operation to .NET 10 took one day, in the middle of a much broader switch-over. It was not the framework that had become easier: it was the project that, in the meantime, had tests, a pipeline, configuration in environment variables and a reproducible container image.
Setback 5: the work nobody sees, and that still has to be funded #

This episode is not isolated. September 2024: a whole month of domain modelling (REST API, DTOs, first tests) without a single pixel of change. 3 December 2025: twelve commits in the day (pipeline, telemetry, commission management) and screenshots strictly identical to those of the day before.
This is the most frequent source of misunderstanding between a technical team and those who fund its work. What is visible and what costs money are not correlated, and project management that only looks at screens always ends up deciding against the core, in other words against the product's ability to absorb the next setback.
Setback 6: the team that changes #
The history shows up to four active contributors in a month, and whole months with just one. February 2026 contains only one commit and a text fix. Conversely, October 2025 was written by a single person, in eleven consecutive merges to production.
What absorbs this variation is not heroics: it is written conventions. In October 2025, configuration moves entirely to environment variables, the application is dockerised, a seed data set is written and the build commands are documented in the repository. From then on, a newcomer can run the project without asking anyone. It is the only lasting answer to staff turnover, and incidentally the condition for AI assistance to be useful rather than noisy.
What the core made possible: replacing half the stack in three weeks #

Between 24 March and 12 April 2026, the repository is restructured into a monorepo, the database moves from MySQL to PostgreSQL (with the two separate databases merged back into one), the framework moves up to .NET 10, the portal is rewritten in Next.js and the Blazor administration is removed from the repository. Three weeks.
None of these operations required rewriting the business domain. That is exactly what a layered architecture is supposed to buy, and it is rarely tested in real conditions: here, it was tested four times in a row. One detail says it all: the day before the Blazor administration was deleted, commits were still fixing its navigation. You keep repairing what you are about to remove right to the end, because the removal is planned, dated, and leaves no double maintenance behind it.
What replaced discipline: executable guardrails #
A core does not hold on its own; it holds because something checks that it holds, on every release. In the same window in which the volume of code exploded, four things appeared:
- Integration tests leave the in-memory database for a real, ephemeral PostgreSQL in a container, reset between tests. An in-memory database replays neither the migrations nor engine-specific SQL: it hides precisely the bugs that a large migration produces.
- Architecture tests automatically check that the layers are not crossed. It is the only way to guarantee that a core stays a core when fifteen people (or fifteen assistant sessions) are writing in it.
- A clear separation between unit tests (a few seconds) and integration tests (a few tens of seconds), so that nobody has a reason to skip the former.
- A five-stage pipeline that blocks anything that breaks, and end-to-end tests on the payment journey added in August 2026, where a mistake costs real money.
The most honest test: can you rebuild your project as it was six months ago? #
That is the question this retrospective ended up asking, and we now use it in our own audits. Rebuild an old commit, apply its migrations, get it to start: if the operation fails, it is almost never the code that is at fault. It is a secret that became mandatory without being documented, a dependency that stops the process when its service is missing, a port already in use, a migration that does not replay.
In other words, it is a test of how well you control your environment, and it is merciless. The tooling that runs it is described in a separate article; what AI assistance changed in the way these migrations were carried out is covered in this one.
What we take away #
- Decide early what will not move. The layered structure and the data model are paid for at the start or ten times over later. Everything else is replaceable, and will be replaced.
- Confine external dependencies. A library that changes its licence or its API should never cost more than an adapter.
- A schema derived from code can be repaired; a schema administered by hand is something you endure. Four migration resets broke nothing for that reason.
- Fund what nobody sees. The months without a single pixel of change are often the most structural.
- Make the project startable without you. Configuration in environment variables, containers, a seed data set, documented commands: that is the answer to a developer leaving as much as to a new one arriving.
- Automate the checks on the core. Tests on a real database, architecture tests, a blocking pipeline: without them, a core degrades in silence.
This is how we design the business applications we build, and how we take over those that already exist. If your platform is a few years old, nobody knows how to rebuild it any more and every change costs more than the last, that is precisely the diagnosis we know how to make. First conversation free, quote within 48 h, and hosting in France if you wish.



