zurück zum Artikel

Vom Softwaremonolithen zu Microservices: Systemumstellung am Praxisbeispiel

Hendrik Gebhardt, Dirk Ziegener

Wie gelingt die Umstellung des Systems vom Monolithen zu Microservices? Die Personalvermittlung Studitemps aus Köln berichtet aus eigenen Erfahrungen.

Aus der komplexen und unübersichtlichen Monolith-Software bei Studitemps sollte eine Microservice-Landschaft mit fachlich voneinander abgegrenzten Applikationen entstehen – ein Projekt, das ebenso viele Hürden wie Learnings brachte.

Um die Ausgangslage besser zu verstehen, sei kurz auf die Geschichte von Studitemps hingewiesen: Bei der Gründung 2008 lag das Geschäftsmodell von Studitemps noch auf Stellenanzeigen, die auf der eigenen Studentenjob-Plattform Jobmensa verkauft wurden. Neben dem Online-Portal auf Basis von Ruby on Rails entstand dafür eine Python-basierte Backend-Software, über die sich die Kunden und Anzeigen verwalten ließen. Das System bekam intern den Namen KISS (Kunden-Informations-Super-System).

2011 stellte Studitemps den Fokus des Geschäfts um: Das Unternehmen wuchs zu einem Personaldienstleister für Studenten heran – auf der Basis von Zeitarbeit, was andere Anforderungen an die Softwarelandschaft stellte. Anstatt Anzeigenkunden zu verwalten, waren nun plötzlich Kundenaufträge, Arbeitnehmer, Einsatzplanung, Arbeitsverträge, Rechnungen und Lohnauszahlungen abzubilden. Das KISS wurde mit der Zeit zu einem schnell wachsenden Unternehmens-ERP (kurz für Enterprise Resource Planning). KISS entwickelte sich zu einem unübersichtlichen Softwaremonolithen und wurde zunehmend komplexer zu betreiben, zu warten und weiterzuentwickeln. Weil die Testabdeckung unvollständig blieb, wurde es zunehmend schwieriger, neue Features auszuliefern oder Fehlerfälle nachzustellen und zu beheben.

Ein Nachfolger für KISS musste her. Um das Tagesgeschäft im Unternehmen fortsetzen zu können, sollte der Monolith nicht sukzessiv umgebaut, sondern parallel zum laufenden Betrieb eine Microservice-Struktur geschaffen werden. Das Ziel war eine Neuentwicklung von Anwendungen mit klar definierten Abgrenzungen und die schrittweise Etablierung neuer Anwendungen. Diese Parallelentwicklung machte es möglich, Ideen und neue Vorgehensweisen immer wieder zu überdenken. Auf Abwärtskompatibilitäten im Prozess musste man keine Rücksicht nehmen.

Im Rahmen der Neuentwicklung wurden Prozesse und Funktionen auf verschiedene Anwendungen und Teams aufgeteilt, um zukünftig schneller auf Änderungen reagieren zu können. Das neue System, das aus unterschiedlichen und fachlich klar voneinander abgegrenzten Services besteht, heißt "Studitemps Works". Die verschiedenen Anwendungen und Services werden separat voneinander betrieben und können auf den unterschiedlichen Programmiersprachen und Frameworks basieren – je nach fachlicher und technologischer Problemstellung.

Dieser Schritt weg vom Monolithen hin zu Microservices war der Grundstein für einen Neustart mit aktuellen Softwarekomponenten und eine fundierte technologische Basis. Die klare (fachliche) Abtrennung der Applikationen bedeutet für die Entwickler Arbeitserleichterung: Der Problem- und somit auch Lösungsraum wird dadurch eingegrenzt und ist nicht immer auf ein riesiges System weiterzudenken. Ganz lässt sich KISS aber noch nicht ablösen, vor allem weil es Überschneidungen im Prozessablauf gibt und manche Funktionen in den neuen Anwendungen in überarbeiteter Form implementiert wurden. Für diese Fälle waren Features zu deaktivieren und Austauschmöglichkeiten für Daten zu schaffen. Augenmerk lag hier auf einer einfachen und schnellen Integration im Bestandssystem.

