Architektur
Ein System mit inneren Grenzen, kein Verbund vieler Dienste.
Alles hier folgt aus einer Frage: Wie kommt ein Modul dazu, 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
Den 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, und Ausgleichsbuchungen sind genau der Zustand, den eine ordnungsmäßige Buchführung nicht haben darf.
Dazu kommt der Betrieb. Der Serverraum ist der eigene, und im Ernstfall steht dort kein Bereitschaftsteam. Ein System, das eine einzelne Person versteht, überwacht und wiederherstellen kann, ist unter dieser Bedingung einer Landschaft aus einem Dutzend Diensten vorzuziehen.
Die Modularität entsteht also 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.
Umgekehrt darf der Kern kein Modul kennen. Wo eine Rechnung im Kern liegen muss, weil die Abrechnung sie braucht, liegt sie eben im Kern.
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 diese Funktion anmeldet, ist zugleich die vollständige Liste dessen, was ein Modul zum System beitragen kann.
| 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 er ist zugleich der, den man am leichtesten vergisst. Ein neues Modul ist dadurch von seinem ersten Tag an auskunfts- und herausgabefähig. Wer die Datenauskunft später über ein gewachsenes System nachrüsten will, hat eine deutlich größere Aufgabe vor sich.
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. Offener Betrag und Zustand sind gespeicherte Ableitungen, die eine einzige Stelle im Programm unter einer Zeilensperre pflegt.
Die Bewegung
Die personenlose Umwidmung, etwa die Zuführung zur Erhaltungsrücklage oder eine Entnahme. Offener Betrag und Fälligkeit fehlen ihr, denn im Moment ihrer Buchung ist sie abgeschlossen.
Die Kontogruppen der beiden überschneiden sich nicht. Keine Auswertung muss also über beide Tabellen hinweg zusammenführen, und in beiden landen kann eine Buchung ohnehin nicht.
Erscheinungsbild
Der Kern ist neutral, das Aussehen wird eingespielt
Im Kern-Stylesheet steht kein einziger Farbwert und kein Schriftname, nur Verweise auf Merkmale. Ein austauschbares Paket belegt diese Merkmale mit Werten und trägt damit das Erscheinungsbild. Oberfläche und PDF benutzen diese eine Schicht gemeinsam, und ein Wechsel wirkt deshalb in beiden Kanälen zugleich.
- 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 sickerte die Marke binnen Monaten wieder in den Kern, und von der Neutralität bliebe eine Behauptung übrig.
- 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. |