lars
webmobiledatenbankendevopsarchitektur
hello (at) larskoelpin.de

Die Modelle einer Applikation

December 03, 2022
architecture

Wenn Software im Team entwickelt wird, ist es wichtig strukturiert zu arbeiten, um Komplexität in der Code-Basis zu vermeiden. Ein Aspekt bei der Strukturierung von Code ist die Architektur – diese findet sich in irgendeiner Form in der Codebase wieder. Doch was ist Architektur und inwiefern wird diese abgebildet?

Komplexe Software existiert selten in einem Vakuum und schmiegt sich häufig in ein Gesamtsystem an, um Funktionalitäten umzusetzen, die ein Zusammenspiel verschiedener Systeme und Daten erfordern. Das Gesamtbild, das sich aus diesem Zusammenspiel und Gebilde ergibt, nenne ich Architektur.

Doch was heißt das in der Praxis? Dazu schauen wir uns folgende beispielhafte Architektur an.

Bild 1: Beispiel Architektur
Bild 1: Beispiel Architektur

Das Bild zeigt eine Applikation (in grau) und dessen Abhängigkeiten in Form von Drittsystemen — Die Architektur eines Gesamtsystems.

Konkret existiert in der Mitte des Bildes die eigene Software, um dessen Code-Base es geht. An ihr angrenzend existieren diverse externe Systeme, zumeist mindestens eine Datenbank. Auch andere Systeme wie ein BLOB Storage wie S3, eine Message Queue wie Kafka oder ein Authorization-Server sind nicht selten.

Es existieren also Applikationen (App A und App B), die das eigene System über verschiedene Transportmechanismen mit Daten beliefern. Zwischen Systemen entsteht also eine Konsumenten-Produzenten Beziehung, die über eine Schnittstelle kommunizieren.

Outbound-Modell

Bei einer Schnittstelle (API) zwischen Systemen gibt es 3 Aspekte:

  • Serialisierungsformat: In welchem Format werden Daten technisch über die Leitung transferiert (z.B. JSON)
  • Protokoll: Über welches Protokoll werden Daten übertragen (z.B. HTTP)
  • Konzeptionelles Modell: Welche Daten sind überhaupt enthalten?

Im Bild wird die Applikation A von der eigenen Applikation angesprochen. Die Applikation könnte dabei beispielsweise die Swapi sein. Dabei HTTP ist das Protokoll und JSON das Serialisierungsformat. Das konzeptionelle Modell sind dabei die Daten, die aus dem JSON extrahiert werden. In dem Falle für /people/1 also:

"name": "Luke Skywalker",
"height": "172",
"mass": "77",
...

Dieses Modell wird in der Regel in irgendeiner Form in die Applikation übernommen. D.h. im konkreten, dass ein JSON-Format beispielsweise in eine Java-Klasse zur Weiterverarbeitung geparst wird.

class SwapiPerson {
  private String name;
  private String height;
  private String mass;
  private String homeworld;

}

Dieses Modell nenne ich das Outbound-Modell. Es bildet den ausgehenden Datenverkehr mit externen Systemen ab. Das spannende ist, wie das konzeptionelle Modell aussieht, liegt nicht in der eigenen Hand. In diesem Modell können sich Datenform und Inhalt jederzeit ändern, es existieren so gut wie keine Versprechen.

Entscheidet der Macher von SWAPI die homeworld wird jetzt eingebettet, statt REST-ful verlinkt? Dann ist unsere Applikation jetzt gebrochen. Basiert dazu noch die Gesamte Logik innerhalb des Codes darauf, dass homeworld ein String, genauer eine URL, ist? Dann steht ein großes Refactoring an.

Es ist deshalb für komplexe Systeme zielführend dieses Modell vom eigenen zu trennen, um das was in der eigenen Hand liegt, von dem zu trennen, was nicht in der eignen Hand liegt. Um die Applikationslogik soweit es geht von den konkreten externen Daten zu schützen.

Ein Spezialfall des Outbound-Modells ist das Modell der Datenbank. Die Datenbank ist streng genommen ein externes System, das die Art des Schemas (=welche Daten sind enthalten) jederzeit ändern kann. Es kann beispielsweise passieren, dass Spalten durch SQL-Skripte umbenannt werden, ohne, dass die Applikation neu deployed wird. In modernen Applikationen, z.B. auf Spring-Boot Basis, ist es jedoch meistens so, dass das Modell der Datenbank und dessen Migrations-Skripte Teil der Applikation selbst sind. Wird eine neue Version der Applikation deployed, wird das Schema der Applikation migriert. Das heißt im Konkreten, dass das Modell der Datenbank häufig einen größeren Stellenwert in der Applikation, den des Domain-Modells, übernehmen kann. Doch dazu später mehr.

Ein Outbound-Modell bildet das konzeptionelle Modell einer Schnittstelle eines externen Systemes ab. Instanzen des Outbound Modell werden dabei von der eigenen Applikation aktiv aus den eigenen Daten erzeugt.

Inbound Modell

Eine Applikation bietet, meist im Kontext von Single-Page-Applikations, auch eine Schnittstelle an. Diese Schnittstelle wird von anderen Systemen, z.B. durch das HTTP-Protokoll, konsumiert.

Um diese Schnittstelle abzubilden gibt es ein Modell — Das Inbound-Modell. Das Inbound-Modell ist im Gegensatz zum Outbound-Modell passiv. Andere Applikationen müssen, wenn sie die Schnittstelle der eigenen Applikation nutzen möchten in das eigene Inbound-Modell übersetzen. Das heißt aber auch, dass dieses Modell immer stabil bleiben sollte, wenn andere Applikationen nicht gebrochen werden sollten.

