Schlanke JEE-Services mit Apache Meecrowave

Das Programmmodell von JEE (aka Java EE / Jakarta EE) ermöglicht schon seit vielen Jahren – namentlich seit der Version 5 – eine effiziente Entwicklung von schlanken Geschäftsanwendungen. Als Ablaufumgebungen stehen ausgereifte und leistungsfähige JEE-Server wie WildFly oder Open Liberty zur Verfügung, die als eine Art Allrounder den Anwendungen recht umfassende Infrastruktur anbieten und sogar mehrere Anwendungen gleichzeitig betreiben können.

Gerade der letzte Punkt, der Parallelbetrieb mehrere Anwendungen, wird allerdings nur selten genutzt, um Störeffekte zwischen Anwendungen zu minimieren. Es gibt also typischerweise genau eine Anwendung pro Server, der ggf. sogar selbst in einem dedizierten Rechner – meist einer virtuelle Maschine – läuft.

Wenn man nun noch einen nur eingeschränkten Teil der angebotenen Infrastruktur benötigt, bspw. JPA, CDI und JAX-RS, also den klassischen Microservice-Stack, kann man sich die Frage stellen, ob tatsächlich ein kompletter JEE-Server gebraucht wird, oder ob man nicht einfach die benötigten Dienste in die Anwendung einbetten könnte.

An dieser Stelle setzt Apache Meecrowave an. Dieses Apache-Projekt verknüpft den JPA-Provider EclipseLink, den CDI-Container OpenWebBeans und die REST-Implementierung CXF zu einem ganz schlanken Server – oder eigentlich zu einer Server-Bibliothek, mit deren Hilfe man den Server einfach als Main-Programm startet.

Zur Einbindung von Meecrowave reicht es aus, die folgende Dependency in den Classpath zu holen:

  
    org.apache.meecrowave
    meecrowave-core
    1.2.3
  

Der in der Anwendung eingebettete Server wird dann z. B. so gestartet:

  public static void main(String[] args) {
    try (Meecrowave meecrowave = new Meecrowave().bake()) {
      // Do work here ...
    }

Beim Eintritt in den try-Block wird der Server gestartet, beim Verlassen wieder gestoppt. Sämtliche aus CDI bekannte Mechanismen für den Bootstrap einer Anwendung können zur Aktivierung von Services etc. genutzt werden, z. B. Observer für den Container Lifecycle:

  void startSomething(@Observes @Initialized(ApplicationScoped.class) Object obj) {
    // Gets called on server start ...
  }

Ein einfacher REST-Service ist dann nur noch ein Ding von wenigen Zeilen:

@Path("hello")
@ApplicationScoped
public class HelloResource {

  @GET
  @Produces(MediaType.TEXT_PLAIN)
  public String getHello() {
    return "Hello!";
  }
}

So ist also dieser – zugegebenermaßen winzige – Microservice mit nur ganz wenig Code schon fertig. Und beeindruckt mit Startzeiten im Sekundenbruchteil-Bereich sowie einem Memory Footprint von nur ca. 12 MB.

Ein einfaches Demoprojekt kann von GitHub heruntergeladen werden: https://github.com/GEDOPLAN/meecrowave-demo.

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

Advertisements

Using GlassFish / Payara 5 with Eclipse Oxygen

If you want to try GlassFish or Payara 5 with one of the current Eclipse versions (Oxygen, which includes JBoss Developer Studio), you run into the problem that the GlassFish Tools plugin checks the GlassFish version and refuses to work with the current release.

As a workaround edit the manifest of glassfish-api.jar located in the modules directory of your GlassFish installation and change the Bundle-Version to 4.0.0.

After this modification you can connect GlassFish 5 to Eclipse via GlassFish Tools, run the server and deploy applications as usual.

See you in our Java EE trainings in Berlin, Bielefeld or at your site: gedoplan-it-training.de/


 

Identity and Equality of JPA Entities

JPA entities contain an id field (or property) annotaded with @Id. For this post I will assume a single field, but compound ids are possible as well.

Entity objects are business objects, so it is generally accepted that we will have to supply equals and hashCode for entity classes. Let me point out here, that it’s very unlikely for our business code to call any of these methods directly, because we don’t normally need explicit comparisons of entity objects in our business code, but we use collections which in turn may call equals and hashcode for looking up or adding entries.

We have several expectations regarding the semantics of equals and collection operations expecially when looking at entity objects:
We have several expectations regarding the semantics of equals and collection operations expecially when looking at entity objects:

  1. Objects based on the same database entry should be the same regarding equals and objects from different db rows should be different

a) including objects from different entity managers,
b) including detached objects,
c) including new (transient) objects,
d) even if some non-id attribute has been changed (they will end up in ‚their‘ db records!).

  1. Hashcode-based collections (e. g. HashSet) should behave friendly, i. e.

