Die Pro­gram­mier­spra­che Java ist als alt­ein­ge­ses­se­ne „Industrie-Sprache“ bekannt. Sie wurde bereits in den An­fangs­jah­ren des Internets kon­zi­piert, doch in der Zwi­schen­zeit hat sich das Web rasant wei­ter­ent­wi­ckelt. Neben der klas­si­schen Client-Server-Ar­chi­tek­tur existiert eine Reihe span­nen­der al­ter­na­ti­ver Modelle: Con­tai­ner­ba­sier­te An­wen­dun­gen, Mi­cro­ser­vices, Ser­ver­less-Computing und reaktive Web-Apps haben sich im Main­stream durch­ge­setzt. Diese Arten von An­wen­dun­gen ließen sich mit Java bisher nur schwer rea­li­sie­ren; mit dem Quarkus-Framework hat sich dies nun geändert. Der RedHat-Manager Ken Johnson hat das so be­schrie­ben:

Zitat

„… it’s a very small Java stack, perfect for con­tai­ners, ser­ver­less and other scenarios where you’re running Java ap­pli­ca­ti­ons in the cloud.“ – Ken Johnson, Quelle: https://www.openshift.com/blog/quarkus-is-here-for-your-java

„Quarkus“ ist ein kleiner Java-Stack, perfekt für Container, Ser­ver­less und andere Szenarien, bei denen Java-An­wen­dun­gen in der Cloud laufen.“ (Über­set­zung: IONOS)

Wir stellen Quarkus vor und zeigen, wie das Framework das Erstellen von Java-An­wen­dun­gen re­vo­lu­tio­niert.

Was ist das Besondere an Quarkus?

Quarkus ist ein von der Firma RedHat ent­wi­ckel­tes Framework zur Er­stel­lung von Java-An­wen­dun­gen. Quarkus wurde mit dem Ziel ent­wi­ckelt, Java-Programme in Con­tai­nern aus­zu­füh­ren. Ins­be­son­de­re liegt das Augenmerk auf der Un­ter­stüt­zung der Or­ches­trie­rungs-Software Ku­ber­netes. Ein weiterer Fokus der Quarkus-Ent­wick­lung liegt auf der Nutzung eta­blier­ter Java-Bi­blio­the­ken und Standards.

Als Aus­füh­rungs­schicht für den Java-Code kommt „HotSpot“ vom OpenJDK-Projekt als Java Virtual Machine (JVM) zum Einsatz. Ferner lässt sich die darauf auf­bau­en­de Ent­wick­lung „GraalVM“ nutzen. Letztere erlaubt das Kom­pi­lie­ren des Java-Codes in direkt aus­führ­ba­ren Ma­schi­nen­code. Um den un­mit­tel­ba­ren Vorteil bei der Nutzung von Quarkus nach­zu­voll­zie­hen, schauen wir uns zunächst an, wie Java-An­wen­dun­gen mit und ohne Quarkus aus­ge­führt werden.

Wie wurden Java-An­wen­dun­gen tra­di­tio­nell aus­ge­führt?

Die Grundidee, die Java bei der Ein­füh­rung re­vo­lu­tio­när machte, war so einfach wie be­stechend: Java sollte es er­mög­li­chen, ein Programm zu schreiben, ohne an eine spe­zi­fi­sche Hardware oder ein Be­triebs­sys­tem gebunden zu sein. Diese Platt­form­un­ab­hän­gig­keit wird oft unter dem Sprich­wort „write once, run anywhere“ („einmal schreiben, überall ausführen“) zu­sam­men­ge­fasst. Die damit ein­her­ge­hen­de Por­ta­bi­li­tät erlaubt, das Programm zwischen Platt­for­men zu bewegen. Ein toller Trick! Doch wie kann das funk­tio­nie­ren?

Wie in anderen Pro­gram­mier­spra­chen auch beginnt ein Java-Programm mit dem men­schen­les­ba­ren Quelltext. Um die An­wei­sun­gen des Quell­tex­tes auf einem Computer aus­zu­füh­ren, werden dazu kor­re­spon­die­ren­de In­struk­tio­nen im Format des spe­zi­fi­schen Pro­zes­sors erzeugt. Bei Java kommt noch ein Zwi­schen­schritt hinzu: Der Quelltext wird – wie bei der Sprache Python – zunächst in ein in­ter­me­diä­res Format, den so­ge­nann­ten Bytecode, übersetzt. Der Bytecode wird in der „Java virtual machine“ (JVM, „Java virtuelle Maschine“) aus­ge­führt. Um ein Java-Programm auf einem Gerät aus­zu­füh­ren, muss auf dieser also eine JVM in­stal­liert sein.

