Desktop-App mit Angular + Electron

Webanwendungen sind dieser Tage nicht mehr wegzudenken aus dem Geschäftsleben. So manche Anwendung die früher ihr Dasein als Swing- oder JavaFX-Applikation gefristet hat, wurden mittels Webtechnologien neu eingekleidet. Trotzdem sind native oder hybride Ansätze immer dann interessant wenn die Anwendung mehr möchte als die Sandbox des Browser es zu lässt. Ein Ansatz der sich großer beliebtheit erfreut: wir schreiben eine Web-Anwendung und „packen sie ein“. Was Ionic für mobile-Apps ist, stellt electron für die Desktop-Welt da. Zeit für einen kurzen Überblick.

Wir starten wie wir es von Angular gewohnt sind. Mittels Angular-CLI erzeugen wir ein neues Projekt, entwickeln Komponenten und Services und verwenden den Development-Server um das Ganze an den Start zu bringen und im Browser an zu zeigen / zu debuggen / zu testen.

Für die Verwendung von electron installieren wir nun zwei weitere Abhänigkeiten:

npm install --save-dev electron
npm install --save-dev electron-packager

Die Grundidee hinter electron, die wir uns bewusst machen sollten ist einfach ( und genial ): wir haben es später mit zwei Prozessen zu tun: zum einen der Renderer-Instanz die quasi unseren Browser darstellt und innerhalb dem unsere Angular-Anwendung läuft . Diese hat die „normalen“ Einschränkungen wie jeder Browser ( kein Zugriff aufs Betriebssystem ). Der Zweite Prozess mit dem die Angular Anwendung per IPC (Interprozesskommunikation) kommunizieren kann ist eine Node-Anwendung, welche alle „Freiheiten“ auf dem System hat.

Also los. Wir legen zwei neue Dateien an (in einem beliegen Ordner, meine Wahl: im Root des Verzeichnisses im Ordner ‚electron‘). Eine package.json für unsere electron „Projekt“. Abgesehen von den Abhänigkeiten handelt es sich dabei bei mir nahezu um eine Kopie der package.json meiner Angular Anwendung und enthält lediglich einige Basis Infos + die Angabe meiner „main“-JS:

{
  "name": "ng-gedoplan-electron",
  "version": "1.0.0",
  "main": "electron.js",
  "description": "Start-File für Electron-Anwendung",
  "author": {
    "name": "Dominik Mathmann, GEDOPLAN"
  }
}

Als zweiten Schritt legen wir diese main-JS an, electron.js:

const { app, BrowserWindow, ipcMain } = require("electron");
const url = require("url");
const path = require("path");

let mainWindow;

function createWindow() {
  mainWindow = new BrowserWindow({
    width: 600,
    height: 600,
    webPreferences: {
      nodeIntegration: true
    }
  });

  mainWindow.loadURL(
    url.format({
      pathname: path.join(__dirname, `index.html`),
      protocol: "file:",
      slashes: true
    })
  );
  // Open the DevTools.
  // mainWindow.webContents.openDevTools();
  mainWindow.setMenu(null);

  mainWindow.on("closed", function() {
    mainWindow = null;
  });
}

app.on("ready", createWindow);

app.on("window-all-closed", function() {
  if (process.platform !== "darwin") app.quit();
});

app.on("activate", function() {
  if (mainWindow === null) createWindow();
});

Hier steuern wir unsere electron App: unser „node-backend“. Das ist in unserem Beispiel sehr überschaubar und enthält leiglich einige Standard-Deklarationen. Da wir bisher keinerlei Interaktion mit dem Betriebssystem haben, kümmert sich das Script also lediglich mittels ein paar Listener um die Erzeugung eines Fenster und reagiert auf entsprechende Fenster-Events.

Das war es schon fast. Auf einen Fehler stoßen wir derzeit mit Angular 8, und zwar das die Scripte aus Security-Gründen nicht geladen werden können wenn das neue Modul-System von JS verwendet wird. Aktueller Workaround: Ziel-JS Version in der Angular-Anwendung tsconfig.json umstellen: „target“: „es5“, Nun müssen wir nur noch dafür sorgen das unsere Angular-App und electron zusammen finden. Das ist einfach: nach dem kompilieren von Angular braucht electron lediglich noch die beiden erzeugten Dateien um zu starten, die wir ihm mittels script in den Ziel-Ordner kopieren ( in meinem Beispiel mittels OS-unabhängigen copyfiles-npm)

