In unserem Projekt geht es um die automatisierte Rechnungstellung auf Basis der vom Kunden genutzten und von unserem Auftraggeber bereitgestellten Infrastruktur.

Das klingt erst einmal nicht so spannend und herausfordernd, denn die Antwort scheint simpel: Man multipliziere die Dauer der Nutzung mit einem Preis X pro Zeiteinheit und bringt dies schließlich auf (digitales) Papier. Doch was ist, wenn es unterschiedliche Arten der Nutzung gibt?

Das war jetzt sehr abstrakt. Um sich unser Aufgabenfeld besser vorstellen zu können, gehen wir es an einem bildhaften Beispiel durch: Wir stellen uns vor, wir sind in einer Spielhalle, in der eine Autorennbahn aufgebaut ist. Doch die Warteschlange ist lang und nicht alle können gleichzeitig spielen. Wenn ich mir schließlich einen Spiel-Zeitslot ergattert habe, dann aber doch nicht spiele oder zu lange, sind meine Mitspieler mit großer Wahrscheinlichkeit sauer und verlangen einen Ausgleich. Unter Umständen gibt es zusätzlich noch eine weitere Rennbahn. Die ist jedoch nicht so spannend wie die erste, weil die Autos nicht so schnell darauf fahren können. Wenn ich mich trotzdem dazu entscheide auf die zweite Rennbahn auszuweichen, habe ich aber möglicherweise sogar einen Vorteil, muss zum Beispiel weniger zahlen. Die besonders Kreativen kommen vielleicht auch auf die Idee, dass Loopings oder Schikanen fahren mehr kosten könnte als eine langweilige Geradeausfahrt. Außerdem könnte zusätzlich noch zwischen langsamen und schnellen Fahrzeugen unterschieden werden. Und um Streitereien vorzubeugen, kommt zudem noch ein elektronischer Rundenzähler sowie eine Stoppuhr zum Einsatz. Dieses Beispiel soll anschaulich erklären, wie komplex eine zu Beginn simpel scheinende Aufgabenstellung sein kann. Neben der reinen Nutzungsdauer müssen also Daten zur Infrastruktur, Verspätungen, Bestellungen oder Störungen berücksichtigt werden, bevor die endgültige Abrechnung erfolgen kann. Dies geschieht über verschiedene, speziell dafür entworfene Systeme. Rechnungen könnten außerdem von anderen Fachbereichen geprüft werden – müssen im schlimmsten Fall sogar korrigiert werden.

Nur in einer idealen IT-Welt würden uns all diese einzelnen Systeme und Fachbereiche ihre entsprechenden Daten über moderne Schnittstellen liefern. Viele Informationen werden uns in einzelnen Dateien zur Verfügung gestellt, wobei die Herausforderung nicht nur ist, dass es sich um einen bunten Strauß an Formaten handelt, sondern auch, dass all diese Datensätze fachlich passend zusammengefügt werden müssen.

Mit welchen Programmen und Hilfsmitteln setzen wir dies nun aber konkret um?

Da wir in einem DevOps Umfeld arbeiten, begleiten wir das Projekt vom technischen Design über die Implementierung bis zum produktiven Betrieb. Doch die Welt der IT dreht sich schnell, so dass wir immer wieder bereits getroffene Entscheidungen hinsichtlich zuvor ausgewählter Tools herausfordern und Alternativen auf dem Markt prüfen. Kleine PoCs helfen uns dabei einen ersten Einblick in die Stärken und Schwächen verschiedener Programme zu bekommen, um zusammen mit unseren Kunden die bestmöglichen Entscheidungen für das Projekt treffen zu können.

Hier möchten wir nun aber gerne eine Momentaufnahme aus unserem Projekt zeigen:

Im produktiven Betrieb unseres Projektes laufen jeden Morgen Abrechnungsprozesse, die einen großen Hardwarebedarf für einen kurzen Zeitraum benötigen. Zwischen zwei solchen Abrechnungsprozessen erfolgt dann die manuelle Korrektur von aufgetretenen Problemen, wofür deutlich weniger Ressourcen erforderlich sind. Um diesen unterschiedlichen Anforderungen kostenoptimiert gerecht zu werden, bietet sich eine Cloud-Plattform an – der Auftraggeber nutzt hierfür Amazon AWS. Damit die Bereitstellung, Skalierung und Verwaltung der Anwendungen automatisch ablaufen, wird zudem Kubernetes eingesetzt. Für die unterschiedlichen Projektphasen wird die AWS-Plattform außerdem in vier Stages aufgeteilt:
1. Entwicklungsumgebung (EU – das technische Design der Anwendungen)
2. Testumgebung (TU – technische Tests)
3. Abnahmeumgebung (AU – fachliche Tests)
4. Produktionsumgebung (PU – der Live-Betrieb für die Kunden)