Zum Ausführen in der JVM wird der Bytecode tra­di­tio­nell in­ter­pre­tiert. Dabei werden die Bytecode-An­wei­sun­gen Stück für Stück in Maschinen-Code-In­struk­tio­nen übersetzt und aus­ge­führt. Per­for­man­ter ist der Prozess der „Just-in-time com­pi­la­ti­on“ (JIT, „Kom­pi­lie­rung zum passenden Zeitpunkt“). Hierbei wird der Bytecode ebenfalls in Ma­schi­nen­code um­ge­wan­delt, es kommen jedoch weitere Op­ti­mie­run­gen zum Tragen. Zum Ausführen eines Java-Programms gehören also die folgenden Schritte:

  1. Java-Quelltext mit dem Java-Compiler-Befehl 'javac' zu Bytecode kom­pi­lie­ren:
javac java_program.java
  1. Java-Bytecode mit dem Java-Runtime-Befehl 'java' ausführen – dabei wird Maschinen-Code erzeugt:
java java_program
Hinweis

Wir sprechen hier von einer „vir­tu­el­len Maschine“. Obwohl der Begriff derselbe ist, ist damit keine Tech­no­lo­gie zur Vir­tua­li­sie­rung eines Be­triebs­sys­tems gemeint. Statt­des­sen wird in­ter­me­diä­rer Code in Maschinen-Code übersetzt.

So praktisch Javas „write once, run anywhere“-Modell auch ist, der Ansatz birgt einige Schwächen: Die Nutzung der JVM bringt einen nicht un­er­heb­li­chen Overhead mit sich. Zum einen wird zum Starten der JVM eine gewisse Zeit benötigt, die zur Laufzeit der ei­gent­li­chen App hin­zu­kommt. Zum anderen ergibt sich neben einem höheren Spei­cher­ver­brauch eine Einbuße an Per­for­mance. All dies spielt eine wenig kritische Rolle bei An­wen­dun­gen, die lange laufen. Jedoch ist der Ansatz für kurz­le­bi­ge, con­tai­ner­ba­sier­te An­wen­dun­gen wenig geeignet. Diese sollen idea­ler­wei­se so schnell wie möglich starten; eine Startzeit von mehreren Sekunden ist in­ak­zep­ta­bel.

Wie werden Java-An­wen­dun­gen mit Quarkus aus­ge­führt?

Im Gegensatz zur nativen Aus­füh­rung von Java-An­wen­dun­gen bieten sich mit Quarkus einige Vorteile. Un­ter­schei­den wir die beiden von Quarkus un­ter­stütz­ten Modi:

  1. Op­ti­mie­rung des Bytecodes und Ausführen in der JVM
  2. Ausführen als nativer Code nach Kom­pi­lie­rung

Mit Quarkus ge­schrie­be­ner Java-Code lässt sich ganz normal auf der JVM ausführen. Jedoch ergeben sich be­acht­li­che Vorteile in Bezug auf Spei­cher­ver­brauch und Startzeit der laufenden Anwendung. Um dies zu erreichen, bedient sich Quarkus einiger Tricks. Ins­be­son­de­re wird eine Reihe zeit­auf­wen­di­ger Schritte von der Aus­füh­rung in den Build-Prozess ver­scho­ben. Dazu gehören die ansonsten bei jeder Aus­füh­rung einer Java-Anwendung ab­lau­fen­den Schritte:

  • Laden und Parsen von Kon­fi­gu­ra­tio­nen
  • Scannen des Java-Klassen-Pfads und Auflösen von An­no­ta­tio­nen
  • Ggf. Erstellen von Entitäten-Modellen für Da­ten­ban­ken o. ä.

