Über unsMediaKontaktImpressum
Hermann Woock 09. September 2020

Domain-Driven Design – Deep Dive

Derzeit führen viele Projektteams µServices und damit auch Eric Evans' Domain-Driven Design (DDD) bei sich ein. Oft geht es darum, den Legacy-Monolithen abzulösen. Viel zu lange hat er die Firma gequält. Er erfüllt die Qualitätsanforderungen einfach nicht mehr und schafft dabei viele Probleme. Er soll weg und durch µServices ersetzt werden. Dabei hat sich herumgesprochen, dass DDD als Zauberwaffe zur Zerlegung von Monolithen eingesetzt werden kann. Doch der Weg dahin ist steinig und oft werden auf der Strecke viele kostbare Perlen des DDDs übersehen. Ungewollt und überraschend führt der Weg manches Mal in eine irritierende Richtung, manchmal auch ins Abseits. Richtig angewendet eröffnet DDD allerdings ungeahnte Möglichkeiten. Schade, wenn man die verpasst...

Auf diese Wege, die Stolpersteine und Perlen möchte ich in diesem Artikel zu sprechen kommen. Der Artikel soll die Tiefen von DDD aufzeigen und bei der Beantwortung der folgenden Frage helfen: Ist DDD für mein Projekt das Richtige? Wenn "Ja", dann lass' es uns aber richtig angehen.

Gute Gründe für DDD

Ohne Frage kann man mit DDD eine Anwendung bauen, in der es wenige Abhängigkeiten gibt. Mittels der Context Map (vgl. Abb. 1) kann man diese Abhängigkeiten sichtbar machen und Datenströme oder Kommunikationswege analysieren. Das ist schon mal im Sinne von Conway’s Law – eine gute Basis, um daraus µServices, Komponenten oder eine andersartige modulare Architektur zu bauen [1].

DDD ist allerdings mehr, als ein profanes Mittel zum Zerlegen eines Monolithen. Es bietet viele weitreichende Vorteile, für das Projekt ebenso wie für dessen Umfeld. Über diese Vorteile sollte man gezielt nachdenken und sich fragen, ob man sie nutzen möchte. Grob ergeben sich Vorteile in vier Bereichen:

  • die Organisation des Auftraggebers,
  • das Projekt,
  • die Anwender und
  • die Entwicklung.

Im Folgenden möchte ich die Vorteile kurz skizzieren.

Die Organisation

DDD bietet die einmalige Gelegenheit, positiven Einfluss auf Geschäftsumfeld und Arbeitsabläufe, aber auch Firmenkultur, die teuren Bottlenecks, die überflüssigen kurzen Dienstwege oder die umständlichen Workarounds zu nehmen. Mit DDD lernt die Firma sich selber, ihr Business und ihre Märkte besser zu verstehen. DDD ist ein Lernprozess, in dem es darum geht, durch IT die richtige Software zu bauen, die der Firma dabei hilft zu prosperieren, um z. B. angemessen dynamik-robust auf dynamische Märkte zu reagieren. Dabei erlernt die Firma in einer Sprache, der sog. Ubiquitous Language zu sprechen. Es bedarf keiner Dolmetscher mehr, die zwischen IT und Anwendern oder Fachexperten vermitteln. Die Einsicht wächst, dass das gewonnene Wissen bares Geld wert ist und dass einem dieses Wissen dabei hilft, das eigene Geschäft besser umzusetzen. Besser jedenfalls als die Konkurrenz. Die Idee, durch Offshore Wissen ins Ausland zu verschenken, kommt einem archaisch – wie aus dem letzten Jahrtausend – vor; unter uns: Ist sie ja auch! Ich habe nur sehr wenige Firmen gesehen, die in einem Offshore-Projekt auch einen Wissenstransfer unternommen haben, sich also das Wissen, das offshore entstanden ist, zusammen mit der Software haben übergeben lassen.

Das Projekt

DDD steigert  die Entwicklungsgeschwindigkeit. Für Monolithen gilt, was Frederick Brooks in seinem Buch "The Mythical Man-Month" beschreibt: Mehr Leute bedeutet zunächst eine Verzögerung des Projekts [2]. Bei DDD ist dies anders. Durch weniger Abhängigkeiten bedeuten hier mehr Entwickler, dass die  Software schneller fertig wird. Dadurch ergibt sich ein kürzeres Time to Market, was ein schnelles Reagieren in komplexen Märkten bedeutet.