"start-electron": 
   "ng build --base-href ./ 
    && copyfiles -u 1 electron/* dist/ng-gedoplan-electron 
    && electron dist/ng-gedoplan-electron"

(anstatt electron) können wir auch stattdessen den Packager verwenden:

 electron-packager dist/ng-gedoplan-electron --platform=win32 --out=dist

um in diesem Beispiel eine Windows-Anwendung zu generieren.

Das war es schon. Zugegeben aktuell haben wir kaum einen Vorteil unsere Anwendung in eine Desktop-App zu verpacken, den spannenden Teil: Zugriff auf das Betriebssystem schauen wir uns erst im nächsten Beitrag an :-). Trotzdem schon mal: hier. GitHub.

Werbeanzeigen

Angular Testing CodeCoverage

Wer Unit-Tests für seine Anwendungen schreibt ( also jeder von uns 😉 ) wird kaum in der Lage sein ohne Tooling zu Überblicken welche Teile der Software getestet wurden und welche nicht. Angular macht es uns da zum Glück sehr leicht. Dank einer simplen Option in der angular.json Konfiguration:

  ...
"test": {
          "builder": "@angular-devkit/build-angular:karma",
          "options": {
            ...
            "codeCoverage": true
          }
        },
...

Dank dieser wird bei jedem Test-Durchlauf ein neuer ein Bericht generiert der als HTML-Seite abgerufen werden kann:

[project-root]/coverage/index.html

Neben der groben Übersicht welche Pfade der Anwendung mit welcher Test-Abdeckung versehen sind lässt sich hier bequem bis auf Datei-Ebene navigieren um so die Stellen zu erkennen die möglicherweise noch nicht ausreichen mit Unit-Tests abgedeckt sind:

Welche Einstellung haben Sie? Anwendungskonfiguration mit MicroProfile Config

MicroProfile (https://microprofile.io/) enthält u. a. den Baustein Microprofile Config, mit dem JEE-Anwendungen aus verschiedenen Quellen heraus Konfigurationsparameter erhalten können. Neben einem API stehen auch Injektionsmöglichkeiten zur Verfügung.

Configuration API

MicroProfile Config ist durch Vorläufer-Projekte wie Apache Tamaya oder DeltaSpike Config beeinflusst und besitzt diverse Implementierungen wie SmallRye Config im WildFly. Zum portablen Zugriff auf den konkreten Provider dient die Klasse ConfigProvider und das Interface Config. Im einfachsten Fall kann ein Konfigurationswert so erfragt werden:

Config config = ConfigProvider.getConfig();
String value = config.getValue("java.version", String.class);

Config Sources

Die Konfigurationswerte werden diesen Quellen entnommen (in absteigender Priorität):

  • System Properties
  • Environment Variables
  • Classpath resource(s) META-INF/microprofile-config.properties

Nach Wunsch können die Prioritäten angepasst und zusätzliche Konfigurationsquellen ergänzt werden.

Injektion von Konfigurationswerten

Für die bequeme Nutzung in CDI Beans steht der Qualifier @ConfigProperty zur Verfügung:

@Inject
@ConfigProperty(name = "answer")
int answer;

Sollte eine solche Property-Referenz nicht befriedigt werden, d. h. sollte es im konkreten Beispiel keinen konfigurierten Wert mit dem Namen answer geben, schlägt das Deployment der Anwendung fehl. Ist das nicht erwünscht, kann als Injektionsziel ein Optional genutzt werden:

@Inject
@ConfigProperty(name = "HOME")
Optional home;

Plattformen

MicroProfile Config wird u. a. von folgenden JEE-Servern unterstützt:

  • OpenLiberty (mit aktiviertem Feature mpConfig-1.3).
  • Payara.
  • WildFly.

Die genannten Klassen befinden sich bspw. in der Maven-Dependency org.eclipse.microprofile.config:microprofile-config-api (derzeit in Version 1.3).

Demo

In https://github.com/GEDOPLAN/microprofile-demo/tree/master/microprofile-config finden Sie ein Beispielprojekt mit einem REST-Service, der API-Nutzung und Injektionsmöglichkeiten zeigt. Die Anwendung kann als WAR-File auf einen der genannten Server deployt werden. Alternativ können über vorkonfigurierte Maven-Profile Docker-Images zum direkten Ausprobieren erstellt werden.

Bis bald – vielleicht auch in einem unserer Trainings in Berlin, Bielefeld, Köln oder bei Ihnen!
https://gedoplan-it-training.de/

Angular Testing mit Spies

Unit Testing gehört zum guten Ton eines jeden Projektes. Angular tut sein bestes um es dem Entwickler so leicht wie möglich zu machen dieses Thema im Projektalltag unter zu bringen. Es existieren mehrere Möglichkeiten Komponenten losgelöst zu testen. Eine davon: ‚Spies‘

Schauen wir uns zuerst noch einmal kurz in groben Zügen das Testing von Angular an sich an ( weiterer Blog beitrag zu diesem Thema hier ) . Sofern wir es nicht deaktiviert haben generiert die Angular CLI zu jeden unserer Komponenten, Service etc. eine zusätzliche TypeScript Datei mit der Dateiendung: spec.ts . Diese Datei ist der Ausgangspunkt unseres Unit-Tests und enthält einen ersten sehr rudimentären Test ob die Komponente erstellt werden kann:

describe('LocalStorageTextAreasComponent', () => {
  let component: LocalStorageTextAreasComponent;
  let fixture: ComponentFixture<LocalStorageTextAreasComponent&gt;;

  beforeEach(async(() =&gt; {
    TestBed.configureTestingModule({
      declarations: [ LocalStorageTextAreasComponent ]
    })
    .compileComponents();
  }));

  beforeEach(() =&gt; {
    fixture = TestBed.createComponent(LocalStorageTextAreasComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create', () =&gt; {
    expect(component).toBeTruthy();
  });
});

Schauen wir uns die wichtigen Bestandteile noch mal an und machen uns klar welche Aufgabe sie haben:

TestBed . Erzeugt ein eigenständiges NGModul welches zum Testen herrangezogen wird. Das bedeutet das wir hier wie gewohnt mit declarations, imports und providers deklarieren was wir verwenden wollen. Im Fall unserer Unit Tests besteht hier z.B. die Möglichkeit Objekte die wir per DI erhalten zu mocken:

    TestBed.configureTestingModule({
      providers: [
        { provide: RecordService, useValue: { save: () => { } } },
        { provide: ProjectService, useValue: { getAll: () => { } } },
      ]
    })

fixture.componentInstance liefert uns dann die konkrete Instanz die wir zum Testen herran ziehen können. Diese Komponente hat natürlich unter umständen Abhänigkeiten und verwendet z.B. Services die bei unseren Tests in aller Regel nicht aufgerufen werden sollen: wir wollen ja unsere Komponente testen und nicht die Services die sie verwendet. Neben der Erzeugung eines kompletten Mock-Objektes und die Verwendung mittels providers-Array existiert noch die Möglichkeit einen Spy zu verwenden. Quasi ein „Beobachter“ der für Methoden registriert wird und über den wir dann,zum einen, Prüfen können ob die Methoden aufgerufen wurden und zum anderen können wir auch dafür sorgen das Test-Werte geliefert werden anstatt den Aufruf an die echte Methode zu delegieren.

  it('close should save', () => {
    const emitSpy = spyOn(component.saved, 'emit');

    const saveSpy = spyOn(component['recordService'], 'save').and.returnValue(of({ id: 100 }));

    component.form.get('id').setValue(null);
    component.form.get('start').setValue(new Date());

    component.beforeunload(null);
    expect(saveSpy).toHaveBeenCalled();
    expect(emitSpy).toHaveBeenCalledWith({ id: 100 });

  });

Zwei Beispiel dafür sehen wir hier. Zeile 2 registriert einen Spy der auf die „save“ Methode der zu testen Komponente registriert wird. Wir lassen den Aufruf zu dieser Methode zu, können aber über den Spy am Ende prüfen ( Zeile 11 ) ob die Methode aufgerufen wurde + bei Bedarf unter Angabe von konkreten Parametern die wir erwarten.

Zeile 4 zeigt nun die Verwendung von einem Spy um Service-Aufrufe zu unterbinden. Ähnlich wie im ersten Beispiel registrieren wir uns mittels spyOn Methode unter Angabe des Objektes und dem Namen der Methode. Hier verknüpfen wir den Spy jedoch mittels and.returnValue und liefern dann ein Observable der ein Dummy-Objekt zurück liefert. Damit können wir die Komponente testen ohne das die Service-Methode aufgerufen wird.

Angular – WebWorker

Der Browser dient schon lange nicht mehr nur als einfaches Anzeigeinstrument für Webseiten. JavaScript übernimmt mehr und mehr Aufgaben die früher auf dem Server abgewickelt werden mussten, dank performanter Endgeräte aber zunehmend auch im Browser behandelt werden. Nun ist es aber so das JavaScript single-threaded ist und CPU-intensive Funktionalitäten zum einfrieren der GUI führen. Abhilfe? WebWorker!

CSS-Animation + rechenintensive Aktion. Einmal ohne ( submit ) und einmal mit webworker

WebWorker ermöglichen es uns parallelen Code aus zu führen, also abseits des Threads der sich um die Abwicklung der GUI Interaktion kümmert. Technische Vorraussetzung dafür ist das der Code des WebWorkers in einer separaten Datei abgelegt wird und mittels asyncronen Event-Handling mit der Anwendung kommuniziert. Insbesonderer der erste Teil hat es vor der Angular Version 8 schwierig gemacht mit diesen zu arbeiten, sorgt Angular durch seinen Buildprozess für das Zusammenführen aller Dateien. Seit Angular 8 unterstützt die Angular-CLI aber die Generierung für WebWorker:

ng generate web-worker [name]

Neben der Erstellung einer entsprechenden Worker-Datei bereitet Angular unsere Anwendung auch für diese vor. So wird neben einer Anpassung an der CLI( angular.json ) auch die TypeScript-Konfiguation angepasst um das spätere Zusammenführen der Dateien zu umgehen. Ein WebWorker besteht dann aus einer einfachen Funktion die Parameter entgegen nimmt und und über eine ensprechende Funktion eine Rückgabe liefern kann:

addEventListener('message', ({ data }) =&gt; {
  for (let i = 0; i < data.times; i++) {
    data.result = data.result * data.multiplier;
  }
  postMessage(data.result);
});

Die Verwendung dieser Funktion ähnelt dann ein wenig der Verwendung von asycronen Service-Methoden, mit dem Unterscheid das wir im Vorfeld explizit ein WebWorker Objekt erzeugen:

  calculateWW() {
    const worker = new Worker('./multiplier.worker.ts', { type: 'module' });
    worker.onmessage = ({ data }) => {
      this.form.get('result').setValue(data);
    };

    this.form.get('result').setValue(this.form.get('initValue').value);
    worker.postMessage(this.form.value);
  }

So registrieren wir einen EventListener der benachrichtigt wird wenn der Worker eine Rückgabe liefert ( Zeile 4 ) und rufen den Worker mit frei wählbaren Parametern auf( Zeile 8 )

Github? Github !

Angular + OpenAPI Generator

In einem älteren Posting haben wir schon einmal einen Blick auf Swagger geworfen, eine charmante Möglichkeit aus den eigenen Rest-Schnittstellen eine technisch auswertbare OpenAPI Beschreibung zu generieren. Swagger bietet neben dieser Core-Möglichkeit noch eine ganze Reihe mehr, z.B. die Generierung von Client-Stubs. Das entsprechende Projekt ( Codegen ) wurde gefühlt eine ganze Zeitlang nicht aktiv verfolgt. Inzwischen lebt die Idee in einem Community getriebenem Projekt weiter: OpenAPI Generator. Werfen wir einen kurzen Blick auf die Verwendung im Zusammenspiel mit Angular.

Die Open API Spezifikation ist eine Standardbeschreibung für Schnittstellen die im JSON oder YAML Format Rest Schnittstellen (inklusive URL, Authentifizierung und Datenmodell) beschreibt. Eine Verwendungsmöglichksit ist die Generierung von Client-Code der die Entwicklung z.B. eines Anuglar-Clients erleichtert.

Der OpenApi Generator bietet hierbei unter anderem mit einem NPM-Tool welches ganz einfach per Kommandozeile installiert werden kann

npm install @openapitools/openapi-generator-cli -g

ein einfacher Aufruf zur Generierung von Angular-Sourcen könnte dann so aussehen:

openapi-generator generate -g typescript-angular -o src/app/api -i ./openapi.json

auf Basis der übergebenen API Spezifikation werden nun entsprechende Type-Script Sourcen generiert für

unsere Model-Klassen:

/**
 * The version of the OpenAPI document: 3.2
 *
 * NOTE: This class is auto generated by OpenAPI Generator 
(https://openapi-generator.tech).
 * Do not edit the class manually.
 */