Mit Quarkus werden diese Schritte einmalig durch­ge­führt und die Resultate für den schnellen Abruf zwi­schen­ge­spei­chert. Als weitere Per­for­mance-Op­ti­mie­rung reduziert Quarkus die Menge der zur Laufzeit dynamisch vor­lie­gen­den In­for­ma­tio­nen. Diese werden durch ent­spre­chen­de statische Kon­struk­te ersetzt. Das ist ins­be­son­de­re im Hinblick auf den Einsatz in Con­tai­nern sinnvoll. Denn eine con­tai­ne­ri­sier­te Anwendung wird für ge­wöhn­lich ohnehin nicht verändert und läuft immer in derselben Umgebung.

Der zweite von Quarkus un­ter­stüt­ze Modus zur Aus­füh­rung von Java-An­wen­dun­gen ist noch in­ter­es­san­ter. Bei der „Ahead-of-time com­pi­la­ti­on“ (AOT) wird aus dem Java-Quelltext statt Bytecode direkt aus­führ­ba­rer Ma­schi­nen­code erzeugt, d. h. es wird auf der Ziel-Hardware keinerlei JVM mehr benötigt. Dafür läuft das Programm nur auf einer spe­zi­fi­schen Pro­zes­sor­ar­chi­tek­tur und muss für andere Platt­for­men neu kom­pi­liert werden. Für den Einsatz in Con­tai­nern ist diese Ein­schrän­kung in der Regel jedoch un­er­heb­lich. Die bei der AOT-Kom­pi­lie­rung erzielten Er­spar­nis­se an Spei­cher­ver­brauch und Startzeit der Anwendung sind geradezu atem­be­rau­bend. Ver­glei­chen Sie die hier dar­ge­stell­ten Per­for­mance-Richt­wer­te von der of­fi­zi­el­len Quarkus-Homepage:

Anwendung Szenario Spei­cher­ver­brauch Zeit zur ersten Antwort
Quarkus + AOT REST 12 MB 0.02 s
Quarkus + AOT REST + CRUD 28 MB 0.04 s
Quarkus + JIT REST 73 MB 0.94 s
Quarkus + JIT REST + CRUD 145 MB 2.03 s
Cloud-Native Stack REST 136 MB 4.3 s
Cloud-Native Stack REST + CRUD 209 MB 9.5 s
Hinweis

Zur Ter­mi­no­lo­gie: Mit REST ist gemeint, dass nur ein Webserver im Container läuft. Beim Szenario REST + CRUD läuft neben dem Webserver eine Datenbank. Beim Cloud-Native Stack enthält der Container neben der Java-Anwendung eine JVM.

Wofür nutzt man Quarkus?

Bei Quarkus handelt es sich nicht bloß um ein weiteres An­wen­dungs-Framework. Statt­des­sen will die Software neu de­fi­nie­ren, was es bedeutet, mit Java An­wen­dun­gen zu ent­wi­ckeln. Rufen wir uns in Er­in­ne­rung: Tra­di­tio­nell war es wichtiger, dass eine Java-Anwendung lange Zeit stabil läuft. Wie lange die Anwendung zum Starten benötigte, war un­kri­tisch.

Denken wir nun an con­tai­ner­ba­sier­te An­wen­dun­gen: Neue Container werden ggf. durch Or­ches­trie­rungs-Software au­to­ma­tisch gestartet. Die im Container be­find­li­che Anwendung soll dann sofort ein­satz­be­reit sein. Ferner werden häufig für einen Service mehrere red­un­dan­te Container gestartet. Die mit Quarkus erzielte Ver­rin­ge­rung des Res­sour­cen-Ver­brauchs mul­ti­pli­ziert sich dem­entspre­chend.

Der RedHat-Manager Alex Handy fasst das so zusammen:

Zitat

„When you think of ser­ver­less computing, mi­cro­ser­vices and the […] cloud, there’s one language you’re probably not [thinking of]: Java. And that’s a real shame. […] Java was and is the workhorse language of business. It remains the third most popular language in the world […] It’s been the language of choice for cor­po­ra­ti­ons that need to keep a single ap­pli­ca­ti­on up and running for years at a time.“ – Alex Handy, Quelle: https://the­news­tack.io/quarkus-gives-spring-boot-users-a-path-to-ser­ver­less-and-live-coding/

„Wenn Sie an Ser­ver­less-Computing denken, an Mi­cro­ser­vices oder die Cloud, dann gibt es eine Sprache, an die Sie wahr­schein­lich nicht denken: Java. Und das ist echt schade. Java war und ist das Ar­beits­tier der Wirt­schaft. Sie ist weiterhin die dritt­be­lieb­tes­te Pro­gram­mier­spra­che der Welt und ist seit langem die erste Wahl für Un­ter­neh­men, die eine einzelne Anwendung jahrelang am Laufen halten müssen. (Über­set­zung: IONOS)