Mit DDD lässt sich auch eine komplizierte Business-Logik gut beherrschen. Unter kompliziert verstehe ich hier, dass es viele, eben komplizierte Zusammenhänge im Projekt gibt, die nur von wenigen verstanden werden. Änderungen dauern lange und sind sehr fehleranfällig. Bei vielen Kunden muss ich immer wieder feststellen, dass nicht unbedingt die Technik das größte Problem darstellt, sondern längst die komplizierte Business-Logik den Entwicklern über den Kopf gewachsen ist. Oft haben IT-Leute ja auch keine Ausbildung oder Studium in ihrer jeweiligen Domain absolviert. Dennoch schreiben sie dafür Software und bauen sich dann ihre Welt, wie sie aus der Sicht der IT Sinn machen könnte. Vielleicht nicht immer die beste Lösung für die Praxis.

Schließlich möchte ich noch die Möglichkeit einer sanften Migration vom Monolithen zu einer µService-Architektur erwähnen – dem Wegbereiter in die Digitalisierung.

Die Anwender

Bei DDD entsteht das Requirements Engineering nicht als Wasserfall-Prozess, noch nicht mal mittels eines Vermittlers, wie dem Product Owner aus Scrum. Vielmehr arbeitet die Entwicklung direkt mit Anwender und Fachexperten zusammen. So wird die Software nicht am Kunden und seinen Vorstellungen vorbeigebaut. Hingegen erlebt die IT Lob und Klage unmittelbar und live mit, so dass sie sofort reagieren kann.

Welcher Anwender hat sich nicht schon mal, kurz nach dem fälschlichen Abschicken eines Formulars, den Button "Halt, nein doch nicht!" gewünscht? Bei DDD kann es ihn geben. Durch Zusammenbringen von IT und Anwender werden fehleranfällige Ereignisse sichtbar, und das Programm kann entsprechend angepasst werden.

