Architektur
Ein System mit inneren Grenzen, kein Verbund vieler Dienste.
Die Architektur beantwortet eine einzige Frage: Wie kann ein Modul dazukommen, ohne dass die bestehenden angefasst werden müssen, und ohne dass die Buchführung ihre Verlässlichkeit verliert.
Die Grundentscheidung
Modularer Monolith statt getrennter Dienste
Der Ausschlag gab ein fachlicher Fall, kein technischer: Wenn eine Bankbuchung einen offenen Posten schließt, müssen beide Änderungen gemeinsam gelingen oder gemeinsam scheitern. Über getrennte Dienste hinweg wäre das eine verteilte Transaktion mit Ausgleichsbuchungen, also genau der Zustand, den eine ordnungsmäßige Buchführung nicht haben darf.
Dazu kommt der Betrieb: Ein System im eigenen Serverraum, das eine Person versteht, überwacht und wiederherstellen kann, ist einer Landschaft aus einem Dutzend Diensten vorzuziehen.
Die Modularität entsteht deshalb im Inneren, über drei Regeln, die ausnahmslos gelten:
- Ein Modul verweist auf die Stammdaten des Kerns und kopiert sie nicht.
- In den Kern schreibt ein Modul ausschließlich über das Hauptbuch, mit Angabe seines Namens und seines eigenen Bezugs.
- Module rufen einander nicht auf, sondern melden Ereignisse.
Der Kern darf umgekehrt kein Modul kennen. Wo eine Rechnung im Kern liegen muss, weil die Abrechnung sie braucht, liegt sie im Kern und nicht in einem Modul.
Der Modul-Vertrag
Was ein Modul mitbringt, wenn es sich anmeldet
Ein Modul ist ein Paket, das beim Start eine einzige Funktion ausführt. Was sie anmeldet, ist die vollständige Liste dessen, was ein Modul zum System beiträgt.
| Beitrag | Bedeutung |
|---|---|
| Datenmodell | Eigene Tabellen in einem eigenen Schema, mit eigener Migration. |
| Adressen und Oberfläche | Eigene Seiten und eigene Vorlagen, gerendert in die gemeinsame Hülle. |
| Navigationseintrag | Ein Punkt im Menü, mehr nicht. |
| Ereignisse | Was es meldet und worauf es hört. |
| Dokumentation | Technisches Handbuch und Benutzerhilfe, beides Bedingung für die Fertigstellung. |
| Datenauskunft | Eine Funktion, die zu einer Person oder einem Objekt die eigenen Daten liefert, samt Verzeichnis der personenbezogenen Tabellen. |
Der letzte Punkt ist der wichtigste und der am leichtesten zu vergessende: Ein neues Modul ist damit von seinem ersten Tag an auskunfts- und herausgabefähig, statt dass die Datenauskunft später über ein gewachsenes System nachgerüstet werden müsste.
Das Hauptbuch
Zwei Tabellen mit klarer Arbeitsteilung
Der Posten
Was jemand schuldet oder was wir jemandem schulden, immer gegen eine Person und immer auf einem Sachkonto der Gruppen Einnahme oder Ausgabe. Der offene Betrag und der Zustand sind gespeicherte Ableitungen, gepflegt unter einer Zeilensperre und von genau einer Stelle im Programm.
Die Bewegung
Die personenlose Umwidmung, etwa die Zuführung zur Erhaltungsrücklage oder eine Entnahme. Sie hat keinen offenen Betrag und keine Fälligkeit, denn sie ist im Moment ihrer Buchung abgeschlossen.
Die Kontogruppen der beiden überschneiden sich nicht. Deshalb braucht keine Auswertung eine Zusammenführung über beide Tabellen, und keine Buchung kann versehentlich in beiden landen.
Erscheinungsbild
Der Kern ist neutral, das Aussehen wird eingespielt
Im Kern-Stylesheet steht kein einziger Farbwert und kein Schriftname, nur Verweise auf Merkmale. Das Erscheinungsbild liegt in einem austauschbaren Paket, das diese Merkmale mit Werten belegt. Oberfläche und PDF teilen dieselbe Schicht, weshalb ein Wechsel in beiden Kanälen zugleich wirkt.
- Ein Test prüft die Regel in beide Richtungen: Der Kern darf kein Literal enthalten, und jedes verwendete Merkmal muss im neutralen Paket definiert sein.
- Ohne diese Prüfung sickert die Marke binnen Monaten in den Kern zurück, und neutral wäre nur noch eine Behauptung.
- Der Seitenrahmen eines Dokuments gehört dem Erscheinungsbild, der Inhalt dem Modul. Die Absenderangaben sind Konfiguration und keine Gestaltung.
Technik
Womit gebaut wird
| Schicht | Wahl |
|---|---|
| Anwendung | Python mit FastAPI und SQLAlchemy, Datenbankschema versioniert über Alembic. |
| Datenhaltung | PostgreSQL, ein Datenbestand, je Modul ein eigenes Schema neben dem Kern. |
| Oberfläche | Serverseitig gerendert mit Jinja2 und htmx in einer gemeinsamen Hülle. Einzelne aufwendige Bildschirme sind Inseln, keine durchgehende Einzelseiten-Anwendung. |
| Dokumente | HTML und CSS nach PDF über WeasyPrint, mit derselben Token-Schicht wie die Oberfläche. |
| Anmeldung | OpenID Connect gegen den eigenen Anbieter, im Betrieb Authentik. |
| Bank | FinTS über python-fints, dazu Dateiimport für MT940 und CAMT. |
| Betrieb | Docker mit getrennten Diensten für Anwendung, Datenbank und Bankzugriff. |