a) adding multiple objects should be possible – even for new (transient) objects,
b) the collection should remain intact even if contained objects get persisted into the db.

If the entity has a business id, i. e. the id attribute has some business meaning and is set by the constructor, the expectations can easily be met by using exactly the id attribute in equals and hashCode.

If you choose to compare all fields instead of just the id, your implementation breaks expectation 1.d). If you choose to not implement equals and hashCode at all and rather use the methods derived from Object, your implementation breaks expectations 1.a) and 1.b).

So, as an intermediate conclusion, implement equals and hashCode in your entity classes and base them on just the id attribute(s):


@Entity
public class Foo {

@Id
private String id;

...

public Foo(String id, ...) {
this.id = id;
...
}

public int hashCode() {
return (this.id == null) ? 0 : this.id.hashCode();
}

public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Foo other = (Foo) obj;
if (this.id == null) {
return other.id == null;
}
return this.id.equals(other.id);
}

Things get more complicated, if you want to use generated ids. JPA supports you in this with the annotation @GeneratedValue which has to be placed on the id attribute in addition to @Id. The generator works for integral number fields and it’s best to abstain from primitive types in order to have the additional value null for unset ids. So you would use Integer, Long or BigInteger depending on the amount of data entries expected:

@Entity
public class Foo {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;

The problems with generated ids looking at identity and equality arise from the fact, that the entity manager will set the ids late – they may be populated not until the commit of the inserting transaction.

To begin with you will use the id field for comparison in equals and in hashCode as you did for business ids before. But if you do that in exactly the way shown above, you break expectation 2.a), because equals will evaluate all new (transient) objects as equal, as their ids are all null.

You can fix this by modifying equals such that it returns false if any of the compared ids are still unset:

public boolean equals(Object obj) {
...
if (this.id == null) {
return false;
}
return this.id.equals(other.id);
}

While this now meets expectation 2.a), it still breaks expectation 2.b): If you add some new entities to a HashSet and persist them afterwards, the collection will be corrupt due to the changed hash codes.

And bad news: There is no way to get around this, if using generated ids! In many application this still is no issue, because the program sequence „Create multiple objects“, „add them to a hash-based collection“, „persist the objects“, „use the collection“ is not very common and can be circumvented by persisting (and flushing) the objects before they get added to the collection.

Please take into account, that the usage of a hash-based collection may not be directly visible and instead be hidden as association collection in some other entity class.

What are your options, if you want to use generated ids and your business logic suffers from the hash-based collection problem discussed above? Well, the solution is to use an id populated early, i. e. in the constructor, which can be generated easily and – most advisable for performance – without hitting the database. java.util.UUID offeres the desired functionalty. It supplies 128 bit integers commonly expressed as 36 character string (32 hex digits and 4 dashes). java.util.UUID uses random numbers as base and offers a distribution which makes duplicates very unlikely. Other implementations exist, which use MAC addresses and random numbers.

