Angular strukturiert 2/2 – @NgModule

Umfangreiche Angular Projekte bestehen in aller Regel aus sehr vielen Komponenten und Service-Klassen die in eine sinnvolle Struktur gebracht werden müssen. Darüber hinaus macht es auch aus Performance Gründen Sinn die eigene Anwendung zu unterteilen. Im ersten Teil haben wir einen Blick auf die Organisation mittels Workspace geworfen. Heute schauen wir uns die Verwendung von Modulen an.

Jede Angular Anwendung besteht aus einem NgModule. Bei genauerer Betrachtung eher aus sehr vielen Modulen, ist doch auch die Core-Bibliothek von Angular in Modulen aufgeteilt ( BrowserModule. FormsModule … ) die wir bei Bedarf importieren. Aber auch unsere eigene Anwendung können wir mittels Module in einzelne Blöcke unterteilen. Neben der besseren Struktur unserer Anwendung hat das noch einen ganz entscheidenden Vorteil: Performance. Wir können komplexe Angular Anwendung in fachliche Teilbereiche gliedern und für jeden dieser Teile ein eigenes Modul implementieren. Ein User der unsere Anwendung aufruft wird nun nicht beim ersten Zugriff unsere gesamte Anwendung laden müssen sondern nur den Bereich ( Modul ) den er gerade anfragt. Erst wenn der User über entsprechende Navigation auf andere Bereiche zugreift werden diese Anwendungsteile geladen. Hier in Kürze wie das geht:

1. ng new [app-name]

Neue Anwendung erzeugen, die in unserem Beispiel nur die Startseite beinhaltet +  gemeinesame Komponente + gemeinsame Services

2. ng generate module [module-1]

Neues Modul generieren, z.B. „materials“ oder „customers“ oder „users“

3. Routing anpassen

const routes: Routes = [
  { path: '', redirectTo: 'home', pathMatch: 'full' },
  { path: 'home', component: HomeComponent },
  { path: 'customer', 
   loadChildren: './customers/customers.module#CustomersModule' },
  { path: 'material', 
   loadChildren: './materials/materials.module#MaterialsModule' }
];