Aus diesem Grund kamen vorwiegend Programmierschnittstellen (APIs) mit JSON oder Dateiexporte- und -importe wie CSV zum Einsatz, die im weiteren Produktlebenszyklus durch eine asynchrone event-basierte Kommunikation abgelöst wurden. Das gelingt, wenn bereits von Beginn an in den Anwendungen ein Fokus auf solch eine Architektur gelegt wird. So kann auch ein legacy CSV-Import lokal die geeigneten Funktionen auslösen, die für die neue Architektur konzipiert wurden. Diese temporäre Integration schlägt die Brücke zwischen alter und neuer Architektur.

Vor der Umstellung kommt die Analyse. Die alte Anwendung, das KISS, war einmal gedanklich zu zerlegen, und zwar in die fachlichen Unternehmenskomponenten und in die technologischen Aspekte dahinter. Datenströme sowie Abhängigkeiten innerhalb der Anwendung und deren Flussrichtungen von und zu anderen Anwendungen waren ein Kern dieser technischen Analyse. In einer Systemlandkarte (s. Abb. 1) wurden alle Anwendungen und deren Bestandteile gesammelt und dadurch Abhängigkeiten aufgedeckt. Die Karte ermöglicht das Darstellen von Datenproduzenten, -konsumenten und -transport.

Systemlandschaft mit Abhängigkeiten aus der Sicht von Jobmensa (Abb. 1)

Eine konsumierende Anwendung lässt sich dabei in der Regel einfacher herauslösen als eine produzierende, da kein Datenfluss über Anwendungs- und damit meistens über Teamebene zu kommunizieren ist. Insbesondere bei gewachsenen Anwendungen kann die Art des Datentransports über die Zeit, je nach Stand der Technik und des Wissenstandes im Team, auf unterschiedliche Weise implementiert sein und sollte bei der Betrachtung mit einbezogen werden. In diesem Fall hilft Domain-driven Design (DDD) und insbesondere die fachlich abgrenzbaren Kontexte (in DDD: Bounded Contexts), um die Software-, System- und Team-Landschaft aufzuteilen. Dabei erkennt man schnell, welche Anwendungen und Endpunkte eine zentrale Rolle spielen. Weiterhin wird ersichtlich, welche Endpunkte abgelöst werden können und ob die Art der Serialisierung homogener und zukunftssicher gestaltet werden kann.

Da Studitemps in der Softwareentwicklung als Vorgehensmodell Domain-driven Design (DDD) etabliert hat, setzt die Entwicklung in der Kommunikation der Anwendungen, und auch innerhalb einer Anwendung, auf Domain Events. Das sind fachliche Ereignisse, die Aktionen im Domänenmodell beschreiben.

Studitemps tauscht Domain Events oftmals nicht nur zwischen den Domänen aus. Sie dienen auch der Kommunikation zwischen den Anwendungen der verschiedenen Scrum-Teams.Die Veröffentlichung der (serialisierten) Domain Events geschieht per RabbitMQ als Message Broker zum Nachrichtenaustausch über fest definierte Exchanges. Im Anschluss lassen sie sich von den verschiedenen Anbindungen (eigenständig) über angebundene Queues konsumieren. Zu Dokumentationszwecken hat sich ein Git-Repository durchgesetzt, das die Projektbeteiligten mit Beispielen zu serialisierten Domain Events und Dokumentationen anhand von Markdown-Dateien füllen. Über Pull-Request lassen sich neue Vorschläge einreichen und kommentieren. In den meisten Fällen hat sich eine teamübergreifende Code-Review etabliert. Den ersten Entwurf des Domain Event legt das Team des produzierenden Systems an, und die Teams der konsumierenden Anwendungen beteiligen sich an der Diskussion. Der rege Austausch räumt frühzeitig Missverständnisse aus dem Weg und sorgt für einen stabilen Stand im Repository, der enorm wichtig ist, da die JSON-Dateien gleichzeitig als Test-Fixtures zum Einsatz kommen.

