Why AI-generated apps fail at the data layer
Code generators are good at screens and bad at schemas. The failures show up months later, in the one part of the system that is hardest to change. Here is what breaks, why, and what to design first.
The short version
- AI code generators optimise for a working demo. The data model they produce is whatever makes the demo work, not what the business will need in year two.
- The expensive failures are not bugs. They are schema decisions: no ownership boundaries, no history, identifiers that leak, money stored as floats, state that lives in the UI.
- The fix is sequencing, not abstinence. Design the data model, the security boundaries and the integration surface first; then let generators fill in the parts that are cheap to regenerate.
- A useful test: if you deleted every screen tomorrow, would the data still describe the business correctly? If not, the architecture is missing.
Ask any team that shipped an AI-generated product in the last two years what broke first. It was almost never the code the generator wrote. It was the data underneath it.
That is not a knock on the tools. Code generators are remarkable at the thing they are asked to do: produce a working application from a description. The problem is what "working" means to a generator. It means the demo runs. It does not mean the business can still run on it after eighteen months of real customers, real money and real edge cases.
This piece is about the specific ways that gap shows up at the data layer, why it shows up there and not somewhere more visible, and what to design before you let a generator anywhere near the schema.
The demo is the spec
A generator, given "build me a booking platform", will produce a booking platform. It will have users, listings, bookings and payments, because that is what booking platforms have. Each will be a table. Each will have the columns the screens need.
What it will not have is anything the screens do not need yet:
- History. A booking has a status. Yesterday it had a different status. Nobody asked for the previous one, so it was overwritten. Six months in, finance wants to know when a booking was cancelled and by whom, and the answer is gone.
- Ownership boundaries. Which service, module or team owns the
userstable? In a generated app, everything owns everything. Every screen writes to every table it can reach, because that was the fastest route to the demo. - Identity that survives change. Records are keyed by whatever was convenient: an email, an auto-incrementing integer, a slug built from a name. Then a customer changes their email, or two listings get the same name, or the integer leaks into a public URL and someone enumerates the whole database.
- Money as money. Prices are stored as floating-point numbers because the generator has seen a million tutorials that do it. Rounding errors arrive with the first refund.
- Time as time. Timestamps without time zones. Dates stored as strings. A "check-in date" that means midnight somewhere, but nobody recorded where.
None of these is a bug. Each one is a reasonable choice for a demo. Together they are a schema that cannot support a business, and a schema is the single hardest thing in a system to change once data is in it.
Why it fails here and not somewhere visible
Front-end code is cheap to replace. If a generated screen is wrong, you regenerate it. Nobody is emotionally or financially attached to a React component.
Data is different for three reasons.
It accumulates. Every day the wrong model is in production, more real records are written into it. Migration cost grows linearly with time and superlinearly with the number of integrations that have started to depend on the wrong shape.
It is the integration surface. Payment providers, analytics, email, the mobile app, the partner API: all of them end up coupled to the schema, directly or through an ORM that mirrors it. Changing a column is now a coordination problem across five systems.
It is where the business rules live, whether you meant them to or not. "A booking cannot overlap another booking for the same listing" is a fact about the business. In a generated app it is usually enforced, if at all, in a form validator on one screen. The database will happily accept the overlap from the API, the admin panel, the import script and the retry job.
So the failure is invisible at launch and expensive at exactly the moment the product starts to succeed. That is the worst possible timing, and it is not a coincidence. Success is what generates the volume, the integrations and the edge cases that expose the model.
What to design before the generator runs
The answer is not to stop using generators. We use them constantly. The answer is sequencing: decide the things that are expensive to change before you generate the things that are cheap to change.
In practice that is four artefacts, and they are short.
1. The domain model, in words
A page or two that names the entities the business actually reasons about, what identifies each one, and which relationships are real. Not tables yet. "A Reservation is made by a Guest for a Unit over a Stay and is settled by one or more Payments." If you cannot write this, you are not ready to generate anything.
2. Ownership boundaries
Which part of the system is allowed to write which data. In a small product this may be three or four modules. The point is that the boundary exists and is enforced, so that when a second team, a partner API or an AI agent arrives, it has a contract to talk to instead of a database to reach into.
3. The invariants
The rules that must never be false, written down and assigned a home. Overlapping bookings, negative balances, orders without a customer. Each one gets enforced at the boundary that owns the data, not in whichever screen happened to be built first.
4. The parts you will never regenerate
Identifiers, money, time, audit history, soft deletes, tenancy. Decide these once, as conventions, and put them in a shared layer that generators build on top of rather than reinvent per table.
With those in place, generation becomes safe, and fast. The generator is now filling in a shape that was designed, instead of inventing a shape from the demo.
A test you can run today
If you already have a generated product in production, here is the diagnostic we use in the first day of an audit:
Delete every screen in your head. Look only at the database. Does it still describe the business correctly?
Can you tell, from the data alone, what happened to a customer's order last Tuesday, who changed it, and whether the money reconciles? Can you tell which records belong to which tenant without trusting a filter in the UI? Can two systems write to the same table without corrupting each other's assumptions?
If the answer is no, the product does not have an architecture yet. It has screens over storage. That is fixable, and it is usually fixable without a rewrite: the screens are fine, the model underneath them needs to be designed and migrated into. But it should be fixed before the next integration, not after.
The honest position
Generated software is not the enemy of good engineering. It is a tool that makes the visible part of a system almost free to produce, which means the invisible part, the model, the boundaries and the rules, is now the whole job.
That is the part we sell. Not because generators are bad, but because they are good enough at everything else that this is where the difference is made.
Questions this raises
Does Automaark use AI code generation?
Yes, heavily, for the parts of a system that are cheap to regenerate: UI scaffolding, boilerplate, tests, migrations we have already designed. We do not let generators decide the data model, the security boundaries or the integration contracts. Those are designed and reviewed by people before any code exists.
Can an AI-generated app be rescued, or does it have to be rebuilt?
Usually rescued. The screens are fine; the schema is the problem. The work is to design the data model the business actually needs, migrate into it with history preserved, and put a real integration layer between the new model and the existing UI. That is a data-platform and backend engagement, not a rewrite.