Zum Inhalt springen
digoCockpit.

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.

BeitragBedeutung
DatenmodellEigene Tabellen in einem eigenen Schema, mit eigener Migration.
Adressen und OberflächeEigene Seiten und eigene Vorlagen, gerendert in die gemeinsame Hülle.
NavigationseintragEin Punkt im Menü, mehr nicht.
EreignisseWas es meldet und worauf es hört.
DokumentationTechnisches Handbuch und Benutzerhilfe, beides Bedingung für die Fertigstellung.
DatenauskunftEine 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

SchichtWahl
AnwendungPython mit FastAPI und SQLAlchemy, Datenbankschema versioniert über Alembic.
DatenhaltungPostgreSQL, ein Datenbestand, je Modul ein eigenes Schema neben dem Kern.
OberflächeServerseitig gerendert mit Jinja2 und htmx in einer gemeinsamen Hülle. Einzelne aufwendige Bildschirme sind Inseln, keine durchgehende Einzelseiten-Anwendung.
DokumenteHTML und CSS nach PDF über WeasyPrint, mit derselben Token-Schicht wie die Oberfläche.
AnmeldungOpenID Connect gegen den eigenen Anbieter, im Betrieb Authentik.
BankFinTS über python-fints, dazu Dateiimport für MT940 und CAMT.
BetriebDocker mit getrennten Diensten für Anwendung, Datenbank und Bankzugriff.