{
  "@type": [
   "https://studitemps.tech/specification/auftragszuordnung/arbeitszeit-zu-auftrag-zugeordnet",
    "https://studitemps.tech/specification/domain-event"
  ],
  "@id": "tech.studitemps:auftragszuordnung:arbeitszeit-zu-auftrag-zugeordnet:e7f4e458-3da6-427d-88e3-227bbad22062",
  "caused_by": "tech.studitemps:studentenzeiterfassung:urspung-foo-bar:e52c1d72-77e4-4813-9e6b-632cc454c86f",
  "correlates_with": "tech.studitemps:studentenzeiterfassung:gemeinsam-erstelltes-event-foo-bar:aaaa6b0f-d852-4a24-9e99-d254b16fdb85",
  "enacted_by": "tech.studitemps:studentenverwaltung:student:3df9132d-0732-4ed9-b380-0d8513f59804",
  "occurred_at": "2020-05-13T10:00:00+02:00",
  "dokumentiert_arbeitszeit": {
    "@id": "tech.studitemps:studentenzeiterfassung:arbeitszeit:f4715430-70b8-0136-1580-54e1ad118e9f",
    "fand_im_auftrag_statt": "tech.studitemps:auftragserstellung:auftrag:12345"
  }

Da die Anwendungen in der Regel eine PostgreSQL-Datenbank verwenden, werden die Domain Events derzeit auch jeweils in dieser abgelegt (Event Store).

Nachteil an diesem Ansatz ist, dass entweder APIs (z. B. eine RESTful HTTP API) zur Verfügung gestellt oder die Domain Events weitere Male per RabbitMQ veröffentlicht werden müssen, wenn eine Anwendung diese konsumieren möchte, aber die Events bereits publiziert sind. Das kann ein gewünschtes Verhalten sein, wenn eine neue Anwendung in die Systemlandschaft integriert wird oder in einem Fehlerfall keine korrekte Abarbeitung gewährleistet war. Ein schneller Erkenntnisgewinn setzt bei der Wichtigkeit der einmaligen Abarbeitung von Domain Events ein. Das ist besonders bei einer wiederholten Veröffentlichung der Fall. Wenn ein angebundenes System als Ergebnis beispielsweise eine E-Mail verschickt, dann würde es diese jedes Mal versenden, wenn das eindeutige Domain Event wiederholt eingespielt wird.

Um eine strukturierte Datenlage sicherzustellen, die in der späteren Weiterentwicklung und Anbindung von Anwendungen enorme Wichtigkeit besitzt, hat das Projektteam bei der Ablösung der Legacy-Software am Anfang der Prozesskette angesetzt. Dieser Schritt stellte die schnelle Verfügbarkeit prozesskritischer Anwendungen sicher, die einen abgesteckten Funktionsumfang bieten und von den aufbereiteten Daten profitieren. Wie erwähnt, wurden einige Komponenten zu Beginn der Neuausrichtung sowohl an die Legacy-Software als auch an die neue Systemlandschaft angebunden. Das bot den Vorteil, dass sich der Betrieb nahtlos fortführen ließ und weiterhin die Neuentwicklung schon im produktiven Einsatz erprobt wurde.

Die Integration der Anwendungen untereinander setzt das Projektteam durch einfache Verlinkungen mit einem zentralen Login um. Damit entfallen aufwendige Integrationsaufgaben und Abstimmungen über die Teams hinweg. Die Benutzer der Software bekommen in den meisten Fällen nur wenig von einem Wechsel der Anwendungen mit. Dafür bieten die Anwendungen genau auf Benutzergruppen abgestimmte Ansichten, da das jeweilige Team sich auf die einzelne Fachdomänen konzentrieren kann. Die Teams untereinander können aber weitgehend unabhängig agieren und autark arbeiten.

Der Service-Ansatz reduziert die Komplexität der einzelnen Applikationen, denn anstelle einer großen Anwendung, in der alles miteinander zusammenhängt und verknüpft ist, beschränken sie sich nun auf einen einzigen, fachlich klar abgegrenzten Aufgabenbereich. Die Applikation ist kleiner und damit der Quellcode weniger umfangreich, was ihn verständlicher und leichter testbar macht.

Die Entkopplung der einzelnen Systembestandteile voneinander führt auch zu einer höheren Systemrobustheit: Sollte es zu einem Fehler, zu Performanceproblemen oder gar einem Ausfall einer Anwendung kommen, ist nur ein kleiner Teilbereich des Systems und damit des Unternehmens betroffen. Während ein Monolith in diesem Fall vollständig auszufallen droht, kann man in einem verteilten System weiterarbeiten. Durch die fachliche Trennung der Anwendungen ist im konkreten Fall sogar nur genau eine Fachabteilung von einem Problem betroffen.

Möglich wird die hohe Unabhängigkeit durch das bewusste Zulassen von Datenredundanzen. Anders als in klassischen, relationalen Datensystemen lassen sich Daten in verteilten Systemen als Kopien in den einzelnen Anwendungen speichern. Das erhöht die Unabhängigkeit der einzelnen Systemteile – man muss sich jedoch Gedanken darüber machen, wie Änderungen von Daten über die verschiedenen Anwendungen hinweg verarbeitet werden.

Es zeigt sich, ganz ohne Kopfzerbrechen geht es nicht. Trotz aller Vorteile hat auch diese Architektur ihre Wermutstropfen: Zwar kann man die Komplexität einer Software-Applikation mit diesem Ansatz deutlich verringern, nicht jedoch die des Gesamtsystems. Die vielen, grundsätzlich voneinander unabhängigen Applikationen müssen permanent miteinander kommunizieren und Daten austauschen. Somit verschiebt sich die Komplexität aus der einzelnen Anwendung auf die Kommunikationsebene dazwischen.

Zusammenfassend lässt sich sagen, dass der Weg steinig sein kann. Bei Studitemps hatten die Beteiligten das große Glück, dass alle Fachabteilungen an neuen, besseren Prozessen und Standards mitgearbeitet haben. Die fachliche Expertise ist bei der Softwareentwicklung sicherlich von großem Nutzen und kann passgenau zugeschnittene Anwendungen ermöglichen. Jedoch verschwimmen die Grenzen der Benutzergruppen zunehmend und die aktuelle Entwicklung muss den Änderungen standhalten. In diesem Fall hilft Domain-driven Design (DDD) und insbesondere die Bounded Contexts, um eine klare Abgrenzung zu gewährleisten.

Die Schnitte in der Softwarelandschaft scheinen zu Beginn immer klar und offensichtlich zu sein, doch sobald es tiefer in die Details geht, kann sich der Wunsch nach einer Verschiebung oder weiteren Schnitten ergeben. Der Entwicklungsprozess und die Teams müssen mit den Veränderungen umgehen können und sie aktiv vorantreiben. Andernfalls läuft man schnell Gefahr, den nächsten Monolithen zu entwickeln und die neu erlangten Denkweisen zu vernachlässigen.

Außerdem ist die Lernkurve von DDD nicht zu unterschätzen. Über die Zeit entwickeln verschiedene Scrum-Teams einen unterschiedlich ausgeprägten Kenntnisstand, auf dem sie arbeiten. Die Wissensschere darf nicht zu groß werden, da sonst die teamübergreifenden Zusammenarbeiten leiden und die gesamte abteilungsweite Entwicklung ausgebremst würde. Um den Umständen entgegenzuwirken, muss zum einen genügend Zeit zur Fortbildung der einzelnen Teammitglieder eingeräumt und zum anderen teamübergreifende Gremien installiert werden, die einen Erfahrungsaustausch ermöglichen. Hier haben sich bei Studitemps die Communities of Practices (CoP) etabliert, die einen Rahmen für einen themenbezogenen Austausch unter interessierten Mitarbeitern schaffen. Das kann vor allem zu Beginn eine große Investition sein, die über die Zeit aber schnell wieder eingeholt wird.

Abschließend sei gesagt, dass man die Fachabteilungen in der Entwicklung von Beginn an einbeziehen sollte. Jedes Team muss einen Weg finden, die Stakeholder aus den entsprechenden Abteilungen einzubeziehen. In regelmäßigen Reviews, am Ende eines Scrum-Sprints, lassen sich wertvolle Erkenntnisse gewinnen und das Entwicklerteam kann in Gesprächen viel Hintergrundwissen aufgreifen. Außerdem werden das Vertrauen in die Software und die weitere Zusammenarbeit gestärkt. Eine bedarfsgerechte Softwareentwicklung ist somit jederzeit sichergestellt.

Hendrik Gebhardt
ist Software Engineer bei der Studitemps GmbH und beschäftigt sich seit über 10 Jahren mit verteilten Systemen.

Dirk Ziegener
ist Head Of Development & IT bei Studitemps und beschäftigt sich schon sein halbes Leben mit Software-Entwicklungsteams und der digitalen Produktentwicklung. Dabei wird er nicht müde zwischen den Welten der Softwareentwicklung des Designs der Nutzer und Stakeholder zu vermitteln. Manchmal sogar mit Erfolg.

(mdo [1])


URL dieses Artikels:
https://www.heise.de/-4994196

Links in diesem Artikel:
[1] mailto:mdo@ix.de