In 2024 I shipped a checkout that sold the same research report into two legal systems at once: Russian companies buying under 152-FZ, European buyers under GDPR. One product. One codebase. And one question that decided the entire compliance story before a single policy document was written: when a buyer pays, which database is their name allowed to land in?

I answered it in the schema.

Residency decides where a person is stored. A Russian buyer’s row is written to the Russian store. A European buyer’s row is written to the European one. Not a country column with a promise to filter carefully: a routing decision made once, at write time, at the storage boundary. Everything downstream inherits it for free.

The Write Path Is the Compliance Program

The default architecture I keep meeting in early-stage teams is one shared users table with a nationality flag. It feels harmless. It is not.

Every debug query against that table is a cross-border transfer. So is every analytics export, every replica spun up in a cheaper region, every CSV a marketer pulls the evening before a campaign. The law does not care that the engineer intended to add a WHERE clause. GDPR’s fine ceiling is €20 million or 4% of global turnover, whichever is higher, and the audit will not ask about intentions.

So the system refuses to hold that conversation at all. Three properties, enforced in the storage layer:

  • Buyer data lands in the store of the buyer’s jurisdiction. The decision happens at write time and is never revisited.
  • The shared analytics spine holds an opaque identifier and nothing that names anybody. No email. No phone number. A reporting database cannot leak what it never stored, and no engineer has to remember to be careful with it.
  • One schema on both sides. The connection string forks; the logic does not.

That last point is the one that makes the pattern affordable. The B2B side of this checkout validates company requisites, enriches them from the state register by tax number, then generates a merged contract-invoice built to the legal template with the amount written out in words. All of that logic exists exactly once. Two jurisdictions did not mean two backends. They meant one router.

Two modest Postgres instances also cost less per month than one hour of a privacy lawyer’s time. The economics of doing this early are not subtle.

Logs Are Storage Too

The second place personal data goes to die is logging.

In this build, emails and phone numbers are masked in every log channel. Access to a generated document is recorded: who opened it, when. The provenance instinct came from the data half of the same project, where a caching intermediary once stamped a fresh date onto a stale snapshot and published a stale commodity quote against a fresher source. Since then I keep a row for what every source returned, not just for the winner. People data deserves the same discipline as price data: record what happened, who touched it, under which version of which agreement.

Which is why consent has a version here. Each order records the consent audit and the exact revision of the offer the buyer accepted. When the terms change in May and a dispute arrives in November, “which contract applies” is a SELECT, not a meeting.

Erasure Is a Command, Not a Ticket

GDPR Article 17 gives you one month to erase someone; Article 12(3) sets the clock. A month is generous for a command. It is nothing for an archaeology expedition across six services, a data lake, and every screenshot folder a marketer ever created.

Here, erasure on request runs as a single command. One entry point that knows every table a person can appear in, instead of a checklist in a wiki that was accurate in March. The residency split makes this tractable: there is exactly one store where the person exists, the analytics spine holds only the opaque id, and the logs were never allowed to hold the email in the first place. Deletion is short because the data was never scattered.

Money follows the same one-way discipline. Order state is a state machine that moves in one direction, the payment webhook is idempotent so a retried callback cannot double-fulfil, and a verification failure returns an error the gateway retries rather than a success that quietly loses the payment.

One more boundary follows the same logic: price. Every plan and price the client sends is recomputed on the server, never trusted, because a price rule that lives in the browser is a suggestion. Same boundary, same principle. The server enforces; the interface decorates.

Mapping It Onto the UAE

I live in Abu Dhabi now, and the local version of this problem is stricter than the one I originally built for. The UAE runs three data protection regimes side by side: Federal Decree-Law No. 45 of 2021, the PDPL, in force since 2 January 2022 for the mainland; the DIFC Data Protection Law No. 5 of 2020; and the ADGM Data Protection Regulations 2021 for the Abu Dhabi financial free zone. A platform onboarding a DIFC fund and a Sharjah retailer answers to two different regulators inside one country.

The spine survives contact with that. Add a store, add a jurisdiction key to the router. Nothing else moves.

What I do not know yet, honestly: I have not taken this architecture through a PDPL audit, and the shape of cross-border adequacy decisions under the executive regulations is a question I cannot answer from the law’s text alone. What I have run in production is a GDPR-grade erasure and audit story on a two-store spine, and its full price was a routing decision plus a masking layer, paid early.

The region is not waiting. Saudi Arabia’s PDPL finished its enforcement grace period in September 2024. Qatar, Oman and Bahrain each have their own acts. One by one, “where exactly does this row live” stops being an infrastructure preference and becomes a legal answer. Cheap to give at write time. Brutal to retrofit at audit time.

✱