Die Vorteile von Quarkus liegen auf der Hand. Jedoch bringt das Framework auch einige Li­mi­ta­tio­nen mit sich. Daher ist Quarkus nicht primär dafür gedacht, exis­tie­ren­de Java-Ap­pli­ka­tio­nen zu migrieren. Vielmehr lohnt es sich, Quarkus als Aus­gangs­punkt für eine Neu­ent­wick­lung ein­zu­set­zen. Im Folgenden schauen wir uns ein paar konkrete Ein­satz­ge­bie­te an. Bei allen genannten Bei­spie­len kommt als Build-Tool Maven oder Gradle zum Einsatz. Man legt das Ein­satz­ge­biet per Kon­fi­gu­ra­ti­on des 'mvn'- oder 'gradle'-Befehls fest. Das Build-Tool erzeugt dann au­to­ma­tisch die be­nö­tig­ten Kon­fi­gu­ra­tio­nen und Artefakte.

Mit Java und Quarkus Mi­cro­ser­vice-An­wen­dun­gen in Ku­ber­netes rea­li­sie­ren

Bei Ku­ber­netes handelt es sich um eine Or­ches­trie­rungs-Software für Container-An­wen­dun­gen. Beliebt ist der Einsatz von Ku­ber­netes mit Docker-Con­tai­nern. Einzelne Services einer Anwendung werden als Docker-Image ge­spei­chert und von Ku­ber­netes verwaltet. Dabei übernimmt der Or­ches­trie­rer das Ma­nage­ment der aus den Images erzeugten Container: Ku­ber­netes startet, steuert und überwacht die Services. Häufig werden zur Last­ver­tei­lung und für erhöhte Feh­ler­to­le­ranz mehrere Kopien eines Service gestartet. Stürzt einer der Services ab, wird der Container zerstört und ein neuer Container aus demselben Image erzeugt. Quarkus bringt die für den Einsatz in Ku­ber­netes not­wen­di­gen Kon­fi­gu­ra­tio­nen von Hause aus mit.

Mit Java und Quarkus REST-APIs und Ser­ver­less-An­wen­dun­gen rea­li­sie­ren

Bei REST handelt es sich um den alt­ein­ge­ses­se­nen Ar­chi­tek­tur-Stil für Web­an­wen­dun­gen. Ins­be­son­de­re APIs werden meist diesem Ansatz folgend im­ple­men­tiert. Einer REST-API zugrunde liegt die Client-Server-Ar­chi­tek­tur. Dabei läuft die Kom­mu­ni­ka­ti­on über das HTTP-Protokoll unter Einsatz der „Verben“ GET, POST, PUT, DELETE. Diese kor­re­spon­die­ren zum aus dem Datenbank-Umfeld bekannten CRUD („create, read, update, delete“). Der Da­ten­aus­tausch zwischen API und Nutzer erfolgt meist per JSON.

Ser­ver­less-Computing ist eine al­ter­na­ti­ve Ar­chi­tek­tur für cloud­ba­sier­te An­wen­dun­gen. Bei diesem auch als „Function as a Service“ (FaaS) bekannten Modell läuft eine einzelne Funktion kurz­fris­tig in einem Container. Die Funktion wird auf­ge­ru­fen, führt eine Be­rech­nung durch und wird dann wieder ab­ge­schal­tet. Trotz des Namens laufen die Ser­ver­less-Funk­tio­nen weiterhin auf Servern. Nun muss man sich als Pro­gram­mie­rer nicht mehr um diese kümmern. Mit AWS Lambda, Google Cloud Functions und Microsoft Azure Functions stehen Ser­ver­less-Um­ge­bun­gen auf allen großen Cloud-Platt­for­men bereit. Mit Quarkus lässt sich Java-Code auf diesen Platt­for­men einsetzen.

Tipp

Erstellen Sie Ihre eigene REST-API – auf einem Dedicated Server von IONOS.

Mit Java und Quarkus reaktive Web-Apps erstellen

