Unser Blog ist umgezogen

Wir haben eine neue Adresse für unser Blog: https://gedoplan.de/javablog/.

Diese Seite wird noch für eine Weile online bleiben, aber neue Posts gibt’s nur noch dort.

Angular 16/17: signals

Diese Reihe wirft einen kurzen Blick auf einige Highlights der Angular Versionen 16 und 17. Heute: „signals“.

Bereits in Version 16 enthalten und mit Version 17 als „stable“ deklariert sind „signals“ der neue reaktive Weg Daten in unseren Controllern zu verarbeiten. Neben diesem reaktiven Ansatz, der uns in vielen Fällen davon entbindet, mit zusätzlichen Observables zu arbeiten, wurde bei der Entwicklung auf die Optimierung der ChangeDetection abgezielt, die in Zukunft auf Basis von signals weiter optimiert wird.

Signals sind einfach ausgedrückt, ein generischer Wrapper der (ähnlich wie ein BehaviourSubject) einen initialen Wert entgegennimmt, auf dessen Änderung sich andere Programmteile registrieren können:

 someId = signal(1);
 material = signal({id: 5, name: 'some material'});

Der Abruf erfolgt dann als Methodenaufruf

<p>Id from signal:  {{material().id}}</p>

(Allgemein wird von jeglichen Methodenaufrufen aus Gründen der Performance/ChangeDetection im Template abgeraten. Im Fall der Signals (genau wie bei gettern) wird ausdrücklich „Entwarnung“ gegeben).

Aktualisierungen der signal-Werte erfolgt über spezielle Methoden. Dabei wird empfohlen, mit immutable objects zu arbeiten, um unerwünschte Seiteneffekte bei der Weiterverarbeitung der Daten zu verhindern. Zwei Methoden existieren

  • update: Wertezuweisung auf Basis des alten Wertes
  • set: direkte Wertzuweisung
  • mutate: in Angular 17 entfernt
    this.material.update(current => {
      const newMaterial = Object.assign({}, current);
      newMaterial.id++;
      return newMaterial;
    })

    //or
    this.material.set({
      id: 200,
      name: 'some other'
    });

Neben dem einfachen Lesen des Wertes lässt sich nun noch sehr einfach auf die Änderung der signal-Werte reagieren, ähnlich wie wir es mit einem zusätzlichen Observable/Subject realisieren würden.

  • computed: erzeugt ein signal, welches immer dann aktualisiert wird, wenn sich ein enthaltendes signal ändert
  • effect: listener der aufgerufen wird, immer dann, wenn sich ein enthaltendes signal ändert
 materialLabel = computed(() => `${this.material().id}:${this.material().name}`);

  constructor(myService: MyService) {
    effect(() => {
      console.log('Value has been changed...');
    })
  }

Sollten wir innerhalb unserer signals mit mutable objects arbeiten, ist etwas Vorsicht geboten. Im Standard erfolgt die Erkennung, ob sich der Wert eines signals geändert hat anhand von Objektgleichheit (===), „compute“ und „effect“ Abhängigkeiten werden also nicht aktualisiert, wenn wir z.B. Objekt-Attribute ändern. Bei der Deklaration des signals können wir jedoch zusätzlich einen equals-callback angeben, mit dessen Hilfe wir entscheiden können, ob ein Objekt sich geändert hat

material = signal({id: 5, name: 'some material'}, {equal: (a, b) => a.id === b.id});

