02 / system · Das System darunter
Multi-Tenant-SaaS-Entwickler: Terminplanung, Buchungen, Zahlungen und Rollen
Die Hälfte Ihres Produkts, die niemand sieht, bis sie bricht. NestJS und PostgreSQL, wo die Datenbank die Regeln durchsetzt, statt Ihrem Code zu vertrauen, dass er sie sich merkt.
- Drei Multi-Tenant-Produkte in Produktion
- Fahrschulen, Yachtcharter, Auslandsstudium
- Alleiniger Entwickler bei Gradsy: Schema, API, Deploy

was ich baue
Die Hälfte des Produkts, die stimmen muss.
Ein Deployment, viele zahlende Kunden, und Regeln, die die Datenbank sich weigert zu brechen. Nichts davon ist sichtbar — bis zu dem Tag, an dem es das ist.
SaaS-Plattformen
ein Produkt, viele zahlende Kunden, getrennt gehalten und getrennt abgerechnet
Multi-Tenant-Anwendungen
ein Deployment, viele Organisationen, und keine Daten, die zwischen ihnen wandern
Buchungssysteme
zwei Personen können nicht denselben Slot nehmen, und die Datenbank ist es, die das verhindert
APIs
Endpoints, die Ihr eigenes Frontend und der Client eines anderen gleichermaßen aufrufen
Auth und Rollen
wer was darf, an einer Stelle am Rand des Systems geprüft
Zahlungen und Abrechnung
Abonnements, Rechnungen, und Webhooks, die zweimal ankommen
Background-Jobs
die Arbeit, die um 3 Uhr nachts läuft, so geschrieben, dass ein zweiter Lauf nichts tut
Drittanbieter-Integrationen
die API eines anderen, wiederholt durch die Stunden, in denen sie weg ist

wie es abläuft
Rahmenbedingung, Schema, API, Betrieb.
Ich frage nach der Rahmenbedingung, bevor ich frage, was gebaut werden soll. Die Antwort verändert das Schema, und das Schema ist das Teure, das man später ändert.
begrenzen
was niemals passieren darf — eine Doppelbuchung, ein vermischter Mandant, eine zweimal belastete Karte
modellieren
das Schema, das genau das unmöglich macht, geschrieben bevor ein einziger Endpoint existiert
bauen
NestJS und Node auf PostgreSQL, Berechtigungen an einer Kante geprüft, Jobs sicher bei zweitem Lauf
betreiben
auf AWS deployed, samt der Pipeline, die es dorthin bringt — Ausliefern ist ein Dienstag, kein Ereignis

warum postgres
Eine Regel gehört in die Datenbank, wenn die Datenbank sie halten kann.
Anwendungscode prüft eine Regel. Eine Datenbank setzt sie durch. Der Unterschied zeigt sich an dem Tag, an dem zwei Leute in derselben Sekunde denselben Button klicken.
- ein Unique Constraint
- zwei Personen buchen denselben Slot. Der zweite Schreibvorgang schlägt fehl, und dieser Person wird gesagt, dass er weg ist.
- Zustände in einer Tabelle
- die Übergänge, die eine Bewerbung machen darf, sind Zeilen, die man lesen kann — keine if-Kette über drei Services verteilt.
- Jobs, die sich gefahrlos wiederholen
- eine Queue liefert mindestens einmal, was manchmal zweimal heißt. Einmal belasten, einmal mailen, einmal zählen.
- Mandantentrennung im Schema
- die Subdomain begrenzt die Query, damit nie ein if-Statement das ist, was zwei Kunden auseinanderhält.
- ein Ort für Berechtigungen
- wer das darf, ist eine Frage, die Sie durch Öffnen einer Datei beantworten.
- Migrationen, die rückwärts laufen
- ein schlechtes Release rollt so zurück, wie es hinausging, statt zu einem Incident zu werden.
die probleme, die ich löse
Einen dieser Sätze haben Sie wahrscheinlich schon laut gesagt.