Die reaktive Pro­gram­mie­rung stellt im Gegensatz zur im­pe­ra­ti­ven Pro­gram­mie­rung ein modernes Pro­gram­mier­pa­ra­dig­ma dar. Man be­schreibt, welche Aktionen ablaufen sollen, wenn bestimmte Er­eig­nis­se eintreten. Die wohl be­kann­tes­ten Vertreter dieses Pro­gram­mier­stils sind die in Ja­va­Script ver­fass­ten Frame­works „React“ und „Vue“. Der Fokus liegt bei beiden auf dem Erstellen web­ba­sier­ter Be­nut­zer­schnitt­stel­len. Mit Quarkus lassen sich An­wen­dun­gen im im­pe­ra­ti­ven und reaktiven Stil rea­li­sie­ren. Es ist sogar möglich, beide Pa­ra­dig­men zu kom­bi­nie­ren.

Wo wird Quarkus ein­ge­setzt?

Quarkus wurde mit dem Ziel kon­zi­piert, Java-An­wen­dun­gen für den Einsatz in Con­tai­nern und Cloud-Um­ge­bun­gen zu op­ti­mie­ren. Durch die Mög­lich­keit, ein Java-Programm direkt in Maschinen-Code zu kom­pi­lie­ren, ergeben sich jedoch noch weitere spannende Ein­satz­mög­lich­kei­ten. Wir be­trach­ten die derzeit in­ter­es­san­tes­ten Ein­satz­ge­bie­te für Quarkus.

Erinnern wir uns zunächst, wie ein mit Quarkus ent­wi­ckel­tes Java-Programm aus­ge­führt wird. Während des Build-Prozesses wird der Java-Quelltext in Bytecode kom­pi­liert, der bei der Aus­füh­rung in Maschinen-Code übersetzt wird. Mit Quarkus lässt sich Bytecode erzeugen, der dann in einer Java-Lauf­zeit­um­ge­bung wie der HotSpot VM per In­ter­pre­ta­ti­on oder Just-in-time(JIT)-Kom­pi­lie­rung aus­ge­führt wird. Dabei kommen je nach Kon­fi­gu­ra­ti­on ver­schie­de­ne per­for­man­ce­re­le­van­te Op­ti­mie­run­gen zum Tragen.

Zum anderen kann die auf HotSpot ba­sie­ren­de GraalVM ein­ge­setzt werden, um mittels Ahead-of-time(AOT)-Kom­pi­lie­rung ein natives Image zu erzeugen. Beim nativen Image handelt es sich um eine Bi­när­da­tei, die alle zur Aus­füh­rung der Anwendung not­wen­di­gen Bi­blio­the­ken und Ab­hän­gig­kei­ten enthält. Da zur Aus­füh­rung keine JVM benötigt wird, ergeben sich aus der AOT-Kom­pi­lie­rung die größten Per­for­mance-Zugewinne.

Java-An­wen­dun­gen in Container-Um­ge­bun­gen

Meist kommt beim Einsatz einer Java-App in Con­tai­nern Ku­ber­netes zum Einsatz. Eine als Docker-Image verpackte Java-App lässt sich auch auf einem OpenShift-Cluster einsetzen. Den Einsatz von Quarkus mit Ku­ber­netes können Sie auch selbst aus­pro­bie­ren, z. B. mit einer Minikube-In­stal­la­ti­on auf Ihrem lokalen System.

Java-Funk­tio­nen in Ser­ver­less-Um­ge­bun­gen

Nutzen Sie Quarkus, um un­kom­pli­ziert eine in Java ge­schrie­be­ne Funktion in den Ser­ver­less-Um­ge­bun­gen von Amazon, Google und Microsoft ein­zu­set­zen.

Java-Programme in ein­ge­bet­te­ten Systemen

Mit der Mög­lich­keit, ein natives Image aus einer Java-Anwendung zu erstellen, lässt sich Java-Code auch auf ein­ge­bet­te­ten Systemen nutzen. Hierbei kommt die AOT-Kom­pi­lie­rung zum Einsatz, die im konkreten An­wen­dungs­fall für geringen Spei­cher­ver­brauch und schnelle Start­zei­ten sorgt.

Tipp

Nutzen Sie Managed Ku­ber­netes von IONOS für Ihre Container-Apps.

Quarkus im Vergleich mit anderen Frame­works