@Entity
public class Foo {

@Id
private String id;

private String description;

public Foo(String description) {
this.id = UUID.randomUUID().toString();
...

By using uuids you cherry-pick the advantages from having early set ids while still having generated values.

The size of uuids may be painful. They are roughly four times the size of an integer, but storage requirements depend on your database product. Remember that they are ids, so every foreign key in the db has the same size. If that is a problem, you can resort to having an early set uuid for supporting equals and hashCode, which is not annotated with @Id. Instead you add another integer field as JPA generated id, i. a. annotated with @Id @GeneratedValue. Now all tables contain a number as primary key as well as a non-primary uuid column, but all foreign keys are just numbers.

So, the subject of JPA ids, which seems easy at first glance, is far from that in reality. You may use the following recipe when designing entity classes:

  • If your entity contains some identifying business attribute, take this as id. Let equals and hashCode use exactly this attribute. All expectations expressed above will be met
    (-> ChemicalElement in showcase).
  • If you don’t find a suitable business id, and …
    • if the hash-based collection problem discussed above is no problem for your application, use a @Id @GeneratedValue annotated integer id. Let equals and hashCode use exactly this attribute, but modify equals such that unset ids render a false return value. All expectations expressed above will be met with the exception of 2.b)
      (-> LabExperiment3NonNullIdEquality in showcase).
    • you want / have to use early set ids, use uuids.
      • If db size does not really matter or your model contains just a few associations, use the uuids directly as JPA ids. Let equals and hashCode use exactly this attribute. All expectations expressed above will be met
        (-> LabExperiment4UUIdEquality in showcase).
      • If you care about the storage consumption of foreign keys in your database, use the uuid just for equals and hashCode and add a separate @Id @GeneratedValue id to your class. Let equals and hashCode use exactly the uuid attribute. All expectations expressed above will be met
        (-> LabExperiment5AddIdEquality in showcase).

There is a showcase on https://github.com/GEDOPLAN/jpa-equals-demo demonstrating the various options. You can find the referenced classes there.

See you in our trainings at Berlin, Bielefeld, Cologne or your site!
http://gedoplan-it-training.de/


 

New and noteworthy in Java EE 8

Am 18.09.2017 wurde nach langer Zeit des Wartens und auch der Unsicherheit über die Zukunft der Plattform die Version 8 der Java Enterprise Edition veröffentlicht. Diese im JSR 366 beschriebene Umbrella Specification umfasst diverse Änderungen an den in ihr enthaltenen Einzelspezifikationen, von den im Folgenden einige – die subjektiv wichtigsten – beschrieben werden:

Java API for JSON Binding 1.0 (JSON-B, JSR 367)

Durch diese neue Spezifikation enthält die Plattform erstmals die Möglichkeit der Serialisierung und Deserialisierung von Java-Objekten in bzw. aus JSON analog zu JAXB. Im Sinne von Zero Configuration gibt es Defaults für die Konvertierung von Attributen, natürlich inklusive der mit Java 8 eingeführten Datums- und Zeit-Typen. Von den Defaults kann abgewichen werden, indem Klassen und Attribute mit Annotationen versehen werden (z. B. @JsonbTransient zum Ausschluss von Attributen) oder indem eine global angepasste Konfiguration genutzt wird.

Java Servlet 4.0 (JSR 369)

Der Fokus der neuen Servlet-Spezifikation liegt auf der Unterstützung von HTTP/2. Der Datenaustausch zwischen Client (Browser) und Server kann dann mittels Header Compression, Request Multiplexing und Server Push effizienter gestaltet werden. Die API-Änderungen sind nur marginal, so dass Anwendungen in den Genuß der Optimierung einfach dadurch kommen, dass sie diese Version der Spezifikation nutzen. Die überwiegende Anzahl der Browser sind bereits HTTP/2-fähig.

JavaServer Faces 2.3 (JSF, JCP 372)

Das Paket javax.faces.bean ist nun endlich deprecated. Statt der JSF-eigenen Manage Beans setzt man nun vollständig auf CDI, was sich auch in der Injizierbarkeit von CDI Beans in JSF-Artefakte wie bspw. Konverter ausdrückt. Weitere Änderungen sind die Unterstützung von Java 8 Date/Time, Websocket Integration und die Möglichkeit feldübergreifender Validierung.

Contexts and Dependency Injection for Java 2.0 (CDI, JSR 365)

Ein CDI Container lässt sich nun auch im SE-Kontext betreiben. Dies drückt sich einerseits dadurch aus, dass die Spezifikation nun getrennte Abschnitte für SE und EE vorsieht. Andererseits ist mit Hilfe der Klasse SeContainer ein implementierungsneutraler Start und Stopp eines Containers möglich. Änderungen gibt es weiterhin bei der Verarbeitung von Events. Observer können nun eine Verarbeitungsreihenfolge erhalten oder asynchron bedient werden.

Bean Validation 2.0 (JSR 380)

Die Validierugsmöglichkeiten für Java Beans wurden um diverse neue vordefinierte Contraints ergänzt (bspw. @Email oder @Positive) und können nun auch auf Elemente von Collections angewendet werden. Schließlich werden nun auch Java-8-Klassen wie LocalDate oder Optional unterstützt.

Java API for RESTful Web Services 2.1 (JAX-RS, JSR 375)

Asynchronität ist in REST-Services schon seit der Vorversion ein Thema, das nun u. a. durch die Nutzung von CompletionStage weiter ausgebaut und resilienzfähig wird. Neu sind zudem der Support für PATCH Requests sowie Server Side Events.

Neben dieser willkürlichen Auswahl haben auch weitere Spezifikationen Änderungen erfahren.

Zu den genannten Punkten werden wir in lockerer Folge weitere Posts in diesem Blog machen. In diesem Sinne: Stay tuned!

Oracle schlägt Verlagerung von Java EE in die Eclipse Foundation vor

Seit nunmehr fast 20 Jahren ist die Java EE eine verlässliche Bank für den Aufbau und Betrieb von Enterprise-Anwendungen. Oracle – zuvor Sun – hat am Erfolg der Plattform zweifellos einen wesentlichen Anteil, zusammen mit den vielen Firmen, Einzelpersonen und der Community, die sich für Standards und deren Weiterentwicklung engagiert haben.

Nun hat sich die Welt aber weiter gedreht – manchmal scheint es, mit stetig wachsender Geschwindigkeit – und neue Anforderungen (Cloud, Microservices, … – you name it) verlangen nach einer Anpassung und Weiterentwicklung der Plattform in einer Taktung, die nicht mehr so recht zu einem in gewisser Weise zentralistischen Modell passen will.

Vor diesem Hintergrund erscheint die Ankündigung von Oracle, Java EE in die Eclipse Foundation zu verschieben, sehr sinnvoll. Sie ist vielleicht sogar überfällig, wenn man die Unsicherheit und Querelen der vergangenen Monate und Jahre betrachet.

Ich bin sehr zuversichtlich, dass dieser Schritt der richtige ist, um Java EE weiter verlässlich, stabil und zukunftsorientiert auszurichten.

Brauchen wir noch Java EE? (Oder: Magengrummeln auf der JAX)

Ich sitze gerade auf der JAX in Mainz und versuche mich vorsichtig wieder abzuregen. Aber der Reihe nach.

Auf Konferenzen hört man viele Buzz Words – da ist die JAX keine Ausnahme. Also hört man mehrere Zig Mal pro Tag die Worte Microservices und Container. Auch wenn die genaue Definition von ersterem nicht festgelegt ist, so meint man damit die Erstellung (oder auch Aufteilung) von Enterprise-Anwendungen in überschaubare Teile – im Sinne von DDD irgendetwas zwischen Aggregat und Bounded Context. Es handelt sich also um ein Pattern, das die Kopplung von Softwareteilen aus einem monolithischen Ansatz herauslöst und in die (Betriebs-) Infrastruktur verlagert. Dadurch gewinnt man zweifellos Flexibilität, kann also Anwendungsteile unabhängig voneinander deployen, unterschiedlich skalieren, ggf. sogar in unterschiedlichen Sprachen entwickeln. Das kommt natürlich nicht ohne Kosten wie Kommunikationsaufwand, Resilience, Schnittstellenevolution etc. Container in dem genannten Kontext meint in nahezu allen Fällen Docker, in aller Regel mit einer passenden Verwaltung und Orcherstrierung wie Kubernetes, OpenShift o.ä.

Das ist alles verständlich, spannend und nachvollziehbar. Was allerdings in meinem Augen ziemlich nervt, ist die allgegenwärtige Vermischung von Programm- und Betriebsmodell: Es erscheint als unumstößlliche Gewissheit, dass die alten, großen (und schlechten?)  monolithischen Anwendungen auf schwergewichtigen Java-EE-Servern betrieben werden, während man in einem Docker-Container nur leichtgewichtige (?) SE-Anwendungen laufen lassen kann, die mit allem anderen als Java EE entwickelt werden.

Dies wurde auch gerade in großen Teilen einer Konferenz-Session dargestellt, die den gleichen Titel trägt wie dieser Blog-Post. Ein paar Kernaussagen daraus:

