Zum Inhalt springen

Architektur-Prinzipien

Drei Prinzipien tragen den Code dieses Systems: Hexagonale Architektur trennt den fachlichen Kern strikt von allem, was außen kommt oder geht, also Datenbanken, externen Diensten und Frameworks. Domain-Driven Design beschreibt, wie der fachliche Kern in Bausteine wie Entitäten, Wertobjekte und Domain-Events aufgeteilt wird, die in der Sprache der Email-Verarbeitung formuliert sind. CQRS trennt schreibende Operationen von lesenden Operationen, sodass jede Aktion ihre eigene Klasse mit genau einer Aufgabe hat. Diese drei Prinzipien greifen ineinander und sorgen gemeinsam dafür, dass sich einzelne Teile austauschen lassen, ohne den Rest des Systems anfassen zu müssen.

Hexagonale Architektur

Ein Email-System nutzt viele externe Dienste gleichzeitig: einen IMAP-Server für den Abruf, ein Sprachmodell für die Analyse, eine Vektordatenbank für die Suche, eine Graphdatenbank für Beziehungen. Wenn der Code für die Geschäftslogik direkt mit dem Code für diese externen Dienste verwoben ist, wird jede Änderung riskant. Ein Wechsel der Datenbank zieht dann Anpassungen an Stellen nach sich, die mit Datenbanken eigentlich nichts zu tun haben.

Die Lösung ist eine strikte Trennung. Der Kern des Systems beschreibt nur die fachlichen Regeln, nicht die Werkzeuge. Welche Datenbank, welches Sprachmodell oder welcher IMAP-Server eingesetzt wird, ist dem Kern egal; er definiert nur, was er von der Außenwelt erwartet. Diese Trennung heißt in der Softwarearchitektur Hexagonale Architektur (en: Hexagonal Architecture), gelegentlich auch Ports und Adapter.

Geschäftslogik (Kern)Regeln, Abläufe, Entscheidungen
Schnittstellen (Ports)Was braucht der Kern?
Implementierungen (Adapter)Wie wird es umgesetzt?
Externe DiensteOllama, Qdrant, ArangoDB, IMAP

Was Ports und Adapter konkret sind

Ein Port ist eine Schnittstelle, die beschreibt, was der Kern von der Außenwelt erwartet, ohne festzulegen, wie diese Erwartung erfüllt wird. Das System definiert acht solcher Ports, jeweils einen für das Sprachmodell, die Vektorsuche, den Graphen, das Embedding, den Email-Versand, die Warteschlange, die Email-Speicherung und die Texterkennung. Am Beispiel des Graphen sagt der Port, dass es eine Funktion zum Synchronisieren von Entitäten geben muss, eine zum Synchronisieren von Relationen und eine zum Traversieren des Graphen. Mehr nicht. Ob dahinter ArangoDB, Neo4j oder eine Textdatei steht, kümmert den Port nicht.

Ein Adapter implementiert einen Port mit einer konkreten Technologie. Der ArangoClient bedient den Graph-Port über HTTP-Aufrufe an ArangoDB; der OllamaClient bedient den LLM-Port über HTTP-Aufrufe an Ollama. Wenn sich die Technologie ändert, wird ein neuer Adapter geschrieben, und der Rest des Systems bleibt unberührt. Genau das ist beim Wechsel des Embedding-Modells sichtbar geworden: nötig waren eine Anpassung der Konfiguration und eine kleine Änderung im Adapter, während die Geschäftslogik, die mit den resultierenden Vektoren arbeitet, unverändert blieb.

Vier Schichten organisieren den Code

Über den Ports und Adaptern liegen vier klar getrennte Schichten, deren Abhängigkeiten immer nach innen zeigen. Die Domain-Schicht enthält die fachlichen Regeln und kennt weder Datenbank noch HTTP. Die Application-Schicht orchestriert die Pipeline und ruft die Ports auf, ohne deren konkrete Implementierung zu kennen. Die Infrastructure-Schicht stellt die Adapter zu den externen Diensten bereit. Die Presentation-Schicht bietet die HTTP-Endpunkte für das Admin-Dashboard. Presentation kennt Application, Application kennt Domain, aber Domain kennt weder Infrastructure noch Presentation.