Quarkus eignet sich für ein breites Spektrum un­ter­schied­li­cher Ein­satz­sze­na­ri­en. Andere Frame­works sind z. T. spe­zi­fi­scher. Schauen wir uns ein paar ver­gleich­ba­re Al­ter­na­ti­ven an:

  • React: Das Ja­va­Script-Framework hat sich als Standard für die na­mens­ge­ben­de reaktive Pro­gram­mie­rung etabliert.
  • Open Liberty: Das von IBM stammende Framework erlaubt ebenfalls die Ent­wick­lung von Mi­cro­ser­vice-An­wen­dun­gen mit Java. Wie Quarkus bringt Open Liberty eine Live-Reload-Funk­tio­na­li­tät mit.
  • Micronaut: Mit dem Micronaut-Framework lassen sich in Java Mi­cro­ser­vices und Ser­ver­less-An­wen­dun­gen pro­gram­mie­ren. Dabei kommt wie bei Quarkus die GraalVM zum Einsatz.
  • Spring / Spring Boot: Bei Spring handelt es sich um das wohl be­lieb­tes­te Java-Framework für Web­an­wen­dun­gen. Spring setzt auf der GraalVM auf und un­ter­stützt neben der Er­stel­lung von Mi­cro­ser­vices die reaktive Pro­gram­mie­rung und Live-Reload. Im Per­for­mance-Vergleich schlägt Quarkus Spring; ein exis­tie­ren­des Spring-Projekt lässt sich relativ kom­for­ta­bel zu Quarkus migrieren.

Was sind die Vor- und Nachteile von Quarkus?

Der her­aus­ra­gen­de Vorteil bei der Ent­wick­lung von Java-An­wen­dun­gen mit Quarkus ist der Zugewinn an Per­for­mance. Dieser kommt ins­be­son­de­re beim Einsatz von Java-An­wen­dun­gen in Container-Um­ge­bun­gen zum Tragen. Zu den Per­for­mance-Vorteilen zählen:

  • Schnelle Startzeit der Anwendung
  • Geringer Spei­cher­ver­brauch der laufenden Anwendung
  • Beinahe un­mit­tel­ba­re Ska­lie­rung von Services
  • Geringer Platz­be­darf der nativen Images

Neben den Per­for­mance-Vorteilen glänzt Quarkus vor allem durch seine Be­nut­zer­freund­lich­keit. Der Einsatz des Frame­works ist für erfahrene Java-EE- und Spring-Ent­wick­ler leicht zu erlernen. Dem kommt weiterhin zugute, dass Quarkus auf einem solidem Grund­ge­rüst aufsetzt. Zum Einsatz kommen u. a. die folgenden Standard-Tech­no­lo­gien:

  • Eclipse Mi­cro­Pro­fi­le
  • Spring De­pen­den­cy Injection
  • Hibernate ORM

Ferner bietet Quarkus eine Live-Coding-Umgebung, in der Ent­wick­ler schnell pro­to­ty­pi­sie­ren können. Zur rei­bungs­lo­sen Ent­wick­lung trägt das Live-Reload-Feature bei: Nach Ak­ti­vie­rung des Dev-Modus werden Än­de­run­gen an Quelltext und Kon­fi­gu­ra­ti­on im Hin­ter­grund kom­pi­liert. Der Ent­wick­ler muss nur noch das Brow­ser­fens­ter neu laden, um die Än­de­run­gen nach­zu­voll­zie­hen.

Kommen wir am Schluss noch zu den Nach­tei­len beim Einsatz von Quarkus. Diese ergeben sich haupt­säch­lich aus den Op­ti­mie­run­gen, die beim Kom­pi­lie­ren zum Tragen kommen.

  • Ins­be­son­de­re die Ver­rin­ge­rung der dynamisch zur Laufzeit erzeugten In­for­ma­tio­nen kann in manchen Szenarien zu Problemen führen.
  • Die stark ein­ge­schränk­ten Mög­lich­kei­ten zur In­tro­spek­ti­on er­schwe­ren u. U. das Debugging einer Anwendung.
  • Der hoch­op­ti­mier­te Build-Prozess für native Images nimmt viel Zeit in Anspruch.

Quarkus ist nicht für jedes beliebige Java-Projekt gedacht. Der Einsatz des Frame­works erfordert z. T. die Um­stel­lung von Prozessen.

Zum Hauptmenü