Angular. Ferdinand Malcher
Story – Listenansicht
Als Leser möchte ich einen Überblick über alle vorhandenen Bücher erhalten, um mir den nächsten Lesetitel heraussuchen zu können.
Jedes Projekt braucht einen einfachen Anfang. Es bietet sich an, für den BookMonkey im ersten Schritt eine einfache Listenansicht für unsere Bücher zu implementieren. Weitere Funktionen werden später folgen.
Ein Buch soll durch ein Vorschaubild dargestellt werden.
Es sollen sowohl der Titel, der Untertitel als auch die Autoren des Buchs dargestellt werden.
Die Entität »Buch«
Wir müssen zunächst überlegen, wie die Entität Buch in unserer Anwendung repräsentiert werden kann. Dazu sammeln wir Eigenschaften, die ein Buch besitzen kann:
isbn: Internationale Standardbuchnummer (ISBN)
title: Buchtitel
description: Beschreibung des Buchs (optional)
authors: Liste der Autoren
subtitle: Untertitel (optional)
rating: Bewertung (optional)
published: Datum der Veröffentlichung
thumbnails: Liste von Bildern (optional)
Die ISBN ist unser Primärschlüssel, das heißt, ein Buch ist durch seine ISBN eindeutig identifizierbar. Einige Eigenschaften wie zum Beispiel der Untertitel und eine Bewertung sind optional und müssen nicht in jedem Fall vorhanden sein.
Die Entität »Thumbnail«
Einem Buch können über die Eigenschaft thumbnails mehrere Bilder zugeordnet sein. Ein solches Bild besitzt die folgenden Eigenschaften:
url: URL zur Bilddatei
title: Bildtitel (optional)
Klasse oder Interface für die Datenmodelle?
TypeScript kennt sowohl Klassen als auch Interfaces, um Daten abzubilden. Beide Sprachbestandteile haben wir im TypeScript-Kapitel bereits kennengelernt.
Klasse
Eine Klasse können wir als Vorlage verstehen, mit deren Hilfe wir konkrete Objekte instanziieren können. Diese instanziierten Objekte haben klar definierte Methoden und Eigenschaften. Für unser Buch könnte eine Klasse zur Abbildung unserer Fachlichkeit (Domäne) wie folgt aussehen:
export class Book {
constructor(
public isbn: string,
public title: string,
public rating?: number,
// weitere Eigenschaften
) { }
// Methode
isVeryPopular() {
return this.rating > 4;
}
}
// Verwendung:
const book = new Book('ISBN', 'TITLE', 5, /* weitere */);
const isVeryPopular = book.isVeryPopular();
Interface
Ganz ähnlich zu Klassen verhält es sich mit Interfaces. Interfaces beschreiben die Methoden und Eigenschaften von Objekten. Allerdings können Interfaces weder eine konkrete Implementierung noch eine Initialisierung bereitstellen. Man kann sie als Vertrag verstehen. Wir versprechen per Vertrag, dass ein bestimmtes Objekt die benötigte Struktur besitzt. Das zuvor gezeigte Beispiel könnte bei der Verwendung eines Interface so programmiert werden:
export interface Book {
isbn: string;
title: string;
rating?: number;
// weitere Eigenschaften
}
export class BookHelper {
static isVeryPopular(b: Book) {
return b.rating > 4;
}
}
// Verwendung:
const book = {
isbn: 'ISBN',
title: 'TITLE',
rating: 5
};
const isVeryPopular = BookHelper.isVeryPopular(book);
Beide Konstrukte ähneln sich, und so können wir sowohl mit Klassen als auch mit Interfaces unsere Daten beschreiben. Wer von einer objektorientierten Programmiersprache kommt, wird wahrscheinlich das Beispiel mit der Klasse bevorzugen.
Es sprechen einige gute Gründe für die Wahl einer Klasse:
Wir bilden ein sauberes Domain Model5 ab, die Daten und dazu passende Methoden sind möglichst nahe beieinander. Als Beispiel hierfür haben wir die frei erfundene Methode isVeryPopular() angedeutet. Beim Einsatz von Interfaces müsste man sich hingegen auf irgendeine Art und Weise einer Hilfsklasse bedienen. Zur Veranschaulichung haben wir den BookHelper eingeführt.
Wir erzwingen durch den Konstruktor eine korrekte Verwendung. TypeScript kann bereits während des Kompilierens sicherstellen, dass kein Objekt mit falschen Werten entstehen wird.
Klassen existieren in JavaScript und sind Teil der Programmlogik (Interfaces hingegen nicht).
Werfen wir jedoch einen Blick in den offiziellen Styleguide von Angular, so konnten wir dort bis zum Frühjahr 2019 noch die folgende Empfehlung finden:
Angular Style Guide: 03-03
Consider using an interface for data models.
Aha? Über diese Empfehlung kann man durchaus erstaunt sein! Auch wir haben uns für den BookMonkey für Interfaces entschieden, und wir möchten diese Entscheidung detailliert begründen.
Als Erstes müssen wir einmal die Wortwahl betrachten: Domänenmodell und Datenmodell. Ein Domänenmodell bildet die Geschäftslogik ab. Handelt es sich bei dem Buch in unserer Angular-Anwendung um ein Domänenmodell? Wir sagen: Nein! Unser BookMonkey soll eine klassische Client-Server-Architektur implementieren. Bei einer Systemarchitektur mit einem Angular-Frontend und einem HTTP-Backend wollen wir die kritische Geschäftslogik auf das Backend verlagern. Dort befindet sich die Domäne. Zum Frontend wird lediglich ein vereinfachtes Datenmodell übertragen. Das bedeutet also, dass wir eine Software planen, bei der die kritische Geschäftslogik nicht im Frontend stattfindet, sondern auf dem Server. Es ist gar nicht notwendig, dass wir die üblichen Ansprüche an unser Modell legen. Im Endeffekt soll unsere gesamte Angular-Anwendung vor allem die Daten vom Server empfangen, darstellen, verarbeiten und wieder zurücksenden. Unter diesen Gesichtspunkten bieten uns Interfaces folgende Vorteile:
Man muss nicht den Konstruktor einer Klasse aufrufen und dafür eine Methode zum »Mappen« der Daten programmieren. Die Daten vom Server im JSON-Format können dementsprechend sofort als passendes Objekt weiterverwendet werden, sofern die Daten vom Server und das Interface übereinstimmen (siehe Kapitel zu HTTP ab Seite