export interface Project { 
    id?: number;
    projectId?: string;
    projectName?: string;
}

und eine zentrale Service-Klasse, die solche Methoden entsprechend unserer Resourcen generiert (unter Berücksichtigung von Pfadparametern und Rückgabewerten) (vereinfachte Darstellung):

public getAllProjects(limit?: number, first?: number): Observable<Project[]&gt; {
    ...
        return this.httpClient.get<Array<Project&gt;&gt;(`${this.configuration.basePath}/resources/project`,
            {
                params: queryParameters,
                withCredentials: this.configuration.withCredentials,
                headers: headers,
                observe: observe,
                reportProgress: reportProgress
            }
        );
    }

Was nun lediglich noch fehlt für den Projektalltag ist die

  • Konfiguration der Base-URL, vorzugsweise über einen Provider, hier z.B. über die environmen.ts
@NgModule({
 ...
  providers: [
    {
      provide: BASE_PATH,
      useValue: environment.baseurl
    }
  ]
  ...
  • Login Credentials über HTTP-Interceptor hinzufügen
@Injectable({
  providedIn: 'root'
})
export class HttpJwtInterceptorService implements HttpInterceptor {
  constructor(private loginService: LoginService) {}

  intercept(req: HttpRequest<any&gt;, next: HttpHandler): Observable<HttpEvent<any&gt;&gt; {
    const token = this.loginService.token;

    if (token) {
      req = req.clone({
        setHeaders: {
          Authorization: 'Bearer ' + this.loginService.token
        }
      });
    }
    return next.handle(req);
  }
}


// + Registrierung im Modul
@NgModule({
 ...
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      multi: true,
      useClass: HttpErrorInterceptorService
    }
  ]
  ...

Grundsätzlich eine sehr coole Sache, die uns viel Tipp-Arbeit spart. Im Hinterkopf sollte man aber immer behalten das wir damit unser Projekt auf zwei Generatoren stützen: 1) Swagger-Core um unsere OpenAPI zu generieren und 2) OpenAPI Generator um unsere Angular Artefakte erstellen zu lassen . Hier werden wir über kurz oder lang bei „komplexeren“ Schnittstellen an den Punkt kommen an dem einer der Generatoren nicht mehr das liefert was wir uns wünschen. Hier heißt es dann gegebenenfalls „händisch“ nach zu arbeiten. Eine Möglichkeit im Anuglar: generell mit einem Service-Delegate arbeiten: damit zentralisieren wir den Zugriff auf die generierten Teile unserer Anwendung und können hier auch zusätzliche Funktionalitäten implementieren ( z.B. „Caching“) und fachliche Gruppen („ProjektService“, „CustomerService“) ausprägen, auch wenn das wieder ein klein wenig Tipparbeit ist 😉

