Über unsMediaKontaktImpressum
Dr. Jürgen Lampe 24. Juli 2018

Was Microservices wirklich kosten – Alles hat seinen Preis.

Microservices erfreuen sich als Architekturparadigma großer Beliebtheit. Dabei werden zwei wichtige Tatsachen weitgehend ausgeblendet: Erstens verursachen Microservices sowohl bei der Entwicklung als auch im Betrieb zusätzliche Kosten, die gerechtfertigt werden müssen. Zweitens sind viele der angestrebten Vorteile schon durch saubere Modularisierung erreichbar. Aus der Diskussion dieser Aspekte wird eine Liste von Fragen entwickelt, die man vor der Entscheidung für eine Anwendung, die Microservices verwendet, ehrlich beantworten sollte.

Im Leben ist bekanntlich nichts umsonst. Doch so trivial und bekannt das auch ist, es wird immer wieder übersehen oder ignoriert. Das verhält sich im Bereich der IT-Technologien nicht anders. Die Konzentration auf versprochene positive Effekte unter Vernachlässigung der Kosten ist infolge des Wettlaufs um Innovationen eher noch ausgeprägter. Und nicht zu vergessen: Es macht natürlich auch Spaß, Neues auszuprobieren und auf alte Fragestellungen anzuwenden.

Um keine Missverständnisse aufkommen zu lassen: Es ist nicht das Ziel, Microservices zu verteufeln, sondern einen Beitrag zu einer verantwortungsvollen Bewertung und Einordnung zu leisten.

Zur Verdeutlichung dieser Zielsetzung sei ein kleiner Exkurs erlaubt. Per Zufall entdeckt, gibt es im Internet eine Liste von Fragen [1], die sich jeder Interessent stellen sollte, bevor er sich einen Hund anschafft. Ganz sicher geht es dabei nicht darum, Hunde als Haustiere abzulehnen, aber es gibt einfach eine Reihe von Voraussetzungen, die erfüllt sein müssen, um vorhersehbaren nachfolgenden Problemen vorzubeugen. Natürlich verhindert so eine Checkliste nicht, dass Hunde in ungeeignete Verhältnisse kommen, aber sie gibt verantwortungsvollen Menschen nützliche Hinweise.

In der IT, wo die Probleme nicht im Tierheim akkumuliert, sondern eher mit einem Achselzucken hingenommen werden, sind fundierte Entscheidungshilfen für die Auswahl bestimmter Technologien und dabei insbesondere Ausschlusskriterien leider selten. Kostenüberschreitungen oder gar Fehlschläge sind so häufig, dass nur in Ausnahmefällen eine genaue Ursachenanalyse erfolgt. 

Es wäre vermessen, hier eine Liste von Kriterien vorzulegen. Vielmehr sollen einige Gedanken und Erfahrungen präsentiert werden, die als Materialsammlung für die Extraktion von Kriterien dienen können. Im Unterschied zu den in [2] behandelten grundsätzlicheren Fragen liegt der Schwerpunkt auf den Aspekten, die sich aus der praktischen Arbeit in konkreten Projekten ergeben haben.

Wenn eine Technologie aus dem Fokus gerät, findet sich viel zu selten jemand, der die gewonnenen Erkenntnisse sammelt und aufbereitet.

Zur Einführung noch eine allgemeine Bemerkung: Microservices sind ja nicht der erste Ansatz zur Strukturierung verteilter Anwendungen. Der eine oder andere erinnert sich vielleicht noch an die Common Object Request Broker Architecture (CORBA). In einigen Punkten hat der Umgang mit Microservices bemerkenswerte Parallelen. Konzepte wie den Service Locator gab es bereits damals. Auch die gängige Praxis, Services in YAML mit Hilfe von Swagger [3] zu beschreiben und gegebenenfalls daraus automatisch Client- und Service-Stubs (in unterschiedlichen Programmiersprachen) generieren zu lassen, entspricht letztlich der Rolle der IDL (Interface Definition Language) von CORBA. Nun geht es nicht darum, in ein "Alles-schon-mal-da-gewesen" zu verfallen. Stattdessen lautet die Frage: Was können wir aus den Erfahrungen lernen? Leider gibt es keine fertige Antwort darauf. Wenn eine Technologie aus dem Fokus gerät, findet sich viel zu selten jemand, der die gewonnenen Erkenntnisse sammelt und aufbereitet. Die zwangsläufige Folge ist, dass bestimmte Fehler wieder und wieder gemacht werden. In Bezug auf CORBA lässt sich sagen, dass es ganz sicher nicht nur an der Schwergewichtigkeit und der Vorherrschaft eines großen Anbieters gelegen hat. Ein wichtiges Problem bestand beispielsweise in den Kosten, die jede Schnittstellenänderung verursachte. Kann man wirklich hoffen, dass – auf die Dauer – Microservices diese Falle werden vermeiden können?