Wie würden Sie am liebsten Fahrradfahren lernen?

  • Die Dokumentation lesen, die ein Freund geschrieben hat, der mit einem Radfahrer gesprochen hat (Wasserfall: Lastenheft, Pflichtenheft).
  • Mit einem Freund, der einen Radfahrer kennt, über das Fahrradfahren sprechen (PO: abarbeiten eines Backlogs in Iterationen).
  • Selbst mit einem Radfahrer darüber austauschen (DDD: Entwickler beobachten Anwender bei der Arbeit).
  • Ein Fahrrad kaufen und es selbst ausprobieren (die Anwender schreiben selber das Programm – nicht empfehlenswert, s. Softwarecrisis[3]

Je näher die IT am Geschehen ist, desto besser wird auch die Software. Die gemeinsame Sprache hilft dabei, Missverständnisse zu vermeiden.  

Die Entwicklung

Die Entwickler können komplexe sowie komplizierte Software beherrschen. Sie erhöhen durch DDD die Softwarequalität wie Skalierbarkeit, Erweiterbarkeit, Resilience und viele mehr. Die IT und die Fachexperten verstehen einander besser und ziehen am selben Strang. Das möchte ich an den folgenden zwei Beispielen kurz erläutern.

  • Ein Entwickler ist es gewohnt, den OK-Button in einem Formular erst dann zu aktivieren, wenn alle Pflichtfelder auf der Seite ausgefüllt sind. Dummerweise hat ein Anwender gerade einen Kunden vor sich, der kurz noch mal weg muss, um seine Partnerin nach einem Datum zu fragen. Heißt das jetzt, alle bis dahin gemachten Eingaben müssen verworfen werden, um den nächsten Kunden zwischenzeitlich zu bedienen und danach alles wieder bei Null einzugeben, oder kann man auch mal unvollständige Daten abspeichern?
  • Primary Keys können in der Datenbank nicht geändert werden. So ist z. B. die Kundennummer ein für alle Mal fest vergeben. Aus einem unerfindlichen Grund kommt es dennoch manches Mal vor, dass so eine Änderung vorgenommen werden muss. Da das nicht geht, hat sich der Anwender einen Workaround einfallen lassen. Er löscht den Kunden und legt ihn mit neuer Kundennummer wieder an. Problem gelöst. Naja, nicht so ganz. Das verwirrt nämlich die Statistik, die plötzlich viele An- und Abmeldungen registriert und versucht, sich einen Reim darauf zu machen. Diesen gibt sie an das Marketing weiter, welches mit seltsamen Angeboten reagiert, auf die niemand anspringt. Zumindest so lange nicht, bis mal wieder eine Kundennummer geändert werden muss.

Arbeiten mit DDD macht Spaß und ist effizient. Die Entwicklung kann sich auf das Wesentliche konzentrieren und erlebt stetig Erfolge mit. Das lockt nicht zuletzt Talente an, die derzeit auf dem Markt schwer zu bekommen sind.

Der – manchmal steinige – Weg zu DDD

Wenn das alles so großartig ist, warum machen wir dann nicht alle einfach DDD?

Lange Zeit haben sich Entwickler auf Technologien gestürzt. Je mehr man davon bei einer Bewerbung im Lebenslauf erwähnen könnte, desto besser waren die Chancen für eine Einstellung bei anständigem Gehalt. Businesslogik war eher Beiwerk und entstand aus den Anforderungen und dem Beheben von Bugs, wenn irgendetwas nicht richtig verstanden wurde. Aber Businesslogik half der Karriere nicht unbedingt weiter. In der nächsten Firma wollte man nichts mehr von Schläuchen oder Druckmaschinen wissen, sondern beschäftigte sich mit Bilanzen oder Rotoren. Was wirklich zählte, war das Beherrschen von Technologien. Heutzutage, durch das Entstehen von immer besserer Open-Source-Software und vielen sehr guten Frameworks ist Technikwissen einer Inflation verfallen. Viele Technologien sind leicht zu erlernen, und morgen sind sie schon veraltet. Was jetzt zählt, ist eine profunde Kenntnis der Business Domain. Wer hier punkten kann ist heute gefragt. Das ist aber in den Köpfen der Leute noch nicht angekommen und bedarf eines Umdenkens.

Auch Architektur wurde in den letzten Jahrzehnten vernachlässigt. Man nahm halt wie die meisten anderen auch die gute alte Schichtenarchitektur (vgl. Abb. 3). Diese musste alles richten. Sie löste zwar nicht wirklich die Probleme, die zu lösen waren, ließ sich aber einfach umsetzen. Leider hatte diese Architektur – und nicht nur wenn sie unsachgemäß benutzt wurde – üble Folgen. Es entstanden mit ihr, wie Martin Fowler sagt "blutarme Objekte" und mit ihnen Monolithen [4]. Schichtenarchitektur ist in vielen Fällen die Abkehr von der Objekt-Orientierung und ein Rückfall in die Zeit der Spaghetti-Software von C, Cobol oder Assembler. Diese Sprachen trennen Variablen von den darauf auszuführenden Operationen. Das wiederum steht im krassen Gegensatz zum Clean-Code-Prinzip von Robert C. Martin – "Tell don’t ask" [5,6] und führt zu Feature-Neid und schließlich zu Spaghetti-Code und Monolithen. Durch die Idee der Kohäsion und der Einführung der Objekt-Orientierung konnte man dieses Übel beseitigen. Im Zuge der Schichtenarchitektur wurde dieser Ansatz allerdings vielerorts durch ihr Entity-Controller-Boundary-Pattern zunichte gemacht. Architekten der Schichtenarchitektur denken in Services (Controllern) und Daten (Entities). Ein Umdenken in Objekten mit einer hohen Kohäsion, also der Zusammenführung von Daten und Operationen muss im Zuge der Verwendung von DDD wieder neu erlernt werden.

Mit welchen Mitteln lässt DDD diese Vorteile real werden?

Die Vorteile für die IT habe ich verstanden. Und das mit den Schwierigkeiten beim Umdenken auch. Aber lassen sich denn jetzt die Perlen von DDD finden, wenn es letztlich doch nur die Wiederentdeckung der Objekt-Orientierung ist?

Ubiquitous Language

DDD ist viel mehr. Zunächst legt es großen Wert auf eine einheitliche Sprache. Dies gilt nicht, wie oft falsch verstanden, über das ganze Projekt hinweg, sondern immer bezogen auf spezielle Teilbereiche im Unternehmen. Solche Bereiche hat es ja auch schon immer gegeben. Wir nennen sie Abteilungen, Bereiche oder auch manchmal abfällig Silos. Hier hat sich oft nicht nur eine eigene Sprache, sondern auch eine eigene Kultur etabliert.

Diese jeweils einheitliche Sprache führt in solchen Bereichen zu effektiven Arbeitsabläufen. Trifft diese Sprache jedoch auf die IT, kommt es wegen der großen Unterschiede zwischen "IT-Denglisch" und Fachchinesisch zu Missverständnissen und Fehlern. DDD schafft Lebensräume für eine einheitliche Sprache mit der jeweiligen Kultur, den sog. Bounded Contexts. Verschiedene Bereiche arbeiten, sprechen und ticken anders. DDD will sie nicht gleich machen, sondern lässt ihnen hier ihre Souveränität. Dass führt unter anderem auch dazu, dass man nicht ständig vermitteln muss, um einen einheitlichen Kompromiss zu finden, der keinen so richtig glücklich macht.

Core Domain

Ein weiterer wichtiger Aspekt ist das Auffinden der Core Domain. Viele Projekte behandeln jeden Teil der Software als gleich wichtig, egal ob Datenbankanbindung, GUI-Gestaltung oder die Umsetzung von Features. Letztere werden halt immer vom Scrum-Team eins nach dem anderen umgesetzt. Natürlich sind die frühen Backlog-Items wichtiger und höher priorisiert als die nachfolgenden. Aber umgesetzt werden sie immer vom gleichen Team in der gleichen Zeit, in einem Sprint. Ob es geschickt ist, die wichtigsten Features schon zu so einem frühen Zeitpunkt, wo das Wissen über das Projekt noch gering ist, umzusetzen, darf bezweifelt werden. Würde es nicht mehr Sinn ergeben, die besten und erfahrensten Entwickler diese Software entwickeln zu lassen und ihnen dafür so viel Zeit zu geben, wie sie brauchen? Wie wäre es, wenn man sie das gleiche Feature gleich dreimal hintereinander umsetzen ließe? Dabei würden bei jeder Iteration die Erfahrungen aus der letzten Iteration einfließen. Es handelt sich schließlich um den Teil der Software, der das Unternehmen zum Marktführer werden lässt. Lassen sich aus dem Code und der gemachten Erfahrung fachliche Patterns entwickeln, die es ermöglichen, wiederkehrende ähnlich gelagerte Probleme geschickt zu lösen, eben besser, als es die Konkurrenz kann? Nicht so wichtige Features kann man dafür von der Stange kaufen oder auch Offshore entwickeln lassen. In diesem Falle wäre Offshore auch sinnvoll, da hier kein relevantes Wissen verloren ginge.

In vielen Migrations-Projekten wird der Monolith erst von seinen vielen Abhängigkeiten befreit, bevor er dann eins zu eins in eine µService-Architektur überführt wird. Es wird versäumt, Ausschau nach der Core Domain zu halten und diese mit neuen Ideen und verbesserten Qualitätsansprüchen umzusetzen. Die Core Domain will gesucht werden. Sie ist nicht immer von vornherein ersichtlich. Erst wenn sie gefunden wurde, scheint sie – wie das Ei des Kolumbus – allen ganz klar zu sein.

Deep Insight

Ein weiterer sehr wichtiger Aspekt, der auch oft vernachlässigt wird, ist der Deep Insight. Wenn er gefunden wird, hilft er dabei, dass auf ganz viel überflüssigen Code verzichtet werden kann, dass Abläufe plötzlich viel leichter von der Hand gehen und dass die Software in eine neue Runde der gewonnenen Möglichkeiten geht. Ich möchte das an zwei einfachen Beispielen erläutern.

Eine lange Bahnfahrt wollen Sie dazu nutzen, einen Schachcomputer zu programmieren. Sie beginnen projekt-orientiert. Jede Figur hat einen Rang und eine Farbe und weiß, wo sie auf dem Brett steht. Also entledigen Sie sich des Spielfeldes, bloß keine Redundanzen. Das ist gewissermaßen Ihr Deep Insight Nr. 1. Kurze Zeit später machen Sie Ihren ersten Zug. Weißer Bauer e2-e4. Dazu müssen Sie schauen, ob vor dem Bauern die Felder frei sind. Leider müssen Sie dafür jetzt alle anderen Figuren befragen, ob diese im Wege stehen. Wäre schön, wenn Sie jetzt das Schachbrett einfach hätte befragen können. Deep Insight Nr.2: Ein Schachbrett ist doch ganz hilfreich. Leider führt das dazu, dass Deep Insight Nr.1 dadurch obsolet wird. Im weiteren Verlauf Ihrer Bahnreise arbeiten Sie an Engine und einigen Algorithmen. Weil sich der Zielbahnhof nähert, verzichten Sie auf die Tests, wodurch sich einige Fehler einschleichen, die dazu führen, dass der Kollege Schachcomputer manchmal sehr zu seinen Ungunsten spielt. Schließlich kommt Ihnen ein weiterer Deep Insight – der Breakthrough, der dazu führt, dass Sie in Ihren Algorithmen viel weniger Fehler haben, nebenbei auch viel Code sparen und zusätzlich auf das Schachbrett verzichten können: Die Einführung einer Graph-Datenbank. Sie müssen nicht mehr selber in Ihrem Code herausfinden, welche Felder frei oder bedroht sind und wie man die Stellung bewerten könnte, sondern lassen die Arbeit die Datenbank tun.

Ein zweites Beispiel: Um eine Anwendung für ein Car-Sharing-Unternehmen zu schreiben, muss man irgendwo die Reservierungen der Kunden für die Fahrzeuge speichern. Aus Gründen der Kohäsion würde man wohl eine Komponente Reservierungs-Management schreiben und sie die Verwaltung organisieren lassen. Dagegen ist nichts einzuwenden. Aus objekt-orientierter Sicht könnte man sagen, dass ein Kunde Reservierungen vorgenommen hat und diese dem Kunden zuordnen. Ebenso könnte man sagen, dass ein Kfz Reservierungen besitzt und sie diesem zuordnen. Auch das wäre okay. Man würde sich wahrscheinlich aus Gründen der Redundanz für den einen oder anderen Fall entscheiden, je nachdem was für die Use-Cases günstiger ist.

In DDD würde man sich wieder die Ubiquitous Language zu Nutzen machen und dem Kunden genau zuhören: "Schönen guten Tag, mein Name ist Woock, Kennung: nichtohnemich, und ich hätte gerne für morgen 16 bis 17 Uhr ein Auto am Jungfernstieg." Man könnte also die Station befragen, ob ein Auto frei ist. Die Station würde dann die Fahrzeuge, die ihr zugehörig sind, befragen. Dazu muss sie nicht wirklich die Kfz befragen, sondern könnte sich eine eigene Liste halten, in die sie schauen würde. Dafür müsste sie die in Frage kommenden Kfz herausfiltern und prüfen, ob sich die gewünschte Reservierung mit einer der vielen vorhandenen Reservierungen schneidet, überlappt, beinhaltet…  Insgesamt viele Abfragen für eine Reservierung. Für einen PC von heute kein großes Problem, aber in der Umsetzung dennoch fehleranfällig. Mit einem Breakthrough ließe sich das Problem auf folgende Art lösen:

Jedes Auto besitzt ein Array von Einheiten in z. B. halben Stunden, der kleinsten zu mietenden Einheit. Dieses Array sind Bool’sche Werte. True bedeutet belegt, false bedeutet frei. So ließen sich die vielen Abfragen nach Überschneidung etc. stark reduzieren. Jetzt machen wir aus den Bool’schen Werten einen Farbcode. Jedes Auto hat seinen eigenen Farbcode. Farbe bedeutet reserviert, keine Farbe bedeutet frei. Die Station hält die Farbmischung aller Autos in einem Array. Ist das blaue Auto belegt, befindet sich im Array die Farbe blau. Ist zusätzlich das gelbe Auto reserviert, so ist die Farbe im Array blau + gelb = grün. Orange würde z.B. bedeuten, dass das Blaue Auto noch zu haben wäre. Lila würde in unserem Beispiel bedeuten, dass das rote und das blaue Auto reserviert sind. Natürlich muss der Computer nicht mit Farben arbeiten, sonder kann das auch durch binären Code bewerkstelligen. Jedes Auto hätte dann eine Primzahl als Identifikation. Der Vorteil, der sich daraus ergäbe wäre, dass man an der Station direkt die Auslastung erkennen würde. Man müsste nicht erst die einzelnen Autos mit all ihren Reservierungen befragen. Daraus ergäbe sich, dass Autos von Stationen mit niedriger Auslastung zu Stationen mit hoher Auslastung umgestellt werden können. Das nennt Eric Evans ein effektives Design. Ein Design ist nach seinen Worten nie fertig, sondern ermöglicht das Entwickeln von neuen Ideen, wie hier die Auslastung.

Supple Design

Als letzte Perle von DDD möchte ich noch auf das Supple Design (geschmeidiges Design) zu sprechen kommen. Wie oft haben wir uns schon damit herumgeschlagen, mit Mitteln der Programmiersprache das Ende eines Monats zu berechnen. Schön ist das nicht. Welche Methode sagt mir, ob ein Auto pünktlich abgegeben wurde? Was heißt in dem Zusammenhang überhaupt pünktlich? Übereinstimmung der Nanosekunden? Oder ist Pünktlichkeit nicht eher mit einer Toleranz versehen (Diese ist in Deutschland womöglich geringer als anderswo..)? Was ist mit Ostern, jeden 2. des Monats oder am Nachmittag? Wäre es für den Entwickler nicht schön, wenn er nicht jedes Mal aus seinem Fluss bei der Umsetzung der Business-Logik herausgerissen wird, weil er unpassende APIs geradebiegen muss und mit der Fehlerbehandlung mehr Zeit verbringt als mit der Umsetzung von Features? DDD sorgt dafür, dass dem Entwickler die Funktionen zur Verfügung stehen, die er braucht, um seine Aufgaben möglichst einfach und auf geradem Wege umzusetzen. Das nennt sich auch im Gegensatz zum allbekannten UX (User Experience) DX (Developer Experience) und beschreibt ein Umfeld, in dem der Entwickler richtig Spaß an seiner Arbeit hat, weil sie ihm leicht von der Hand geht.

Hermann Woock bei den IT-Tagen 2020

Zum gleichen Thema hält Hermann Woock einen Vortrag auf den diesjährigen IT-Tagen – der Jahreskonferenz der Informatik Aktuell.

Domain-driven Design – Deep Dive
Mittwoch, 09.12., 16:00 Uhr

Voraussetzungen und weitere Stolpersteine

Welcher Voraussetzungen bedarf es, damit DDD in meinem Projekt einen Sinn ergibt? Nicht jede Firma ist dafür geeignet. Es benötigt

  • …die Bereitschaft des Lernenwollens, des Experimentierens und Forschens nach besseren Lösungen als der erstbesten.

  • …den Wunsch und die Möglichkeit nach Veränderung in der Firma, oft auch als Transformation verstanden. Veränderung muss aus dem Operativen heraus entstehen und nicht von "oben" vorgegeben werden.

  • …eine Wertekultur mit funktionierender Kommunikation, Fehlerkultur und gegenseitiger Wertschätzung.

Was DDD und µServices nicht bringen sind Synergien. Im Gegenteil; Sie schaffen Redundanzen und Mehrarbeit. Sie helfen nicht beim Einsparen von Ressourcen, sondern verbrauchen mehr davon. DDD hilft, komplexe und komplizierte Software in den Griff zu bekommen. DDD unterstützt darin, Software schnell auf den Markt zu bringen und zu experimentieren. Mit DDD kann ich meine Performance und Resilience steigern. Aber Ressourcen einsparen kann ich nicht!

Die Einführung von DDD stiftet viel Unruhe in der Firma, viel Veränderung – aber eröffnet auch viele Möglichkeiten, die Firma in das Zeitalter der Digitalisierung zu begleiten.

Quellen
  1. M. E. Conway; 1968: Conway's Law
  2. F. P. Brooks; 1975: The Mythical Man-Month. Addison-Wesley
  3. Prof. Dr. F. L. Bauer; 1968: Software Engineering. NATO Science Commitee, Garmisch, Germany
  4. M. Fowler; 2003: AnemicDomainModel
  5. D. Thomas, A. Hunt; 1999: The Pragmatic Programmer. Addison-Wesley
  6. R. C. Martin; 2008: Clean Code. Prentince Hall

Autor

Hermann Woock

Als Trainer, Berater und Coach arbeitet Hermann Woock seit über 10 Jahren bei der oose eG. Dabei beschäftigt er sich ganzheitlich mit der erfolgreichen Umsetzung von Enterprise-Projekten.
>> Weiterlesen
Das könnte Sie auch interessieren

Kommentare (0)

Neuen Kommentar schreiben