Coding - Vom Monolithen zur Service Oriented Architecture

Uff, die Überschrift hat es mal in sich und ist sicher nicht der Aufhänger für die nächste Halloween Party. Heute geht es um unser hauseigenes ERP-System, das wir liebevoll Disco nennen. Über die Jahre hinweg ist unsere Software, die im Übrigen auf einem klassischen LAMP Stack läuft, schon ein klein wenig gewachsen und bietet von der Verwaltung des Online Shops bis hin zur Verzollung von Sendungen ziemlich viele coole Tools.
Das einzige Problem, das sich im Laufe der Zeit ergeben hat, ist, dass die Module stark ineinander integriert wurden und somit das nötige Wissen und die Gefahr von unbeabsichtigten Nebenwirkungen bei Änderungen extrem hoch geworden ist. Aus diesem Grund haben wir den Entschluss gefasst, dass wir den riesigen Monolithen in kleinere Services zerschlagen und die Abhängigkeiten untereinander mit klar definierten Schnittstellen regeln. Wer jetzt 5000 Microservices erwartet, wird leider enttäuscht. Vielmehr sollen die großen Module herausgelöst und als alleinstehende Services lauffähig werden. Als ersten Service lösen wir das Warenlager aus dem Shop-Ökosystem, hierdurch werden viele Prozesse im Lager vereinfacht und der Zugriff auf die Lagerhaltung pro Standort zentralisiert. Dies war schon dringend notwendig, da ein Skalieren auf mehrere Standorte mit der aktuellen Software nur bedingt möglich ist und immer einen enormen Aufwand mit sich bringt. Nach außen hat das NWMS, also das Nice Warehouse Management System, eine API zum Einspielen der unterschiedlichen Aufträge. Hierbei kann ein Lagereingang, Verpackungsauftrag, eine Inventur oder allgemeine Kontrolle eingespielt werden. Darüber hinaus gibt es eine direkte Anbindung zu der Datenbank für die Produktstammdaten und Benutzer. Zurückgespielt werden über Webhooks beziehungsweise Feeds nur noch die Auftragsänderungen und Lagerstände. Somit sind die Interaktionsmöglichkeiten mit der neu geschaffenen Blackbox klar geregelt und alle weiteren Services müssen keinerlei Wissen darüber haben, wie die Lagerverwaltung funktioniert.
Dokumentiert wird die gesamte API direkt im Sourcecode und als Open API Dokumentation beim Deployment extrahiert und publiziert. Die Prozesse im Lager wie picken oder zwischen Bereichen umräumen sind die gleichen geblieben, jedoch wurde der gesamte technische Unterbau an das neue Auftragsschema angepasst. Unser Schnittstellen-Denken führt sogar so weit, dass der neue Service auf dem jeweiligen Standort gar nicht mehr aus unserer Softwareschmiede stammen muss, da für die Integration nur eine geeignete API gebaut werden muss. Technisch sind wir unserem Stack treu geblieben und haben nur eine neue Datenbank mit derzeit drei Tabellen für das Warenlager aufgesetzt. Eine Tabelle beschreibt die Aufträge selbst, die zweite definiert Abhängigkeiten zwischen diesen und eine dritte zeichnet Änderungen bei den Aufträgen auf. Die Lagerpositionen und Warenwirtschaftsdaten sind noch in den bestehenden Tabellen und werden in einem weiteren Schritt herausgelöst. Darüber hinaus haben wir auf einen autoincrement Schlüssel verzichtet und stattdessen eine GUID implementiert. Hierdurch kann jedes Objekt in der gesamten Disco Suite über die Anwendungen hinweg eindeutig identifiziert werden und Kollisionen bei den Schlüsseln sind unmöglich. Die nötigen Daten und das Ergebnis des Auftrages landen in 2 JSON Felder. Auf der einen Seite gibt es die Request-Daten, welche unveränderlich sind und nur beim Anlegen des Auftrages geschrieben werden. Auf der anderen Seite stehen die Result-Daten, welche bei Änderungen im Auftrag durch einen CommandHandler verändert werden. Hierdurch ergibt sich der Vorteil, dass jede Änderung bis ins kleinste Detail getestet, aufgezeichnet und der ursprüngliche Auftrag immer eingesehen werden kann. 

Den aufmerksamen Leser:innen meiner Artikel dürfte der Widerspruch zum Blog “Unser Weg zum Monorep” aufgefallen sein. Ich kann euch beruhigen, auch die neuen Services werden komplett im Monorepository liegen und nur in der Pipeline beim Deployment auseinander dividiert. Der Vorteil, der sich daraus ergibt, liegt klar auf der Hand: Wir können Shared Source Code wie zum Beispiel ein einheitliches MVC System oder einen einheitlichen Datalayer relativ einfach erweitern, ohne deren Abhängigkeiten aus dem Blick zu verlieren. Darüber hinaus können die nötigen Entities und Factories fürs Anlegen der Aufträge direkt im Shared Bereich liegen und müssen daher nicht doppelt oder dreifach gewartet werden. Auch kann die Basisimplementierung der API selbst direkt im geteilten Bereich implementiert sein und nur der konkrete Aufruf und das Zusammenbauen der Aufträge in der jeweiligen Serviceebene geschehen. 

Scrum ermöglicht uns die Umstellung in kleineren Iterationen zu denken und selbst wenn wir nach ein paar Sprints erkennen, dass ein paar Kleinigkeiten nicht optimal sind, können diese locker mit ein paar Refactoring-Tickets wieder gerade gebogen werden. Schwieriger ist hier auch die Arbeit für die Product Owner beim Erstellen der User Stories. Auf der einen Seite müssen die Anforderungen der aktuellen Software exakt erfasst werden und auf der anderen Seite sind aber die Tickets meist technischer Natur. Nicht selten hört man hier den Satz, es soll alles wieder genau so funktionieren wie bisher, nur halt eben anders.

Durch diese Änderung, welche sich sicher über die nächsten Jahre hinziehen wird, ist es uns möglich, das Wissen für einzelne Services zielgerichteter zu verteilen und Änderungen schneller und ohne Querschläger zu entwickeln. Auf jeden Fall wird es eine enorme Herausforderung, unsere Software in die richtige Größe zu zerteilen, sodass die Schnittstellen nicht unnötig kompliziert werden und die logischen Einheiten der Dienste aber erhalten bleiben.

Stay Nice!