Begrifflichkeit

In diesem Beitrag werden die Begriffe Microservice-Architektur und monolithische Architektur zur Gegenüberstellung verwendet. Letztere ist dadurch gekennzeichnet, dass in der Regel die Anwendung als ein Artefakt betrachtet und in Betrieb genommen wird. Das schließt ausdrücklich nicht aus, dass auch eine monolithische Anwendung wartungsfreundlich modular aufgebaut sein kann. Der Fokus dieser Betrachtungen liegt auf den Konsequenzen, die sich aus den für Microservice-Architekturen typischen unabhängigen Deployments und der Kommunikation über Netzwerkprotokolle ergeben.

Kosten

Microservices verursachen zusätzliche Kosten, und zwar sowohl in der Entwicklung als auch zur Laufzeit. Diese Tatsache wird gern übersehen. Deshalb werden die entstehenden Aufwände im Folgenden genauer betrachtet.

Laufzeitkosten

Als Laufzeitkosten werden all die Kosten zusammengefasst, die während der produktiven Phase einer Anwendung einzig und allein aus dem Architekturparadigma entstehen. Hier beschränken wir uns auf den Anteil, der unmittelbar bei der Code-Ausführung selbst entsteht. Mittelbare Kosten, die sich beispielsweise aus dem Betrieb einer verteilten Anwendung etwa für Zertifikatsverwaltung, Netzwerk-Konfiguration usw. ergeben, bleiben unberücksichtigt. Das heißt jedoch ausdrücklich nicht, dass dieser Kostenblock vernachlässigbar wäre.

Der Aufruf eines Microservices ist um einige Größenordnungen teurer als ein Methodenaufruf. Die Daten müssen

  1. konvertiert und in ein textuelles Standardformat gebracht werden (JSON, XML),
  2. über eine zu etablierende Netzwerkverbindung an den Empfänger übertragen werden und
  3. schließlich beim Empfänger geparst und in ein passendes Datenformat zurück übertragen werden.

Der erste und der letzte Schritt erfordern umfangreiche Zeichenketten-Operationen, die nicht nur in Java als ressourcenintensiv bekannt sind. Bei einfachen Funktionen kann dieser Overhead spürbar ins Gewicht fallen.

Dass Netzwerkverbindungen (Punkt 2) aufwändig sind, ist ebenfalls nicht neu. Wie aufwändig, hängt u. a. vom verwendeten Protokoll (z. B. HTTP oder HTTPS) ab. Durch die zusätzlich benötigten Aktivitäten zur Absicherung gegen unbefugte Zugriffe steigen die Kosten weiter.

Sowohl der Rechenaufwand als auch die Netzwerkoperationen sollten im Hinblick auf Cloud-Computing, für das viele Microservice-Anwendungen gebaut werden, nicht unterschätzt werden. Wenn, wie häufig üblich, die Abrechnung auf der Basis von abgerufener Prozessorleistung und genutztem Übertragungsvolumen erfolgt, hat der beschriebene Overhead unter Umständen auch messbare finanzielle Konsequenzen.

Außer bei großen verteilten Anwendungen sind die zusätzlichen Laufzeitkosten im Vergleich zum folgenden Kostenblock jedoch weniger wichtig. Ganz vergessen sollte man sie trotzdem nie.

Entwicklungskosten

Zusätzliche Kosten in der Entwicklung entstehen an verschiedenen Stellen. Sie sind nicht immer klar abgrenzbar. Beispielsweise können Aufwände, die sich aus der konsequenten Modularisierung, die ja Voraussetzung jeder Microservice-Architektur ist, auch bei einer monolithischen Lösung (im oben bezeichneten Sinn) anfallen. Diese Modularisierungskosten finden ganz allgemein viel zu wenig Beachtung, wie in einer Analyse derartiger Projekte gezeigt werden konnte [4]. Darüber hinausgehende Kosten können sich daraus ergeben, dass die Modularisierungsanforderungen für Microservices tendenziell höher oder feingranularer sind.