Was für die eigene Applikation das Inbound-Modell ist, ist für jegliche andere Applikation ein Outbound-Modell.

In der Praxis kann das Inbound-Modell von Spezifikationen (z.B.OpenAPI) generiert werden. Aus dieser können mithilfe Code Generatoren z.B. entsprechende Java-Klassen generiert werden können. Doch auch andere Serialisierungsformate wie Protobuf, können ein Inbound Modell beschreiben, das ein natives Inbound-Modell der jeweiligen Sprache übersetzt werden kann.

Ein Beispiel für ein konzeptionelles Inbound Modell kann beispielsweise ein CreateUserRequestDto oder ein UserPageDtosein.

class CreateUserRequestDto {
  private String wantedUsername;
}

Wichtig ist zu verstehen, dass Inbound und Outbound-Modelle jeweils Read- und Write-Aspekte haben. Ein Read-Aspekt ist ein Modell, aus dem Daten extrahiert werden, also lediglich gelesen wird, während bei einem Write-Aspekt das Überführen von vorhandenen Daten in das Modell relevant ist.

Ein Inbound-Modell kann sowohl ein CreateNewUser (Write) als auch ein AllUsersPage (Read) Modell enthalten, während ein Outbound-Modell ein “SwapiRequest” (Write) und “SwapiPerson”(Read)-Modell haben kann.

Sowohl das Outbound- als auch das Inbound-Modell haben ein Problem: Die Daten die in Instanzen dieses Modells stammen, sind absolut nicht vertrauenswürdig. Wer verspricht uns, dass in dem Feld wantedUsername überhaupt ein Nutzername drinsteckt? Warum kann dort kein "", z=123.50 oder gar ' DROP DATABASE users; drinstehen?

Es fehlt deshalb eine dritte Art von Modell, die genau diese Versprechen gibt, sodass Daten, die in die Applikation eingepflegt werden auch das enthalten, was wir als Entwickler erwarten. Dieses dritte Modell beschützt uns Entwickler vor teuren Datenmigrationen und kann Komplexitität aus dem Code nehmen, da die Modelle nicht ständig validiert werden müssen. Dieses Dritte Modell ist der Dreh- und Angelpunkt der anderen beiden Modelle — Denn sowohl Inbound- als auch Outbound-Modell werden immer in dieses übersetzt.

Domain Modell

Dieses Modell ist das, was viele als Domain-Modell bezeichnen. Das Ziel des Domain-Modells ist es, alle Versprechen an die Daten sicherzustellen und als Schnittstelle zu den anderen Modellen zu dienen.

Dieses Modell enthält die Validierungsregeln der Applikationen. Anders als beim Inbound-Modell sollten invalide Zustände der Applikation in diesem Modell niemals erreicht werden. Dazu können verschiedene Konzepte — bekannt aus dem Domain Driven Design — angewandt werden. Das Domain Modell gilt als zentrale Schnittstelle zwischen den beiden anderen Modellen, da hier die Logik zur Validierung umgesetzt ist — diesem Modell und dessen Daten sollte jederzeit vertraut werden können.

Bild 2: Inbound Modell Architektur
Bild 2: Inbound Modell Architektur

Gilt in der Applikation die Regel, dass ein Nutzername einzigartig ist? Dann sollte ein Nutzer der den doppelten Namen erhalten soll niemals erzeugt werden können. Sieht ein Feld vor, dass eine Email-Adresse drin steckt? Dann sollte jederzeit (z.B: durch Value Objekte) sichergestellt werden, dass zumindest ein @-Zeichen vorhanden ist-

Beispiel

Ein CreateUserRequest (Inbound) kommt in der Applikation ein.

Bild 3: Outbound Modell
Bild 3: Outbound Modell

Dieser wird in einen konkreten User (Domain) übersetzt. Beim Übersetzen wird überprüft:

  • Ist der Nutzername einzigartig
  • besteht der Nutzername aus validen Zeichen
  • Ist eine Email-Adresse im korrekten Format?

Wurden alle Validierungen ausgeführt, wird der User in einen PostgresUser (Outbound-Modell) übersetzt. Der PostgresUser enthält die Information, welche Attribute des User auf welche Spalte in der Datenbank abbilden. Der PostgresUser ist genau das Modell, das im Falle von ORM “aus den Annotationen virtuell erzeugt wird”. Wird kein ORM verwendet, werden können Attribute und Operationen in diesem Objekt beispielsweise in SQL übersetzt werden (“Active Record”). Nach dem Speichern, kann im Domain Modell weiter mit einem User gearbeitet werden. Ist die Verarbeitung vorüber kann der erstellte User in dieser in das Inbound-Modell übersetzt werden, um die letztliche Antwort vorzubereiten. Im Falle diesem Falle ist das beispielsweise eine HATEOSUserPage der verschiedene Links zu Operationen für eine RESTful API enthält.

Fazit

Es existieren in Applikationen also 3-Modelle: das Outbound-, das Inbound- und das Domain-Modell. Diese Modelle können explizit in der Software, als auch implizit modelliert werden. Häufig lassen sich Outbound-Modelle auch als ein Domain-Modell betrachten. Das bekannteste Beispiel, wo ein Outbound-Modell zum Domain Modell werden kann, ist durch das Nutzen von ORM. In der Praxis sind die werden Applikationen heutzutage mit SQL-Migrationen für relationale Datenbanken deployed. Das Deployment enthält also die Definition für das Outbound-Modell (In Form von SQL Skripten).