Beim Design der Anwendungslandschaft setzen wir dabei auf den Micro-Services Ansatz. Das heißt, die bereits angedeutete, komplexe Logik wird nicht von einem einzigen Code-Block übernommen, sondern in verschiedene Funktionalitäten gespalten und auf unabhängige Services verteilt. Diese Services laufen dann in sogenannten Pods auf der von Kubernetes gemanagten AWS – Cloud Plattform.

Aus Performancegründen ist ein geschicktes Design dieser Service-Landschaft entscheidend. Beim richtigen Schnitt der Funktionalitäten lassen sich so mehrere Pods, also Instanzen eines Service, betreiben. Es macht beispielsweise nur Sinn, dass ein einzelner Pod eines Service eine große Datei einliest. Im Anschluss kann er dann die einzelnen Datensätze der Datei zur weiteren Verarbeitung auf verschiedene Pods des nächsten Service verteilen. Gleichzeitig bietet eine solche Mehrzahl von Service-Instanzen die Möglichkeit, dass beim Ausfall eines Pods problemlos andere einspringen. So ist sichergestellt, dass kein Datensatz während der Verarbeitung verloren geht.

Bei den Services unterscheiden wir zwischen Frontend und Backend. Das Frontend, die Benutzeroberfläche für die fachlichen Anwender, ist in Angular geschrieben; das Backend für die Implementierung der Logik hingegen im Java-Framework SpringBoot.

Insgesamt ergibt sich so eine Anwendungslandschaft mit über 50 Services, die auf unserer AWS-Plattform, intern via REST, kommunizieren. Die API’s werden von uns entsprechend des OpenAPI-Standards definiert, wofür wir Swagger nutzen.

Neben der von uns bereitgestellten Oberfläche für nutzerfreundliche Eingaben benötigen wir aber auch eine Reihe technischer Datensätze, welche uns als Dateien oder über SOAP- und REST-Schnittstellen zur Verfügung gestellt werden. Für den regelmäßigen Import dieser Daten setzen wir Spring Batch ein. Bei bekannten Dateiformaten wie CSV, XLSX oder XML kommen für die Verarbeitung entsprechende Open-Source Bibliotheken, wie zum Beispiel FasterXML/Jackson zum Einsatz. Bei proprietären Formaten muss die Verarbeitung hingegen vollständig selbst implementiert werden. Dabei werden während des Imports viele Daten plausibilisiert, um möglicherweise fachliche Widersprüche zu identifizieren. Eine weitere Herausforderung ist zudem, dass nicht jede Schnittstelle die Daten passgenau liefert. Zum Teil müssen aufwändige Berechnungen durchlaufen werden, damit die weitere Verarbeitung in kürzester Zeit erfolgen kann.

Egal ob durch eine Nutzereingabe über unsere UI (User Interface) oder einen zeitlich getriggerten Import: Jeder Datensatz durchläuft eine Reihe von Service-Aufrufen, wobei viele von ihnen synchron erfolgen. Um für eine hohe Ausfallsicherheit zu sorgen, kommunizieren jedoch auch einige asynchron über JMS, wofür wir ActiveMQ verwenden. Erklärtes Ziel und wichtiges Akzeptanzkriterium ist hierbei die schnelle Verarbeitung mehrerer zehntausender Datensätze in kürzester Zeit. Dabei hilft es die Anzahl der Service-Aufrufe so gering wie möglich zu halten. Wann immer es sich anbietet, werden deshalb die Daten in einem Cache vorgehalten. Für dieses Feature nutzen wir Redis oder auch Caffeine.

Neben der reinen Orchestrierung von Service-Aufrufen gibt es außerdem Prozesse, die mit Camunda modelliert und über die Camunda Workflow Engine als Java-Prozesse implementiert werden. Dieses Tool ist insbesondere dann hilfreich, wenn zwischen technischen Prozessschritten immer wieder Interaktionen mit dem Nutzer notwendig sind.

Fortlaufend werden die Daten in einer PostgreSQL Datenbank gespeichert. Dies dient dazu, die verarbeiteten Daten zu speichern und so eine Revisionssicherheit zu schaffen. Gleichzeitig nutzen wir für die Versionierung und Verwaltung des Sourcecodes Git. Das Ausrollen der Anwendungen auf die verschiedenen Stages ist in Gitlab über entsprechende CI/CD Pipelines eingerichtet. Damit die verschiedenen Datenbanken auf allen Stages mit der installierten Version der Anwendung harmonieren, werden Änderungen an Tabellen oder Indizes nur über Liquibase-Skripte vorgenommen. Dies ermöglicht eine versionierte Ablage der Datenbank-Skripte in Git.

An dieser kompakten Darstellung wird sichtbar, dass es sich um ein vielseitiges und komplexes Projekt handelt. Dennoch macht es Spaß sich täglich den herausfordernden Aufgaben zu stellen und gemeinsam als Team individuelle Lösungen zu erarbeiten.