Die Implementierung hochgradig verteilter Anwendungen ist technisch kein Problem. Auf den ersten Blick erscheint es sogar relativ einfach und schnell möglich zu sein, einen Microservice zu erzeugen. Dank frei verfügbarer Bibliotheken und CDI (Contexts and Dependency Injection) ist das tatsächlich so. Die nicht unerheblichen Kosten entstehen vorrangig in Bereichen, die aus Entwicklersicht weniger Aufmerksamkeit finden.

Sicherheitsaufwand

Jeder Microservice ist ein potentielles Einfallstor für Schadprogramme. Kommunikation ausschließlich über gesicherte Verbindungen (z. B. HTTPS) als ein erster nützlicher Schutz ist jedoch nicht gratis zu realisieren. Überdies hat die Praxis gezeigt, dass bisweilen ganze Netzwerkstrukturen infiltriert werden konnten oder Protokolle Implementierungsfehler enthielten, sodass es leichtfertig wäre, sich nur auf diesen Schutz zu verlassen. Deshalb müssen alle empfangenen Daten umfassend validiert werden. Insbesondere auf Injektion bösartiger Daten beruhende Angriffsvektoren sind dabei ins Auge zu fassen. In manchen Fällen kann es sinnvoll oder sogar zwingend erforderlich sein, Daten zusätzlich verschlüsselt oder zumindest signiert zu übertragen. 

Intern sollte jeder Service durch einen Wrapper geschützt sein, der sicherstellt, dass wirklich nur die geforderten Funktionen von außen erreichbar sind. In der entgegengesetzten Richtung, beim Aufruf von Services, sollte auf diese nur über einen Adapter zugegriffen werden, der die externe Funktion kapselt und verhindert, dass irrtümlich Informationen gesendet werden, die ein Angreifer nutzen könnte [5]. Das alles ist zusätzlicher Code, der geschrieben, dokumentiert und gewartet werden muss (und der sehr wahrscheinlich zusätzliche Fehler, die gefunden und beseitigt werden müssen, enthält).

Testaufwand

Nur ganz selten findet man Hinweise darauf, dass Microservices einen erheblich höheren Aufwand beim Testen verursachen. Das gilt für alle Ebenen, angefangen vom Entwicklertest bis hin zum Abnahmetest. 

Einerseits hat das die eher triviale Ursache, dass im Hinblick auf die angestrebten unabhängigen Deployments eine sehr hohe Testabdeckung erreicht werden muss. Schließlich dürfen auch momentan noch gar nicht verwendete Facetten nicht vernachlässigt werden, da sie möglicherweise in Zukunft von einem Service-Kunden benutzt werden. Testfälle wachsen dabei in die Rolle von ausführbaren Verträgen zwischen Service-Anbieter und -Kunden hinein. Nur Funktionen, die durch Testfälle garantiert sind, können sicher verwendet werden. Das stellt nicht nur an die Definition der Tests hohe Anforderungen, immerhin soll der Service möglichst vollständig repräsentiert werden, sondern auch an die verständliche Formulierung (Implementation und Dokumentation).

Andererseits müssen Services auch über ihre Netzwerk-Schnittstellen (z. B. die REST-Schnittstellen) getestet werden. Zum Testen gibt es zwar recht gute Werkzeuge, beispielsweise Wiremock[6], aber der Aufwand für die Erstellung der Testfälle ist trotzdem deutlich größer als bei einfachen Unit-Tests. Außerdem sind die Test-Frameworks selbst komplex und nicht immer einfach zu verstehen. Es gibt viele Möglichkeiten, Fehler zu machen und da die interne Funktionsweise der Mocks verborgen bleibt, kommt es bisweilen zu schwer ergründbaren unerwünschten Effekten (z. B. gegenseitige Beeinflussung, sporadische Fehlschläge). Erfahrungsgemäß erfordert das Erstellen eines Microservice-Testfalls häufig ein Vielfaches des Aufwands, den der Unit-Test der zugrundeliegenden Funktion verursacht. Dass solche Tests dann auch deutlich länger laufen, fällt dagegen eher weniger ins Gewicht.

Nutzen