  • „Kernbestandteile von Java EE sind Web (aka JSF), EJB, JCA, JMS. CDI ist als Bindeglied dabei“
    Das verkennt in meinen Augen nahezu komplett die Mächtigkeit von CDI. Dieser Standard/Container kann weit mehr als nur EJBs untereinander und mit der Web-UI zu verbinden. Über seine portable Extension-Schnittstelle ist eine fast grenzenlose Anpassung der gesamten Plattform möglich.
  • „Services baut man in Java EE mit EJB“
    Oder eben CDI, wenn man die speziellen Eigenschaften von EJB nicht benötigt (Timer, Remoting etc.). Wobei EJBs ja auch nicht schwergewichtig sind. Wer das behauptet, hat seit Java EE 5 (2009!) oder 6 nicht mehr drauf geschaut. Ein wesentlicher Punkt in Richtung Einfachheit ist doch Dependency Injection. Die kam für EJB mit der Version 3. Und seit 3.1 kann man auf Local Interfaces verzichten. Heute unterscheiden sich EJB und CDI Beans im Programmcode nur marginal und auch zur Laufzeit sind keine global signifikanten Vor- oder Nachteile zu verzeichnen.
  • „Mit Java-EE kann man nur auf relationale DBs zugreifen“
    Hä? Es gibt zwar kein Standard-API zum Zugriff auf NoSQL-DBs. Die sind ja ihrerseits auch noch gar nicht standardisiert. Aber man man doch das jeweilige proprietäre API benutzen!
  • „Die Struktur einer Java-EE-Anwendung ist starr und fix verknüpft, während in einem Container(-Cluster)  Services dynamisch verbunden und skaliert werden können“ (Man stelle sich dazu links eine Grafik eines Application Servers der Jahrtausendwende vor, rechts ein Schaubild einer aktuellen Kubernetes-Landschaft)
    Das vergleicht nun wirklich Äpfel mit Birnen (oder wie der Speaker es ausdrückte: Rüben mit Kraut). Das eine ist eine aus einem Build herausgefallene, monolithische Anwendung, das andere kleine Einzelservices in separaten Deployments. Warum sollten die Services auf der rechten Seite denn nicht mit dem Java-EE-Stack aufgebaut sein? Ein kleiner Service aus JAX-RS, CDI und JPA ist mit Java EE schnell geschrieben und läuft problemlos in einem Docker-Container. Auch der Footprint der Anwendung mit ca. 100 MB ist nicht wirklich schwergewichtig, auch wenn an der einen oder anderen Laufzeitumgebung vielleicht noch optimiert werden kann.
  • „Ihr müsst jetzt nicht auf Java EE verzichten, wenn eure Ansprüche an Durchsatz und Geschwindigkeit in einem niedrigen oder mittleren Bereich sind“
    Mal abgesehen davon, dass dieser Satz so ähnlich ist wie der, den man auf Sprüche-Karten findet („Das kannste schon so machen, aber dann isses halt Kacke“): Limitierungen in diesem Bereich liegen bspw. in synchronen Konzepten. So belegt ein JDBC-Treiber einen Thread, auch wenn er auf die Reaktion der DB wartet. Das lässt sich mit Aufteilung und Containern nicht verändern. Und im Web(-service)-Bereich bietet Java EE auch asynchrone Vorgehensweisen.

Ich will dem Speaker hier aber gar nicht unterstellen, dass er das so gemeint hat, wie ich (und nicht nur ich) das verstanden habe. Er hat nur den falschen Titel für seine Session gewählt: „Brauchen wir noch Java-EE-Server?“ wäre besser gewesen, denn in meinen Augen geht es ausschließlich um das damit verbundene Deployment- und Betriebskonzept. Das kann kann kritisieren, wenn man möchte. Allerdings läuft ein WildFly oder Payara auch wunderbar in einem Docker-Container – kaum unterscheidbar von einem alternativ darin liegenden Fat JAR.

Also: Tun wir uns allen den Gefallen, nicht die Spezifikation Java EE mit den darin enthaltenen Teilen JAX-RS, CDI etc. und die mögliche Ablaufumgebung Java-EE-Server zu vermischen.

Oder auch (mit einer kleinen Anleihe an eine bekannte Fernsehwerbung von früher): Nichts ist unmööööglich mit Java EE und Docker!

Rightsize your Services mit WildFly Swarm

WildFly Swarm

Überblick

Klassische Application Server wie JBoss/WildFly oder Glassfish/Payara sind etablierte und ausgereifte Ablaufumgebungen für Enterprise-Applikationen. Dass mehrere Anwendungen bis zu einem gewissen Grade voneinander isoliert in einem Server berieben werden können, wird in der Praxis wegen der unvermeidlichen Gemeinsamnutzung von Java VM, CPU, Memory etc. selten ausgenutzt. Man trifft also meist Server an, auf denen genau eine Anwendung deployt ist. Durch den Trend zu Microservices kommt es zu einer deutlichen Vergrößerung der Serveranzahl mit den daraus resultierenden Anforderungen im Bereich Installation, Konfiguration und Betrieb. Populäre Alternativen zu Java EE – z. B. Spring und Dropwizard – nutzen ein anderes Betriebsmodell: Statt Anwendungen in jeweils einen Server zu deployen, wird der Server in die Anwendungen integriert. Für die Java-EE-Welt bietet das Open-Source-Projekt WildFly Swarm (wildfly-swarm.io>) einen solchen Ansatz.

Komposition der Runtime

WildFly Swarm ist vergleichbar mit einem in Stücke zerteilten WildFly Application Server. Diese Stücke – sog. Fractions – werden als Maven Dependencies zur Anwendung hinzugefügt:

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.wildfly.swarm</groupId>
      <artifactId>bom-all</artifactId>
      <version>${version.wildfly.swarm}</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

<dependencies>
  <dependency>
    <groupId>org.wildfly.swarm</groupId>
    <artifactId>jaxrs-cdi</artifactId>
  </dependency>
  <dependency>
    <groupId>org.wildfly.swarm</groupId>
    <artifactId>jpa</artifactId>
  </dependency>
  ...

Die derzeit (Dez. 2016) aktuelle Version ist 2016.12.0.

Aus den so gewählten Fractions und dem eigentlichen Anwendungscode wird dann ein Uber Jar gebaut. Dies geschieht mit Hilfe eines Plugins in der Maven-Konfiguration des Projektes:

<build>
  <plugins>
    <plugin>
      <groupId>org.wildfly.swarm</groupId>
      <artifactId>wildfly-swarm-plugin</artifactId>
      <version>${version.wildfly.swarm}</version>
      <configuration>
        <mainClass>de.gedoplan.micro.bootstrap.SwarmBootstrap</mainClass>
      </configuration>
      <executions>
        <execution>
          <goals>
            <goal>package</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

Die o. a. Main Class dient der programmatischen Konfiguration der Anwendung (s. u.). In einfachen Fällen wird sie nicht benötigt. Das entsprechende Element der Plugin-Konfiguration entfällt dann.

Durch den Build-Lauf (z. B. per mvn package) entsteht dann im target-Verzeichnis neben dem normalen Zielartefakt xyz.war eine Datei namens xyz-swarm.jar, die die Anwendung inkl. Server Runtime enthält.

Konfiguration der Anwendung

Wenn die Default-Konfiguration nicht ausreicht, weil bspw. DB-Verbindungen eingerichtet werden müssen, kann eine individuell prgrammierte Main-Klasse genutzt werden. Darin können die einzelnen Fractions mit Hilfe eines Fluent API parametrisiert werden. Alternativ kann eine externe Konfigurationsdatei referenziert werden:

public class SwarmBootstrap {

  public static void main(String[] args) throws Exception {
    Swarm swarm = new Swarm();

    System.setProperty("swarm.context.path", "micro-ohne-server");

    URL standaloneFullXml = SwarmBootstrap.class.getClassLoader().getResource("configuration/standalone-full.xml");

    swarm
        .withXmlConfig(standaloneFullXml)
        .start()
        .deploy();
  }

Die im Beispiel genutzte externe Konfigurationsdatei entspricht der vom WildFly Application Server bekannten standalone-full.xml, muss aber nur die für die zu konfigurierenden Fractions benötigten Einstelldaten enthalten.

Anwendungsstart

Die Anwendung wird durch Ausführung des durch das o. a. Plugin erzeugte Uber Jar gestartet: java -jar target/xyz-swarm.jar.

Weitere Infos