@Injectable({
  providedIn: 'root'
})
export class ProjectService {
  private projects: Project[];

  constructor(private service: DefaultService) {
  }

  getAll(limit?: number): Observable<Project[]&gt; {
    if (!this.projects) {
      return this.service.getAllProjects(limit).pipe(tap(r =&gt; (this.projects = r)));
    } else {
      return from([this.projects]);
    }
  }
}

JEE – Ende mit Schrecken oder Schrecken ohne Ende?

Die Geschichte von Oracle und Jakarta EE

Im Herbst 2017 entschloss sich Oracle zur Überführung der Java EE in ein Open-Source-Projekt unter dem Dach der Eclipse Foundation. In meinem Beitrag https://javaeeblog.wordpress.com/2017/09/13/oracle-schlaegt-verlagerung-von-java-ee-in-die-eclipse-foundation-vor/ von damals war ich zuversichtlich, dass dieser Schritt Java EE aus dem starren Korsett des JCP befreien und der Plattform die Modernität und Agilität ermöglichen würde, die neben der Verlässlichkeit und Stabilität in aktuellen Projekten benötigt wird.

Erste Zweifel kamen mir bei der Nachricht, dass Oracle die Rechte am Namen „Java EE“ nicht abgeben würde. Als Folge davon müssen alle Vorkommen von „Java EE“ durch den neuen Namen „Jakarta EE“ ersetzt werden. Nun ist Textersatz nicht wirklich kompliziert, also was soll’s?