Letztlich ist es das Ziel – wie bei allen wirtschaftlichen Aktivitäten – aus dem Einsatz von Microservice-Architekturen einen finanziellen Nutzen zu ziehen. Einige der häufig vorgebrachten Vorteile werden nun diesbezüglich genauer betrachtet.

Flexibilität, bessere Wartbarkeit

Es wird allgemein angenommen, dass Microservices einzeln verteilt und entwickelt werden können. Dadurch könnten Teams weitgehend unabhängig voneinander arbeiten, was die Skalierung agiler Entwicklungs-Prozesse mit wenig Kommunikations- und Koordinationsaufwand ermöglichen soll. Darüber hinaus wird erwartet, dass Microservices, da sie klein sind, übersichtlich bleiben und leicht weiterentwickelbar beziehungsweise durch Neuimplementierungen ersetzbar sind.

Genau genommen sind das jedoch Vorteile, die größtenteils bereits durch konsequente Modularisierung ohne die beschriebenen Mehrkosten erreicht werden können. Der Zusatzaufwand, der dadurch entsteht, dass beim Deployment ein größeres Artefakt (der Monolith) gebaut und verteilt werden muss, fällt erst bei sehr großen Projekten ins Gewicht.  

Für die Testaufwände lässt sich das nicht so einfach sagen. In der Zielvorstellung mit häufigen automatischen Service-Updates werden alle Tests automatisch als Teil der Deployment-Pipeline ausgeführt. Das setzt nicht nur den erwähnten hohen Testabdeckungsgrad voraus, sondern auch ein entsprechendes Geschäftsumfeld, indem die in einem solchen Szenario nie ausschließbaren unerkannten Fehlfunktionen beherrschbar und tolerierbar sind. In so einem Fall sind echte Einsparungen möglich. Wenn die erforderliche Elastizität gegenüber Fehlern in Produktion aufgrund geschäftspolitischer, gesetzlicher oder regulatorischer Vorgaben nicht gegeben ist, können die entsprechenden Potentiale hingegen nicht realisiert werden. Denn dann muss – wie bei einem Monolithen – weiterhin immer die gesamte Anwendung vor der Freigabe überprüft werden.

Skalierbarkeit

Microservices können unabhängig voneinander skaliert werden. Welcher Nutzen lässt sich daraus gewinnen? Die Antwort hängt ganz von der Anwendung ab. Wenn die Geschäftsvorfälle so strukturiert sind, dass sie zu stark variierender Frequenz bei der Nutzung der verschiedenen Services führen können, ist die unabhängige Skalierbarkeit zweifellos ein Vorteil. Das gilt umso mehr, wenn durch die Beschränkung auf jeweils einige wenige Services neue Processing-Knoten bei volatiler Last schneller hochgefahren werden können.

Wenn die Last allerdings weniger stark schwankt und wenn vor allem die Nutzungsfrequenzen der diversen Services in relativ festen Verhältnissen zueinander stehen, ergeben sich keine nennenswerten Vorteile gegenüber einer kompakten Anwendung, vorausgesetzt letztere ist ebenfalls so ausgelegt, dass sie den Erfordernissen entsprechend durch Starten weiterer Instanzen und entsprechender Lastverteilung skaliert werden kann.

Einsparungen sind vor allem dann möglich, wenn nur relativ kurze aber hohe Lastspitzen abgedeckt werden müssen.

Fehlertoleranz

Verteilte Systeme können gut gegen den Ausfall einzelner Services abgesichert werden, so dass das Gesamtsystem robust ist. Allerdings ist auch dieser Vorteil nicht ausschließlich durch Microservices realisierbar. Im Gegenteil, es ist eher umgekehrt: Weil massiv verteilte Systeme viel häufiger von Fehlern, z. B. Verbindungsabbrüchen, bedroht sind, müssen sie von vornherein eine höhere Resilienz aufweisen. Diese Fehlertoleranz muss natürlich organisiert und verwaltet werden. Auf IT-Seite entstehen dabei eher Mehrkosten. Der etwaige Nutzen ergibt sich vorrangig auf der geschäftlichen Seite aus höherer Verfügbarkeit und daraus hoffentlich resultierender größerer Kundenzufriedenheit.

Leistungsfähigkeit