“Wir haben an ein zweites Unternehmen verkauft, und jetzt entscheidet ein if-Statement, wessen Daten zurückkommen.”
Das funktioniert — bis jemand das if vergisst. Ich verlege die Trennung ins Schema, wo Vergessen keine Option ist, die der Code hat.
“Zwei Kunden haben denselben Slot gebucht. Wir haben einem erstattet, und niemand weiß, wie es passiert ist.”
Ihre API hat den Slot gelesen und dann geschrieben. Die zweite Buchung kam zwischen diese beiden Schritte. Ein Unique Constraint schließt die Lücke; keine noch so gründliche Prüfung im Code tut das.
“Der Zahlungsanbieter hat denselben Webhook zweimal geschickt, und wir haben zweimal abgebucht.”
At-least-once ist die Funktionsweise jeder Queue und jedes Webhooks. Jeder Job, den ich schreibe, tut beim zweiten Lauf nichts.
“Niemand im Team will die langweilige Hälfte übernehmen.”
Die Berechtigungen, die Sonderfälle der Abrechnung, der Job um 3 Uhr nachts, der stimmen muss. Das ist die Hälfte, die ich nehme.
“Eine Person versteht dieses System, und nichts davon ist aufgeschrieben.”
Ich dokumentiere beim Bauen — das Schema, die Rollen, die Jobs und die Gründe hinter den unbequemen Entscheidungen. Ich war diese eine Person, und das ist ein schlechter Ort, ein Team zurückzulassen.
- NestJS
- Node.js
- PostgreSQL
- Prisma
- Redis + BullMQ
- AWS
Zuerst das Schema, dann die API, dann die Oberfläche. Die Screens darüber baue ich mit, damit es keine Naht gibt, an der meine Arbeit aufhört und die von jemand anderem anfangen soll.
how I build it
A rule belongs in the database if the database can hold it.
Two counselors open the same appointment slot and both click book. Postgres lets both of them read that slot before either one saves. Both see it free. Both take it. No amount of checking in your API code closes that gap, because the gap sits between the read and the write, and it is as wide as your traffic is heavy. A unique constraint on the table does close it. The second write fails, and the second person is told the slot has gone.
State works the same way. An application that moves through stages gets a table of states and the moves allowed between them, not a text column and a pile of if statements spread across three services. Then "can this go from submitted to accepted" is a row you can look at instead of a code review you have to run.
Queues promise to deliver a message at least once. In practice that sometimes means twice. So every background job is written to do nothing on the second run. Charge once. Email once. Count once.
Schema first, then the API, then the interface. Migrations run backwards as well as forwards. Permissions are checked in one place at the edge of the system, so "who is allowed to do this" is a question you answer by opening one file.
when a project needs more than me
I deploy and run the infrastructure most products need. Some projects need more than that: dedicated platform work, heavy data pipelines, an environment with real operational demands. For those I bring in senior infrastructure engineers I already work with.
The database and the application stay with me. The platform goes to somebody who does it full time. You are still dealing with one arrangement rather than three, and one person answerable for how it turns out.
wie die zusammenarbeit aussieht
Sie bringen es. Ich übernehme. Sie bekommen es besser zurück.
you bring
- ein Produkt mit einem zweiten Kunden, oder ein System, das einmal gebrochen ist
- die Regel, die niemals verletzt werden darf
- wer auch immer das Support-Ticket beantwortet, wenn sie es doch wird
I handle
- Schema-Design, und Migrationen, die in beide Richtungen laufen
- Multi-Tenant-Isolation und rollenbasierter Zugriff
- APIs, die Ihr Frontend und der Client eines anderen gleichermaßen aufrufen
- Queues, Background-Jobs und Wiederholungssicherheit
- Zahlungen, Rechnungen und doppelte Webhooks
- Deployment und die Pipeline, die es erledigt
you get
- Ihr Code, in Ihrem Repository, ab dem ersten Commit
- Dokumentation, die beim Bauen entsteht statt für das Ende versprochen zu werden
- ein System, in dem die Regeln von der Datenbank durchgesetzt und nicht von Menschen erinnert werden

Etwas in Ihrem System, das niemals brechen darf?
Dreißig Minuten über das, was Sie betreiben, und darüber, was damit schiefgegangen ist. Ich sage Ihnen, wohin die Regel gehört und ob ich der Richtige bin, sie dorthin zu bringen.