Es verbleibt aber genauso das geistige Eigentum an den bislang von Oracle geführten Spezifikationen in den Händen von Oracle, so dass sich nach meiner Beobachtung nun viele Monate lang JEE-Experten damit beschäftigen durften, Spezifikationen so umzuschreiben, dass sie zwar inhaltlich kompatibel bleiben, aber keine schützenswerte Originalformulierung mehr enthalten.

Diese Zeit hätte man sehr gut in die Weiterentwicklung der Plattform stecken können. Glücklicherweise gibt es das Projekt MicroProfile (https://microprofile.io/), das in der letzten Zeit ganz und garnicht paralysiert herumgelegen hat, sondern mit wirklich hoher Innovationsgeschwindigkeit JEE-Erweiterungen entwickelt hat, die dringend notwendig sind, um JEE in der Greifvogelvoliere von immer kleineren Services irgendwo in den Wolken flugfähig zu halten.

Nun kommt aber noch der dicke Brocken, dass man sich nicht auf eine Weiterentwicklung des Paketnamensraums javax einigen konnte: Die bisherigen Pakete dürfen nur unverändert weiter genutzt werden, Neu- und Weiterentwicklungen müssen in neuen Paketen (jakarta) geschehen. Einfacher Textersatz ist hier natürlich wiederum möglich, bricht aber die Kompatibilität bestehender Anwendungen und Bibliotheken mit der neuen, paketumbenannten Plattform.

Wer sich nun mit Häme zu Aussagen wie „JEE ist eh Sch…, habe ich doch schon lange gesagt. Mit Spring ist alles besser!“ versteigt, sollte mal nachschauen, wieviele javax er in seinem Code findet.

Mir erschließt sich nicht, welchen Wert Oracle in den zurückgehaltenen Rechten sieht. Für mich als Entwickler und Berater scheint der Wert zukünftig bei 0 $ zu liegen. Und dafür erzeugt man große Verärgerung und Kosten bei Kunden und Anwendern? Nun bin ich kein Jurist, insbesondere kein Markenrechtler. Ich muss das also nicht verstehen.

Blick nach vorne

Was kann man nun in dieser verfahrenen Situation tun? Man könnte eine Koexistenz von javax und jakarta anstreben, Neuerungen wie gefordert in jakarta einführen und javax als Basis nutzen. Basis im Sinne von Basisklasse bzw. Basisinterface bedeutet aber eine (zu) enge Kopplung an die alten Versionen: API-Änderungen müssten sich i. W. auf Erweiterungen beschränken und man würde die ganzen alten Zöpfe weiter mit sich herumschleppen.

Es wird also darauf hinauslaufen, dass betroffene Pakete von javax.xyz in jakarta.xyz umbenannt werden. Das bedeutet zwar breaking Changes, bietet aber die Möglichkeit, veraltete APIs abzustoßen. Die erste Version, die dies betrifft, ist Jakarta EE 9, denn Jakarta EE 8 ist vollständig kompatibel zu Java EE 8, nutzt also weiterhin die bestehenden javax-Pakete. Es wird derzeit diskutiert, ob die Umbenennung der Pakete in einem Big Bang geschehen soll oder inkrementell immer dann, wenn Änderungen an einer Teilspezifikation durcheführt werden sollen. Ersteres hätte den Charme, mit einem – allerdings großen – Schritt sofort Unabhängigkeit zu erreichen. Bei der inkrementellen Vorgehensweise könnte man darauf spekulieren, dass sich manche Teile der Plattform zumindest in näherer Zukunft gar nicht ändern werden und man den Aufwand in diesem Bereich also sparen könnte. Ein Festlegung der Marschrichtung sollte noch im Juni 2019 geschehen.

Die Kompatibilität von Anwendungen älterer Versionen zu neuen Laufzeitplattformen ist einer der großen Werte der JEE. Ihn gilt es auf jeden Fall zu erhalten. Ich bin mir sicher, dass die Anbieter der modernen JEE-Server wie WildFly, OpenLiberty, Payara oder TomEE Mittel finden werden, z. B. durch Bytecode-Manipulation zur Deployment-Zeit, Altanwendungen mit javax-Paketen auf Jakarta-EE-9+-Servern lauffähig zu erhalten.

Das Ganze erzeugt nicht gerade unerheblichen Aufwand. Er ist es aber wert, angenommen zu werden. Kaum eine Plattform für Enterprise-Anwendungen hat ein ähnliches Gewicht, eine ähnliche Verbreitung in Projekten bei gleichzeitiger Flexibilität hinsichtlich der Laufzeitplattform – ob klassische Server wie die oben genannten oder Micro-Frameworks wie KumuluzEE, Meecrowave oder Quarkus. Wenn nun juristische Verkrustungen entfallen und die weitere Entwicklung ohne Korsett geschehen, kann’s doch nur super werden!

Bis bald – vielleicht auch in einem unserer Trainings in Berlin, Bielefeld, Köln oder bei Ihnen!
https://gedoplan-it-training.de/