Dieser ganz wichtige Aspekt spielt in der allgemeinen Diskussion eine viel zu kleine Rolle. Viele der großen weltweit agierenden Systeme sind in einer anderen Architektur gar nicht vorstellbar. Für diese Anwendungen ist aber auch typisch, dass sie stark aus der IT-Architektur heraus entwickelt worden sind und weiterentwickelt werden.

Obwohl Anwendungen dieser Kategorie nicht die Regel sind, lässt sich aus ihnen eine wichtige Erkenntnis gewinnen. Auf die Dauer sind derartige Architekturen nur profitabel, wenn die Geschäftsvorfälle zur technischen Struktur passen. Das bedeutet einmal, dass Geschäftsmodelle, die sich nicht entsprechend aufgliedern lassen, besser nicht so abgebildet werden sollten. Zum anderen heißt das, dass die Weiterentwicklung der Geschäftstätigkeit auf die technischen Gegebenheiten Rücksicht nehmen muss, um Modifikationen der Schnittstellen zwischen den Service-Einheiten zu vermeiden, weil die besonders teuer umzusetzen sind. 

Kosten-Nutzen-Vergleich

Alles hat seinen Preis. Diese einfache Wahrheit wird angesichts begeisternder neuer Technologien gern übersehen. Die Kunst besteht darin, für die jeweilige Situation einen akzeptablen Maximalbetrag auszumachen und dann zu prüfen, ob die Lösung innerhalb des so gegebenen Kostenrahmens möglich ist.

In den vorangegangenen Abschnitten wurden Kosten und Nutzen von Microservice-Architekturen betrachtet. Für eine abschließende Bewertung müssen beide Größen gegenübergestellt werden. Dabei steht man vor dem Dilemma, dass unterschiedliche Qualitäten zu vergleichen sind:

  • Die aufgeführten Kosten entstehen in jedem Fall und sind – mit allen Unsicherheiten – relativ gut quantifizierbar.
  • Der Nutzen entsteht hingegen zum großen Teil aus "weichen" Faktoren (z. B. Time to Market, höhere Verfügbarkeit, bessere Skalierbarkeit), die sich schwieriger in Zahlen fassen lassen und überdies noch stark von ungewissen Entwicklungen in der Zukunft abhängen.

Trotzdem ist es möglich, sinnvolle Schlussfolgerungen zu ziehen. Der erste wichtige Punkt besteht darin, sich der zusätzlichen Kosten überhaupt einmal bewusst zu werden. Danach kann man versuchen, anhand der dargestellten Überlegungen den Umfang des Mehraufwands sowohl für die Entwicklung als auch den Betrieb abzuschätzen. Es wäre nicht überraschend, wenn bei manchem Projekt bereits an dieser Stelle klar würde, dass es wirtschaftlich nicht sinnvoll ist.

Falls die erwarteten Mehraufwände vertretbar sind, sollte im nächsten Schritt der potentielle Nutzen ermittelt werden. Zweckmäßigerweise werden dazu mindestens zwei Szenarien betrachtet, nämlich einmal ein optimales und eines für den wahrscheinlichsten oder ungünstigsten Fall. Auch dabei können wieder die oben beschriebenen Aspekte – natürlich auch weitere – zur Anwendung kommen. Wenn der Nutzen selbst im optimalen Fall die Kosten nicht rechtfertigt, ist die Entscheidung klar. Andernfalls ist es die ureigenste Aufgabe der Geschäftsführung, auf der Basis der aufbereiteten Zahlen über das weitere Vorgehen zu entscheiden.

Fragenliste

Zum Abschluss sollen die aufgeworfenen Probleme in einer Liste von Fragen zusammengefasst werden, die sich jeder vorlegen sollte, bevor eine Entscheidung für oder gegen eine Microservice-Architektur erfolgt.

Ausgangspunkt ist eine ganz generelle Frage, die dabei helfen soll, zu erkennen, ob es wirklich essenzielle Gründe für die Wahl der Technologie gibt oder ob man nicht vielleicht doch nur einem aktuellen Trend folgt:

  • Welche konkreten Anforderungen lassen sich nur mit der Microservice-Architektur und nicht mit einem sauber modularisierten Monolithen erreichen [7]?