Soweit so gut. Signals werden in Zukunft ein integraler Bestandteil unserer Angular-Anwendungen werden und die Möglichkeiten sind noch lange nicht ausgeschöpft. Besonders interessant: die Verwendung von signals als Alternative zu klassischem @Input-/@Output-Binding, die auf der Roadmap der Entwickler steht ( https://github.com/angular/angular/discussions/49682 ) Bleiben wir gespannt.

Wie immer. Live. In Farbe. bei Github

WildFly Bootable JAR – JEE-Server im Projekt integrieren

Application Server werden häufig als schwergewichtig characterisiert, was aber nicht an ihrer Größe liegt, sondern daran, dass man zum Betrieb einer Enterprise-Applikation zunächst einen Server installieren und konfigurieren muss, bevor die Anwendung darauf deployt werden kann.

Man kann die Vorbereitung allerdings auch im Anwendungsprojekt erledigen und automatisieren, sodass der zusätzliche Aufwand im Betrieb entfällt. Das geht bspw. mit einem Maven-Plugin für WildFly:

      <plugin>
        <groupId>org.wildfly.plugins</groupId>
        <artifactId>wildfly-jar-maven-plugin</artifactId>
        <version>10.0.0.Final</version>
        <configuration>
          <feature-packs>
            <feature-pack>
              <location>wildfly@maven(org.jboss.universe:community-universe)#27.0.1.Final</location>
            </feature-pack>
          </feature-packs>
          <layers>
            <layer>jaxrs</layer>
            <layer>cdi</layer>
          </layers>
          <output-file-name>wildfly-bootable.jar</output-file-name>
        </configuration>
        <executions>
          <execution>
            <phase>package</phase>
            <goals>
              <goal>package</goal>
            </goals>
          </execution>
        </executions>
      </plugin>

Ein mvn package erzeugt nun zusätzlich zum normalen Ergebnis des Builds – i. A. ein war-File – die Datei wildfly-bootable.jar, die den Server inklusive der Anwendung enthält. Sie kann mit java -jar wildfly-bootable.jar gestartet werden.

Wie oben zu sehen ist, wird der Server aus sog. Layers zusammengesetzt. Somit kann man also einen Server genau auf die Anwendung zuschneiden und die Teile weglassen, die nicht benötigt werden.

Weitere Möglichkeiten sind u.a.:

  • Erstellung eines sog. Hollow JARs. Es enthält nur den provisionierten Server. Die Anwendung wird dann beim Start mittels Kommandozeilenparameter bestimmt.
  • Ausführen von Konfigurations-Skripten (jboss-cli), z. B. um bestimmte Datasources einzurichten oder Logging-Einstellungen vorzunehmen.
  • Starten von Anwendung und Server im Development-Modus mit Hot Reloading von Code-Änderungen.

Weitere Details s. https://docs.wildfly.org/27/Bootable_Guide.html.

Wir nutzen das Plugin und insb. den Development Mode seit einiger Zeit in unseren Seminaren. Die Erfahrungen sind sehr gut. Angenehm ist, dass die Projekte ihre Serverkonfiguration mitbringen und für die Weiterentwicklung der Anwendungen keine besondere Integration in die IDE vonnöten ist.

Jakarta NoSQL

Die Jakarta NoSQL Spezifikation hat das Ziel den Zugriff auf NoSQL-Datenbanken zu vereinheitlichen. Wollte man bisher bspw. eine MongoDB in seine Enterprise-Anwendung integrieren, musste man den proprietären Client dazu verwenden. Dieser arbeitete mit den entsprechenden Datentypen der MongoDB und den entsprechenden Zugriffsmechanismen. Damit verbunden ist immer eine Einarbeitung in die entsprechenden Eigenheiten eines solchen Clients.

Jakarta NoSQL liegt nun in einer ersten produktiven Version vor und wird voraussichtlich mit dem nächsten Jakarta EE 11 Release im nächsten Jahr veröffentlicht. In diesem Blogbeitrag möchte ich ein einfaches Beispiel für die Anbindung an eine dokumentenorientierte NoSQL-Datenbank beschreiben.

Document API

Wie üblich im Persistenzumfeld starten wir mit einer Entität:

@Entity
public class Customer {
    @Id
    private long id;
    @Column
    private String name;
    @Column
    private String street;
    @Column
    private String postcode;
    @Column
    private String city;
}

Der Zugriff auf die Datenbank kann nun entweder über eine Template-Klasse (DocumentTemplate) erfolgen. Oder es kann auf Basis der Jakarta Data Spezifikation ein Repository-Ansatz für den Zugriff gewählt werden.

Jakarta Data

Mit der Jakarta Data Spezifikation wurde ein vereinheitlichter Zugriff auf unterschiedliche Datenquellen definiert. Damit ist es möglich eine Implementierung auf Basis eines Interfaces automatisch generieren zu lassen.

@Repository
public interface CustomerRepository extends CrudRepository<Customer, Long> {
    public List<Customer> findByName(String name);
}

Das CrudRepository-Interface definiert dabei die grundlegenden Zugriffsmethoden. Alle weiteren Methoden kann ich über eine entsprechende Namenskonvention ebenfalls automatisch implementieren lassen.

Die Verwendung kann dann einfach über eine DI an gewünschter Stelle erfolgen:

@Inject
CustomerRepository customerRepository;
...
customerRepository.findByName("Horst");

Fazit

Die Kombination von Jakarta NoSQL und Jakarta Data ermöglicht den einfachen Einsatz von NoSQL-Datenbanken in Enterprise-Anwendungen. Über die Vereinheitlichung der NoSQL-Persistenzschicht und der Daten-Zugriffsschicht ist eine herstellerunabhängige Nutzung von NoSQL möglich.

Das Beispiel befindet sich in unserem Github-Repository.