Der spannende Teil ist hier die Deklaration der Navigation auf die neuen Module, über die Eigenschaft „loadChildren“ wird hier der relative Pfad zur Module Datei angegeben + der Klassenname des Moduls (getrennt mittels „#“). Der Build Prozess resultiert dann in separaten JavaScript Dateien für die jedes Modul welche nur beim auslösen der entsprechenden Navigation abgerufen wird.

Zusammen mit Teil 1 hier auf github

Werbeanzeigen

Angular strukturiert 1/2 – workspace

Umfangreiche Angular Projekte bestehen in aller Regel aus sehr vielen Komponenten und Service-Klassen die in eine sinnvolle Struktur gebracht werden müssen. Darüber hinaus macht es auch aus Performance Gründen Sinn die eigene Anwendung zu unterteilen. Im ersten Teil werfen wir einen Blick auf die Organisation mittels Workspace.

In allen unseren Projekten werden wir den ein oder anderen Service Implementieren oder Komponenten ausprägen die nicht nur für das vorliegende Projekt interessant sind, sondern auch für anderen unternehmenseigene Projekte. Eine solche eigene Basis-Bibliothek lässt sich natürlich als eigenständiges NPM-Packet entwickeln und über ein internes Node-Repository ausliefern. Insbesondere zur frühen Entwicklungszeit, wenn sich noch viel in den Bibliotheken tut verkompliziert dieses Vorgehen aber die Bereitstellung und Verwendung. Eine Alternative (die sich aber später ebenfalls problemlos als NPM-Paket veröffentlichen lässt) ist seit der Version 6 in Angular-CLI vorhanden: „Libraries“ .

Die Grundidee ist einfach: ein einzelnes Angular-Projekt  (workspace)  dient als Basis für die Entwicklung unserer Anwendungen und Bibliotheken. Für jede Bibliothek ( library) oder Anwendung( application ) werden über entsprechende CLI-Befehle eigenständige Bestandteile generiert. Hier das grundsätzliche Vorgehen mittels Angular-CLI Befehle:

1.  ng new [workspacename]

Optional kann hier die Option: –create-application=false gewählt werden um die Demo-Anwendung nicht initialisieren zu lassen

2. ng generate library [name-1] –prefix=[prefix]

Eine erste Bibliothek, die wir später in unseren Projekten verwenden können. Hier entwickeln wir die gemeinsamen Komponenten und Services.

3. ng build [name-1]

Bibliothek bauen, optional kann hier auch das Flag –watch aktiviert werden um bei Änderungen den Build-Prozess zu triggern

4. ng generate application [name-2]

Eine Anwendung generieren. Hier arbeiten wir wie gewohnt, können aber auf die Services/Komponenten unserer Library zugreifen (nicht vergessen das Utils-Module zu importieren)

5. ng serve [name-2]

Entwicklungs-Server starten für definierte Anwendung…

Sowohl die Bibliotheken, als auch die einzelnen Anwendungen können später ganz autonom verteilt oder deployt werden. Im zweiten Teil werfen wir einen Blick auf die strukturierung mittels NgModule.

Bis dahin:

https://github.com/GEDOPLAN/anuglar-modulesD

Transaktionssteuerung mit @Transactional

Transaktionen sind nötig, um Daten atomar, konsistent, isoliert und dauerhaft bearbeiten und speichern zu können (ACID-Prinzip). Methoden von CDI-Beans können durch eine simple Annotation mit einem aktiven Transaktionskontext versehen werden.

Transaktionsinterceptor mit dem Binding @Transactional

Schon seit der Version 1.1 von CDI steht ein global aktivierter Transaktionsinterceptor zur Verfügung. Er wird mit der Annotation @Transactional einer Methode einer CDI-Bean zugeordnet:

public class SomeService {
  ...
  @Transactional
  public void doSomethingTransactionally() {
    ...
  }

Beim Aufruf der Methode ohne aktive Transaktion wird nun eine Transaktion gestartet und nach dem Verlassen der Methode wieder geschlossen. Die Regeln für das Transaktionsende sind etwas seltsam:

  • Endet die Methode mit einem Return, wird ein Commit versucht.
  • Wirft die Methode eine sog. System Exception, resultiert dies in einem Rollback. System Exceptions sind i. W. die unchecked Exceptions (ergänzt um die RemoteException, die aber explizit kaum eine Rolle spielt).
  • Wirft die Methode eine Application Exception – das sind alle anderen Exeptions – wird ein Commit (!) versucht.

Insbesondere der letzte Punkt ist (zumindest mir) unverständlich: Für mich modellieren Exceptions immer Ausnahmesituationen, die den jeweiligen Geschäftsprozess ungültig machen. Glücklicherweise kann man mit einem Parameter der Annotation auch in diesem Fall ein Rollback ansteuern:

  @Transactional(rollbackOn=Exception.class)
  public void doSomethingTransactionally() {

Transaktionsmodus

Die Art der Transaktionssteuerung durch @Transactional kann mit dem Parameter value bestimmt werden:

  • TxType.REQUIRED ist der Default, der in den allermeisten Fällen passend ist. Beim Aufruf einer davon betroffenen Methode wird geprüft, ob bereits eine aktive Transaktion vorliegt. Wenn ja, wird diese für den Methodenaufruf genutzt. Andernfalls wird eine neue Transaktion gestartet und am Methodenende wieder beendet.
  • TxType.REQUIRES_NEW startet bei Methodenbeginn stets eine neue Transaktion, die am Methodenende wieder beendet wird. Eine ggf. bereits aktive Transaktion wird zuvor suspendiert. Dieser Modus sollte dann verwendet werden, wenn die Ergebnisse des Methodenaufrufs auch dann dauerhaft abgespeichert werden sollen, wenn die umgebende Transaktoin zurückgerollt wird. Beispiel: Auditing/Protokollierung von Methodenaufrufen.
  • TxType.MANDATORY verlangt eine aktive Transaktion bei Methodenbeginn und wirft andernfalls eine TransactionRequiredException. Dieser Modus ist könnte prinzipiell für Repositories (a.k.a DAOs) interessant sein, da deren feingranulare Methoden i. A. von einem Service aufgerufen werden, der selbst transaktional ist. Leider werden Lifecycle-Methoden (@PostConstruct-annotierte Methoden etc.) nicht durch die @Transactional-Interceptoren intercepted. Häufig werden darin Repository-Methoden aufgerufen. Dann sollte für Repositories wie auch bei anderen Services TxType.REQUIRED verwendet werden.
  • Die weiteren Modi TxType.NOT_SUPPORTED, TxType.SUPPORTS und TxType.NEVER sind i. A. unbrauchbar.

Danke für’s Lesen und bis bald – vielleicht auch in einem unserer Trainings in Berlin, Bielefeld, Köln oder bei Ihnen!
https://gedoplan-it-training.de/

Alles gesund? Health checking mit MicroProfile Health

Im Bereich von Microservices und verteilten Systemen – aber nicht nur dort – ist es wichtig zu wissen, ob ein Service/System ordnungsgemäß läuft oder gestört ist. Im MicroProfile (https://microprofile.io) ist mit MicroProfile Health ein Teil enthalten, der es in Kombination mit CDI sehr einfach macht, Health Checking in EE-Anwendungen zu integrieren.

Health Check Callbacks

Die Idee von MicroProfile Health ist, dass Anwendungen auf EE-Servern oder auf Basis von MicroProfile-Frameworks ein oder mehrere Methoden zum Prüfen des Gesundheitszustandes enthalten. Der Server sammelt die Zustände bei Bedarf ein und aggregiert daraus einen Gesamtzustand, der auf dem Rest-Endpoint /health veröffentlicht wird:

GET /health

200 OK
{
  "outcome": "UP",
  "checks": [{
    "name": "Service1",
    "state": "UP",
    "data": {
      "memory": 180672240
    }
  }, {
    "name": "Service2",
    "state": "UP"
  }]
}

Die Callback-Methoden heißen – etwas uninspiriert – call und stammen aus dem Interface org.eclipse.microprofile.health.HealthCheck. Der Server sucht sich automatisch alle Klassen zusammen, die dieses Interface implementieren und mit dem CDI-Qualifier org.eclipse.microprofile.health.Health versehen sind:

@ApplicationScoped
@Health
public class HealthCheck1 implements HealthCheck {

  @Override
  public HealthCheckResponse call() {
    ...

Das Prüfergebnis können die Methoden bequem mittels Builder-Pattern zusammensetzen:

  public HealthCheckResponse call() {
    return HealthCheckResponse
        .named("Service1")
        .up()
        .withData("memory", Runtime.getRuntime().freeMemory())
        .build();

Neben up gibt es auch down sowie die Methode status, der der Gesundheitszustand als Parameter übergeben wird.

Gesamtgesundheit

Bei einem GET-Request auf den Pfad /health werden alle verfügbaren call-Methoden aus allen Anwendungen des Servers aufgerufen und zu einem Gesamtzustand kombiniert. Liefern alle Checks UP, ist das Attribut outcome im Gesamtergebnis ebenfalls UP und der Response hat den Status 200 OK. Andernfalls wird der Response mit 503 Service unavailable ausgeliefert und outcome ist darin DOWN.

Plattformen

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

  • OpenLiberty (mit aktiviertem Feature mpHealth-1.0).
  • Payara.
  • WildFly (Achtung: WildFly hat unterschiedliche Ports für Applikations- und Management-Zugriffe, im Default 8080 und 9990. Der o. a. REST-Endpoint /health steht über den Management-Port zur Verfügung).

Die genannten Klassen befinden sich bspw. in der Maven-Dependency org.eclipse.microprofile:microprofile.

Demo

In https://github.com/GEDOPLAN/health-demo finden Sie ein Beispielprojekt mit zwei Health Checks, deren Ergebnis über eine Webseite beeinflusst werden kann. 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.

Ausblick

Im Beispiel wird als Zusatzinformation die Menge freien Speichers hinzugefügt, was im Kontext von Health Checking nicht grundsätzlich falsch ist, aber doch eher eine Metrik darstellt. Für deren Veröffentlichung hält MicroProfile eine weitere Teilspezifikation bereit: MicroProfile Metrics. Sie wird Gegenstand eines der nächsten Blog Posts sein – stay tuned!

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

Angular, i18n mit ngx-translate

Internationalisierung. Eine typische Aufgaben bei der Implementierung von Web-Anwendungen. Diese Anforderung macht auch vor Angular nicht halt. Hier bieten sich dem Entwickler gleich zwei Möglichkeiten: im ersten Teil haben wir die Möglichkeiten der Core-Bibliothek betrachtet, heute werfen wir einen Blick auf eine zusätzliche Bibliothek: ngx-translate.

i18n-2

@ngx-translate/core kann wie gewohnt per npm-Install im eigenen Projekt integriert werden. Ab hier ist die Verwendung (vielleicht im vergleich zur Core-Variante) relativ gut nach zu vollziehen. Anstatt den Build-Prozess für unterschiedliche Sprachen zu manipulieren werden die Texte hier zur Laufzeit umgewandelt. Damit das funktioniert muss das entsprechende Modul registiert und konfiguirert werden:

import {TranslateModule} from '@ngx-translate/core';


@NgModule({
  imports: [
      TranslateModule.forRoot()
  ],
   ...

Grundsätzlich wäre damit der Service schon einsatzbereit, müsste aber „manuell“ mit Übersetzungen versehen werden. In aller Regel wollen wir hier aber die Übersetzungen in einer separaten Datei vorhalten. Im Fall von ngx-translate geschieht das in einer JSON-Struktur.

{
  TITLE: 'Willkommen',
  HOME: {
    TEXT: 'Hallo Welt',
    FOOTER: 'verwendete Sprache: {{loc}}'
  }

Wie diese zugreifbar gemacht wird, wird über so genannte „TranslateLoader“ entschieden. Dabei handelt es sich um ein simples Interfaces welches entweder selber implementiert werden kann oder man greift z.B. auf den HTTPLoader (@ngx-translate/http-loader) zurück. Als Beispiel einer Implementierung wäre auch eine lokale Variante denkbar:

export class I18nLoader implements TranslateLoader {
  getTranslation(lang: string): Observable {
    if (lang === 'de') {
      return of(de);
    } else {
      return of(en);
    }
  }
}

---- Registrierung ----    
TranslateModule.forRoot({
  loader: {
    provide: TranslateLoader,
    useClass: I18nLoader
  }
})

(nicht für Produktion gedacht, alle Übersetzungen der Anwendungen würden immer zum Browser übertragen werden!)

Damit sind wir schon in der Lage unsere Übersetzungen ein zu setzen. Dies geschieht entweder:

im Template mittels Pipes

{{ 'HOME.TEXT' | translate }}
{{ 'HOME.FOOTER' | translate: { loc: 'de' } }}

oder im Code mittels Service

constructor(private translateService: TranslateService) {
  this.locale = translateService.getBrowserLang();
  translateService.use(this.locale);
  translateService.get('TITLE').subscribe(t => console.log('ngx: ' + t));
}

Das Vorgehen mittels ngx-translate ist sicherlich etwas weniger komplex in der Verwendung und Konfiguration und bietet genug Flexibilität um auch im Projektalltag gut einsetzbar zu sein. Ein Nachteil müssen wir uns allerdings bewusst machen: die Übersetzungen zur Laufzeit durchführen zu lassen kostet (zumindest ein wenig) Zeit, sodass mit der (komplizierteren?) Core-Variante ein leichter Performance-Vorteil vorliegt.

Live! In Farbe! auf github

Angular, i18n

Internationalisierung. Eine typische Aufgaben bei der Implementierung von Web-Anwendungen. Diese Anforderung macht auch vor Angular nicht halt. Hier bieten sich dem Entwickler gleich zwei Möglichkeiten: zum einen die Verwendung der Core-Bibliothek oder die Verwendung der Bibliothek: „ngx-translate“.

i18n-1

Angular i18n

Die Core Bibliothek bietet bereits die Funktion Texte innerhalb der Anwendung zu internationalisieren dazu ist in folgenden Schritten vorzugehen:

  1. Texte auszeichnen
  2. Übersetzungsdatei erzeugen
  3. Übersetzungen durchführen
  4. Anwendung bauen

 

Texte auszeichnen

Angular bedient sich für die Internationalisierung der Templates der Pipe „i18n“. Diese kann innerhalb des Templates verwendet werden um die Inhalte von beliebigen Komponenten zu markieren:

 
<h1>placeholder-text</h1> 

Neben der reinen Markierung existieren außerdem noch Möglichkeiten zum setzen
einer Beschreibung oder die Vergabe einer eindeutigen ID die später hilfreich ist wenn Texte der Anwendung hinzugefügt werden sollen: beschreibung@@id

 
<h1>placeholder-text</h1> 

 

Übersetzungsdatei erzeugen

Dank Angular-CLI ist die Erzeugung einer entsprechenden Übersetzungs-Datei dann ein Kinderspiel

 ng xi18n 

Führt zur Generierung einer „xlf-Datei“ welche alle markierten Textstellen als so genannte „trans-unit“ beinhaltet:

 
 
  placeholder 
   
    app/app.component.html 
    12 
  
 

 

Übersetzungen durchführen

Die Default-Sprache ist Englisch. Für weitere Übersetzungen wird die so erzeugte Datei nun kopiert und in aller Regel um einen Sprach-Post Fix erweitert (z.B. messages.de.xlf) Die eigentliche Übersetzung muss dann natürlich noch in den Dateien durchgeführt werden: dazu wird neben den bereits vorhandenen „source“ Attributen ein „target“ Attribut ergänzt, welches den übersetzten Wert erhält:

 

  (welcome) 
  Willkommen 
  ... 
 

 

(Alltagstauglich mit ngx-i18nsupport)

Bis hierher erscheint das Vorgehen noch nachvollziehbar. Allerdings zeigt sich die Core Bibliothek bei einem genaueren Blick etwas sperrig:

ein Update bestehender Texte ist direkt nicht möglich.

So würde bei einem erneuten Aufruf der Generierung die bestehende Datei einfach überschrieben werden und alle bisher gepflegten Übersetzungen wären dahin. Ein npm-Tool was hier Abhilfe schafft ist: xliffmerge welches über npm installiert werden kann (npm install –save-dev ngx-i18nsupport). Dieses Tool erlaubt uns ein intelligentes mergen von bestehenden Sprachdateien. Ein Typischer Ablauf lässt sich vermutlich am einfachsten anhand des projekt-tauglichen npm-Script erkären:

 
---- messages.config.json ----- 
{ 
 "xliffmergeOptions": { 
      "srcDir": "src/i18n", 
      "genDir": "src/i18n" 
    } 
} 

---- npm script ----- 
"i18n": 
   "ng xi18n --output-path i18n --i18n-locale en 
     && xliffmerge --profile src/messages.config.json en de" 

Zuerst lassen wir Angular-CLI ein Standard Message File generieren, welches wir in einem speziellen Ordner ablegen (ng xi18n –output-path i18n). Dann kommt xliffmerge ins Spiel: mittels einer Konfigurationsdatei (in der wir im einfachsten Fall wie zu sehen nur den Ordner angeben in dem unsere messages.xlf abgelegt wurde) und den Sprachen die wir unterstützen wollen (xliffmerge –profile src/messages.config.json en de) werden nun entsprechende Dateien erzeugt (messages.de.xlf, message.en.xlf). Der entscheidende Punkt:
sollten diese Dateien bereits vorhanden und mit Übersetzungen versorgt sein werden nur die neuen Elemente in diese Dateien übernommen.

 

Anwendung bauen

Ein zweiter Punkt dürfte den ein oder anderen überraschen. Wenn wir den AOT Compiler verwenden (was wir in aller Regel für unsere Produktions-Builds tun wollen und in der aktuellen Version auch der Standard Fall bei einem Prod-Build über
Angular CLI ist) ist ein dynamisches lesen / ermitteln der übersetzten Werte technisch nicht möglich. Der Compiler läuft ja im Fall von AOT nicht im Browser sondern bereits beim Build-Prozess.

Das heißt: ein separater Build pro unterstützte Sprache

und eine eigenständige Version unserer Anwendung die deployt werden muss. Das klingt nun etwas aufwendiger / dramatischer als es ist. Also schauen wir uns das ganze mal praktisch an:

mit AOT

Ein separater Build für jede unterstützte Sprache. Das lässt sich relativ einfach über ein npm Skript lösen das z.B. für Deutsch und Englisch auf Basis der oben erstellen Message-Dateien so aussehen könnte:

 "build": 
"npm run i18n 
&& ng build --prod --i18n-file src/i18n/messages.en.xlf --i18n-format xlf --i18n-locale en --output-path dist/en
--base-href . 

&& ng build --prod --i18n-file src/i18n/messages.de.xlf --i18n-format xlf --i18n-locale de --output-path
dist/de --base-href .", 

für jede Sprache haben wir nun im „dist“ Ordner eine lokalisierte Version unserer
Anwendung. Ein entsprechend konfigurierter Proxy oder eine simple Einstiegsseite mit ein wenig JavaScript könnte nun
automatisch auf Basis der Browser-Locale auf die korrekte Version umleiten.

mit JIT

Während der Entwicklung werden die meisten ( aus Gründen der Geschwindigkeit) auf den JIT Compiler setzen ( Standard bei ng serve). Zwar muss auch hier die Sprache vor Start der Anwendung feststehen ( und damit muss die Anwendung bei immer neu geladen werden wenn die Sprache geändert werden sollte) aber anders als bei AOT kann die Anwendung je nach Bedarf dynamisch initialisiert werden. Dazu konfigurieren wir die entsprechende Sprache und Message-Datei beim bootstrap unserer Anwendung in der main.ts, hier zum Beispiel über die Browser-Locale:

 
declare const require;
export function translationsFactory() {
  let locale = (window.clientInformation && window.clientInformation.language) || window.navigator.language;
  let language = locale.split('-')[0];

  return require(`raw-loader!./i18n/messages.${language}.xlf`);
}

platformBrowserDynamic()
  .bootstrapModule(AppModule, {
    providers: [
      { provide: TRANSLATIONS, useFactory: translationsFactory, deps: [] },
      { provide: TRANSLATIONS_FORMAT, useValue: 'xlf' }
    ]
  })
  .catch(err => console.error(err));

 

(Alltagstauglich mit i18n-polyfill)

Leider muss auch hier wieder ein „aber“ da gelassen werden. Eine typische Anforderung ist sicherlich auch Benachrichtigungen zu internationalisieren, also auch programmatisch auf dies Texte zugreifen zu können. Bisher hat es dieses Feature leider noch nicht direkt in den Standard geschafft, bis dahin existiert aber ein Polyfil der hoffentlich in ähnlicher Form auch in den Standard übergehen wird:
i18n-polyfill, https://github.com/ngx-translate/i18n-polyfill

Viele Ecken müssen bis hierher umschifft werden um die vermeidlich einfache Anforderung der Internationalisierung um zu setzen. Offiziell heißt es das dieser Prozess / das Vorgehen noch verbessert werden soll um die Verwendung von i18n zu vereinfachen,seien wir also gespannt.
Im zweiten Teil werfen wir trotzdem einen kurzen Blick auf eine Alternative Lösung die in Sachen Performance zwar der Core-Bibliothek nicht das Wasser reichen kann, aber ein etwas pragmatischeres Vorgehen wählt.

Live! In Farbe! auf github

Frohe Weihnachten, Angular Dynamic Components

demo

auf github