Microservices bringen in gewissem Sinne die Silostruktur zurück. 

  • Lassen sich die Geschäftsprozesse in klar abgegrenzte Stränge/Geschäftsvorfälle zerlegen, die isoliert bearbeitet werden können?
  • Änderungen dieser Zerlegungsstruktur sind im Allgemeinen sehr kostspielig. Ist die gewählte Struktur flexibel genug, um den wahrscheinlichen Änderungen des geschäftlichen Umfelds ohne substantielle Umbauten der Service-Struktur entsprechen zu können?
  • Sind die Beziehungen zwischen diesen Strängen hinreichend exakt und vollständig definiert, sodass langwierige und konfliktträchtige Abstimmungsrunden wahrscheinlich nicht nötig sein werden?
  • Viele Vorteile erschließen sich erst auf längere Sicht. Haben die einzelnen Stränge hinreichendes Entwicklungspotential, um über einen absehbaren Zeitraum eigenständig neue Features liefern zu können?

Abhängigkeiten können auch entstehen, wenn Entwicklerteams mehr als einen Service entwickeln oder einzelne Personen in mehrere Teams integriert sind. 

  • Sind ausreichende Entwicklungskapazitäten vorhanden, um wirklich parallel – und damit schneller – entwickeln zu können?
  • Der Aufbau und das Zusammenfinden agiler Teams kostet Zeit und Geld. Können die eingespielten Teams dann auch über einen wirtschaftlich sinnvollen Zeitraum hinweg ausgelastet werden?

Der unmittelbare Nutzen, der sich aus der Microservice-Architektur ergibt, ist betragsmäßig in der Regel überschaubar. Erst über Skaleneffekte lassen sich daraus ansehnliche Gewinne generieren.

  • Kann realistischer Weise mit einem so starken Wachstum gerechnet werden (oder ist der Bedarf bereits so groß), dass sich die in die Entwicklung investierten Mehraufwände in überschaubarer Zeit amortisieren?
  • Schwanken die Leistungsanforderungen tatsächlich so stark, dass der organisatorische Aufwand für das bedarfsgesteuerte Zu- und Abschalten von Rechnerinstanzen durch eingesparte Nutzungsentgelte gerechtfertigt werden kann?

Eine besonders tückische Kostenfalle lauert in den Verwaltungsfunktionen. Häufig beschränkt man sich bei Pilotanwendungen auf die kostengünstig bereitgestellten Paketlösungen. Bei der Realisierung des echten Projekts stellt sich dann heraus, dass teure Anpassungen zwingend erforderlich sind.

  • Sind beispielsweise die von Cloud-Anbietern standardmäßig bereitgestellten Verwaltungsfunktionen wirklich ausreichend?
  • Rechtfertigt der Umfang der voraussichtlichen Nutzung die gegebenenfalls anfallenden Anpassungsaufwände?

Letzten Endes läuft alles auf die einfache Frage hinaus: Welcher Nutzen bleibt unter dem Strich übrig? 

Quellen
  1. Landestierschutzbeauftragte Hessen: Fragen an Tierfreundinnen/Tierfreunde, die sich einen Hund anschaffen wollen
  2. Informatik Aktuell – Dr. Jürgen Lampe: Microservices: Starke Medizin – aber gegen was?
  3. Smartbear: Swagger
  4. J. Knodel, M. Naab und B. Weitzel, 2015: Modularity - Often Desired, but Rarely Achieved. Softwaretechnik-Trends. Bd. 35, 2, S. 37-38.
  5. Youtube: K. Anton: The Path of Secure Software
  6. WireMock
  7. Informatik Aktuell – Christoph Iserlohn und Till Schulte-Coerne: Warum es nicht immer Microservices sein müssen
  8. G. Turnquist: REST, SOAP, and CORBA, i.e. how we got here

Dr. Jürgen Lampe auf den IT-Tagen 2018

Autor Dr. Jürgen Lampe spricht auf den diesjährigen IT-Tagen – der Jahreskonferenz der Informatik Aktuell zu den Themen Clean Code und KI:

Clean Code Revisited oder Clean Code: Wie weiter?
(13.12.2018, 09:00 Uhr)

Wie intelligent ist KI?
(11.12.2018, 09:00 Uhr)

Autor

Dr. Jürgen Lampe

Dr. Jürgen Lampe befasst sich seit mehr als 15 Jahren mit Design und Implementierung von Java-Anwendungen im Bankenumfeld.
>> Weiterlesen
Das könnte Sie auch interessieren
botMessage_toctoc_comments_9210