Disclaimer

Benutzung auf eigene Gefahr.

1. Einführung und Ziele

Das Konzept von Fachwerten stammt aus dem aus dem Werkzeug- und Material-Ansatzes (WAM):

Fachwert

Fachwerte sind Erweiterungen von primitiven Datentypen objektorientierter Programmiersprachen. Sie halten Informationen und sind unveränderlich, weswegen sich Fachwerte zum Beispiel für Geldbeträge anbieten: Es ergibt Sinn, zwei Geldbeträge miteinander zu addieren, jedoch nicht sie zu multiplizieren. Hier ist es also angebracht, einen unveränderlichen Geldbetrag als eigenen Fachwert zu behandeln und für jede Änderung an diesem einen neuen zu erstellen. Ein Fachwert kennt niemanden außer sich selbst und andere Fachwerte. Im DDD ist dieses Konzept als Value Object bekannt.

— Wikipedia
Werkzeug- und Materialansatz

Die Idee einer eigenen Fachwert-Bibliothek ist schon recht alt. Ich glaube, sie stammt ursprünglich von Herwig Scheidel, mit dem ich zusammen zwischen 2000 und 2005 bei der RWG und Fiducia IT mit der Portierung eines C++-Frameworks nach Java beschäftigt war. Damals hatten wir eine eigene Fachwert-Bibliothek für den Bankbereich, die Basis des jGEBOS-Frameworks war. Es tauchte auch die Idee auf, den Fachwert-Teil des Frameworks als OpenSource auszugliedern, was aber aufgrund diverser Fusionen wieder in Vergessenheit geriet.

Ziel dieser Bibliothek ist es, für den deutschsprachigen Raum alle wichtigen Datentypen bereitzustellen, die man sonst immer wieder selbst implementieren muesste.

1.1. Aufgabenstellung

Diese Bibliothek greift die Idee von damals (s.o.) wieder auf, ist aber nicht auf den Bankbereich beschränkt:

  • Erweiterung des Java-Typsystems um weitere Datentypen wie IBAN, BIC oder Anschrift

  • Validierung - es sollen keine ungültigen Werte erzeugt werden können

  • Klassen dürfen nicht final sein, sondern sollen erweitert werden können

  • einfache Handhabung

  • wenig externe Abhängigkeiten

Im März 2017 wurde jfachwert im Maven-Repository aufgenommen (OSSRH-29910). Die Bibliothek setzt auf Java 8 auf. Ab Oktober 2019 wurde die die Bibliothek auf Kotlin umgestellt, während die JUnit-Tests in Java verblieben (um die Kompatibilität mit Java sicherzustellen).

1.2. Qualitätsziele

  • Stabilität:

    • stabile Schnittstelle

    • automatische Tests (angestrebte Testabdeckung: > 70%)

  • ungültige Werte sollen zurückgewiesen werden

  • sauberes Exception-Handling

1.3. Stakeholder

Rolle Erwartungshaltung

Autor

Feedback

Entwickler

intuitive API, Unterstützung bei Fehl-Bedienung, keine (wenig) externe Abhängigkeiten

2. Randbedingungen

2.1. Technische Randbedingungen

Ursprünglich setzte jFachwert auf Java 8 auf, wurde aber ab Oktober 2019 auf Kotlin umgstellt. Dennoch sollte jFachwert sowohl mit Java 8 und Java 11 laufen. Dies wird durch JUnit-Tests sichergestellt, die in Java verblieben sind und die mit Java 8 und Java 11 ausgeführt werden.

Die Abhängigkeiten zu anderen Bibliotheken werden auf ein Minimum reduziert. Dies erleichtert den Einsatz von jFachwert für andere Projekte.

2.2. Organisatorische Randbedinungen

jFachwert wird nach dem GitFlow-Modell. D.h. die eigentliche Entwicklung findet auf dem develop-Zweig statt. Releases werden mit dem jgitflow-maven-plugin gebaut, wie in der Entwickler-Dokumentation beschrieben.

3. Kontextabgrenzung

3.1. Fachlicher Kontext

Fachwerte konzentrieren sich auf den deutschsprachigen Raum und es wird (bis auf wenige Ausnahmen) der in Deutschland übliche Begriff verwendet. So heißt es im Deutschen Kontonummer und nicht AccountNumber.

3.2. Technischer Kontext

jFachwert benötigt mindestens Java 8 und verwendet Optional für optionale Parameter und Rückgabewerte. Damit sind null als Parameter nicht erlaubt und können auch nicht als Rückgabewert vorkommen.

4. Lösungsstrategie

interface fachwert

Fachwerte sind durch folgende Eigenschaften gekennzeichnet:

  • unveränderlich (immutable),

  • serialisierbar.

Sie repräsentieren einen festen Weg und sind damit das genaue Gegenteil von transient (flüchtig). Diese Eigenschaften werden durch das Fachwert-Interface ausgedrückt, von der alle Fachwerte abgeleitet sind.

4.1. Objekt-Erzeugung

Da Fachwerte immutable (unveränderlich) sind, kommt der Erzeugung eine wichtige Bedeutung zu. Prinzipiell bieten sich dabei folgende Verfahren an:

  • Erzeugung über Konstruktur

  • Erzeugung über Builder-Pattern

  • Erzeugung über statische of(..)-Methode

Builder-Pattern bieten sich eher dann dafür an, wenn die Erzeugung eines Objekts komplexer ist. Da Fachwerte aber tendenziell einfach aufgebaut sind und oft nur mit einem Argument für den Konstruktor auskommen, wird nur der Konstruktor für die Erzeugung eines Objekts angeboten.

Die Erzeugung über eine statische of(..)-Methode hat den Vorteil, dass Objekte, die oft benötigt werden, in einem Cache vorgehalten werden können. Dies beschleunigt die Erzeugung von Objekten, vor allem aber reduziert es den Speicherverbrauch. Deswegen werden ab v1.2 bereits erzeugte Objekte in einen Cache gelegt, um Duplikate zu vermeiden.

4.2. Validierung

Da ungültige Objekte verhindert werden sollen, muss die Validierung im Konstruktor erfolgen. Um den Konstruktor übersichtlich zu halten, werden die Prüfung in eigene Validatoren ausgelagert.

Auch sind Prüfziffer-Verfahren, die zur Erkennung fehlerhafter Fachwerte dienen, eine Art der Validierung. Deswegen befinden sich Validatoren und Prüfziffer-Verfahren im selben Package.

Viele Klassen bieten auch eine statische validate-Methode an, um auch ohne Kreierung von Fachwerten eine Überprüfung der Parameter durchführen zu können. Mit Umstellung auf Kotlin in v4.0 ist die validate-Methode in ein Companion-Objekt gewandert. Druch ändert sich der Aufruf von UStdIdNr.validate("DE136695970") in UStIdNr.Companion.validate("DE136695970").

Ab jFachwert 1.0 wird jetzt eine IllegalArgumentException (und nicht mehr eine ValidationException) geworfen, wenn die Validierung im Konstruktor fehlschlägt. Damit verhält sich jFachwert jetzt so, wie man es von anderen Konstruktoren gewohnt ist.

4.3. Exceptions

Mit Throwable.getLocalizedMessage() gibt es seit JDK 1.1 die Möglichkeit, sprach-spezifische Fehlermeldungen zur Verfügung zu stellen. Dies wird verwendet, um für den deutschsprachigen Raum eine entsprechende Fehlermeldung für die Anzeige anzubieten.

4.4. Encoding

Als Encoding wird UTF-8 verwendet. Um Encoding-Probleme zu vermeiden, wird im Source-Code und in Resourcen keine Umlaute, sondern stets die Ersatzdarstellung (z.B. Pr\u00fcfziffer) verwendet.

Auch in Kommentaren im Source-Code werden keine Umlaute verwendet. Hier werden Umlaute ausgeschrieben, z.B. /* Pruefziffer */). Dies hat Auswirkungen auf die Javadoc-Generierung. Dies wird aber in Kauf genommen, um Probleme mit falschen Encoding zu vermeiden.

Bei der Architektur-Dokumentation werden Umlaute akzeptiert. Sollte es sich allerdings herausstellen, dass dies auf GitHub oder jfachwert.de zu Problemen führt, wird auch hier auf Umlaute verzichtet werden.

5. Bausteinsicht

5.1. Gesamtsystem

packages
Enthaltene Bausteine

Die einzelnen Klassen sind in fachliche Packages aufgeteilt.

Tabelle 1. Packages
Package Beschreibung

bank

alles, was mit Banken zu tun hat wie IBAN, BIC oder Kontonummer

steuer

das Finanzamt lässt grüßen - hier sind Fachwerte wie Steuernummer und UStIdNr defniert

formular

was so üblicherweise in Formualaren vorkommt wie Anrede oder Familienstand

post

hier finden sich Fachwerte wie Anschrift, Adresse oder PLZ

rechnung

Fachwerte, die man üblicherweise auf Rechnungen antrifft wie Bestellnummer, Referenznummer oder Rechnungsmonat

math

einige Dinge, mit denen man rechnen kann, wie z.B. Bruch oder Prozent

net

Fachwerte für das Neuland "Internet" wie EMailAdresse oder Domainname

med

Fachwerte wie PZN oder IK für den medizinischen Bereich und der Welt der Krankenkasse

zeit

Fachwerte für den Umgang mit Zeitangaben

Das pruefung-Package enthält Klassen zur Validierung und zu verschiedenen Pruefzifferverfahren. So enthalten manche Fachwerte wie IBAN eine Pruefziffer, um Fehleingaben zu erkennen.

Wichtige Schnittstellen

Jede Klasse hat einen Konstruktor, über den ein Objekt davon angelegt wird. Es gibt keine Setter-Methoden, da alle Klassen immutable sind.

5.1.1. bank

package bank

Mit dem bank-Package hat alles angefangen. Die Fachwerte in diesem Paket repräsentieren Konzepte, die im Bank-Umfeld typischerweise anzutreffen sind.

Die Geldbetrag und Waehrung-Klasse implementiert dabei die aktuelle Money-API 1.1 und wurde gegen das aktuelle TCK getestet. Aus diesem Grund wurde die GeldbetragFactory hinzugefügt, die vom TCK zum Aufsetzen der Tests verwendet wird.

5.1.2. steuer

package steuer

Bereits mit v0.0.2 gab es das steuer-Package, das einige Begriffe im steuerlichen Umfeld abdeckt.

5.1.3. post

package post

Das post-Package ist das dritte fachliche Package, das mit v0.2 hinzugekommen ist. Neben der PLZ oder Ort finden sich auch hier zusammengesetzte Fachwerte wie Adresse oder Anschrift.

Mit v2.1 ist die Klasse Name hinzugekommmen, die eine equalsSemantic-Version für den semantischen Vergleich besitzt. Damit werden Namen, die unterschiedlich geschrieben werden, aber für denselben Namen stehen, als gleich angesehen (z.B. "Karlheinz" und "Karl-Heinz"). Will man den exakten Vergleich, so gibt es die equalsExact-Methode. Die equals-Methode stützt sich in v3 auf die equalsSemantic-Version. Darauf sollte man sich aber nicht verlassen, da sich das in künftigen Versionen ändern kann.

5.1.4. rechnung

package rechnung

Mit v0.3 ist das rechnung-Package hinzugekommen. Hier sind vor allem Klassen zu finden, die auf Rechnungen oder Bestellungen zu finden sind.

5.1.5. net

package net

Das net-Package ist mit v0.4 hinzugekommen. Hier befinden sich Begriffe aus dem Internet (z.B. EMailAdresse), aber auch Klassen, die im weitesten Sinn unter Vernetzung (z.B. Telefonnummer) eingeordnet werden können.

5.1.6. math

package math

Mit v0.6 kam das math-Package hinzu. Es enthält einige Klassen zum Rechnen, die vorwiegend von java.lang.Number abgeleitet sind.

5.1.7. util

package util

Das util-Package ist mit v0.7 hinzugekommen. Es enthält im Wesentliche die TinyUUID- und SmallUUID-Klasse, die die gleiche Funktionalität wie die UUID-Klasse hat. Lediglich die toString()-Methode liefert eine kompaktere Schreibweise (22 bzw. 25 Zeichen) als die UUID-Klasse (36 Zeichen).

5.1.8. formular

package formular

Wer sich nicht damit rumschlagen will, was als Enum alles in eine Anrede, Geschlecht oder Familienstand hinein sollte, findet hier eine Implementierung, die für die allermeisten Fälle ausreichen dürfte.

5.1.9. med

package med

Das med-Package ist mit v1.1 hinzugekommen. Es enthält Klassen aus der Welt der Krankenkassen.

5.1.10. zeit

package zeit

Das zeit-Package ist mit v5 hinzugekommen. Die darin enthalt Zeitdauer-Klasse eignet u.a. für die Zeitmessung von Berechnungen:

    void someMethod() {
        Zeitdauer zeitdauer = new Zeitdauer();
        // Berechnungen, ...
        log.info("Die Berechnung dauerte {}.", zeitdauer);
    }

5.2. Prüfungen und Exceptions

package pruefung

Alles, was mit Prüfungen und Validierung zu tun hat, ist im pruefung-Package abgelegt. Dazu gehoeren:

  • Prüf-Verfahren

  • Validatoren

  • Exceptions

5.2.1. Validatoren

validatoren

Alle Validatoren implementieren das 'SimpleValidator'-Interface. Im Falle, dass die validate-Methode fehlschlägt, wird eine javax.validation.ValidationException geworfen.

5.2.2. Prüfverfahren

pruefverfahren

Alle Prüfverfahren implementieren das PruefzifferVerfahren-Interface. Das NoopVerfahren spielt dabei eine Sonderrolle - es dient zur Deaktivierung des Prüfverfahrens.

5.2.3. Exceptions

exceptions

Um eine sprachabhängige Fehlermeldung über getLocalizedMessage() bereitzustellen, sind alle Exceptions von LocalizedValidationException abgeleitet. Die Fehlermeldungen und Begriffe sind dabei als ResourceBundle in den Dateien "messages.properties" und "messages_de.properties" abgelegt.

5.3. Geldbetrag…​

Ab 1.0 kam die Geldbetrag-Klasse im bank-Paket (de.jfachwert.bank) hinzu, die das MonetaryAmount-Interface aus JSR-354 implementiert. Geldbetrag vereinfacht den Umgang mit der Erzeugung von MonetaryAmount-Instanzen durch

  • Bereitstellung entsprechender Konstruktoren,

  • statische valueOf-Methoden (anlog zur BigDecimal-Klasse),

  • statische of-Methoden (analog zur Money-Klasse)

5.3.1. …​ als Alternative zu BigDecimal

Damit eine Portierung von Altlasten, in denen BigDecimal als Datentyp verwerwendet wurde, einfach möglich ist, steht nahezu die gleiche Schnittestelle wie der BigDecimal- bzw. Number-Klasse zur Verfügung. So gibt es auch hier eine valueOf()-Methode, die eine Zahl in einen Geldbetrag umwandelt (mit Währung der aktuellen Default-Locale).

5.3.2. …​ als Alternative zur Money-Klasse

package bank geldbetrag

Die GeldbetragFactory ist durch JSR 354 vorgegeben, ebendso die Waehrung-Klasse. Beide muessen (neben einigen weiteren, internen Klassen) vorhanden sein, um das TCK (Technical Compatibility Kit) zu bestehen.

Ein Geldetrag kann entweder über die GeldbetragFactory, als auch direkt über den Konstruktor oder valueOf-Methode der Geldbetrag-Klasse erzeugt werden. Die voreingestellte Genauigkeit beträgt dabei 4 Stellen. Wird eine höhere Genauikgeit gewünscht, kann dies über einen zusätzlichen MonetaryContext-Parameter beim Konstruktor der valueOf-Methode eingestellt werden.

Um den Wechsel von oder zur Money-Klasse aus der Referenz-Implementierung zu erleichern, werden die gleichen Methoden angeboten. So gibt es in der Geldbetrag-Klasse eine statische of(..) und ofMinor(..), die semantisch der Implentierung der Money-Klasse entsprechen.

6. Laufzeitsicht

sequence creation

6.1. Objekt-Erzeugung

Bei der Erzeugung eines Fachwerts (z.B. einer IBAN) werden ein oder mehrere Validatoren (auch Prüfziffer-Verfahren) aufgerufen, um ungültige Parameter mit einer ValidationException abzuweisen. Sind keine Prüfverfahren oder Prüfregeln bekannt, wird mindestens der NullValidator aufgerufen. Er sorgt dafür, dass null als Parameter zurückgewiesen wird.

6.2. FachwertFactory

Mit der FachwertFactory, die mit Version 0.5 eingeführt wurde, kann ein beliebiger Fachwert erzeugt werden. Damit kann die FachwertFactory auch zur Validierung von Fachwerten eingesetzt werden, ohne dass dieser explizit erzeugt werden muss.

sequence factory

Dies ist vor allem für die automatisierte Validierung gedacht, wenn der Fachwert erst zur Laufzeit bekannt ist. Damit lassen sich dann Anforderungen wie "validierte das Eingabefeld 'Bankverbindung'". In gdv.xport wird dies z.B. zur Validierung von importierten Datensätze verwendet.

7. Verteilungssicht

dependencies

7.1. JAR-Datei

jFachwert wird als JAR-Datei ins zentrale Maven-Repository hochgeladen. Dort kann sie über folgende Maven-Koordinaten als Abhängigkeit in ein Projekt hinzugefügt werden:

<groupId>de.jfachwert</groupId>
<artifactId>jfachwert</artifactId>

7.2. Abhängigkeiten

Externe Abhängigkeiten zu andere Bibliotheken beschränkt sich auf commons-lang3 und der Kotlin-Standard-Bibliothek. Bei Verwendung von Maven oder Gradle werden diese Abhängigkeiten automatisch hinzugefügt.

8. Querschnittliche Konzepte

8.1. AbstractFachwert

AbstractFachwert

Viele Fachwerte wie IBAN oder BIC sind nur ein dünner Wrapper um die String-Klasse. Für diese Fachwerte werden die Gemeinsamkeiten in AbstractFachwert zusammengefasst.

8.2. Validierung

Validierung von Fachwerten findet im Konstruktor statt. Zur Unterstützung gibt es eine Reihe von Validatoren und Prüfziffer-Verfahren, die im pruefung-Package zu finden sind. Will man die Validierung direkt ausführen, bitten die meisten Klassen eine validate-Methode im Companion-Objekt an. Ansonsten kann man für die Validierung auch direkt auf die Validatoren zugreifen.

9. Entwurfsentscheidungen

9.1. Keine null-Werte

Ein Grund für die Migration auf Kotlin war die Unterbindung von null-Werten. Damit kann schon auf Aufruf-Ebene schon garantiert werden, dass null-Werte nicht weitergegeben werden, sondern sofort zum Fehler führen ("fail fast").

9.2. NULL-Konstanten

Mit v2.2 wurden NULL-Konstanten eingeführt. Damit können Variablen mit diesem Wert vorbelegt werden.

9.3. Kein Logging / Minimale Abhängigkeiten

Um nicht von einem bestimmten Log-Framework abhängig zu sein, wurde auf Logging weitgehend verzichtet. Das bedeutet, dass in einem Falle eines Fehlers oder fehlerhaften Aufrufs eine hilfreiche Exception ausgelöst wird. Diese Exception stellt alle Informationen zur Verfügung, um die Ursache des Fehlers zu finden. Exceptions, die abgefangen und behandelt werden, werden ueber das Logging-Framework des JDKs protokolliert. Um diese Meldungen sichtbar zu machen, muss der Log-Level FINE oder FINER (für den Stacktrace) konfiguriert werden.

Um sich nicht weitere unerwünschte Abhängigkeiten bei der Verwendung von jFachwert einzufangen, werden die Abhängigkeiten zu anderen Bibiotheken auf ein Minimum reduziert. Die einzigen Abhängigkeiten, die in Kauf genommen werden, sind:

  • kotlin-stdlib: diese Bibliothek kam durch die Umstellung auf Kotlin in v4 hinzu

  • commons-lang3: diese Bibliothek ist bereits als Abhängigkeit vieler anderer Bibliotheken vorhanden

  • validation-api: Basis-Bibliothek für Validierung (wird mit v5 entfernt)

  • money-api (JSR 354): diese API sowie einige weitere Bibliotheken dazu sind optional, da sie Java 9 Bestandteil des JDKs sind. Evtl. müssen sie unter Java 8 als Dependency mit aufgeführt werden, wenn diese Funktionalität benötigt wird.

Mit v4.4 wurde die Abhänigkeit zu commons-text und commons-collections4 wieder entfernt, da Kotlin hierfür genügend Unterstützung anbietet, um die fehlende Funktionalität selbst zu implementieren. Auch wurde die ValidationException aus der validation-api-Bibliothek durch eine gleichnamige Exception aus de.jfachwert.pruefung.exception ersetzt, um die Abhängigkeit zu JavaEE zu vermeiden. Andernfalls hätte diese Abhängigkeit auf JakartaEE geändert werden müssen.

9.4. Ableitung möglich

Um für Fachklassen auch eigene Logik hinterlegen zu können, können die Klassen abgeleitet werden. Deswegen sind die Klassen nicht final.

9.5. Konstruktor mit Validierung

Jede Klasse besitzt einen Konstruktor, der als letztes Argument einen Validator (z.B. in Form eines Pruefzifferverfahrens) hat. Damit ist sichergestellt, dass

  • nur gültige Fachwerte erzeugt werden können,

  • der Validator von der abgeleiteten Klasse ver- oder entschärft werden kann (je nach Anforderung).

9.6. Minimaler Footprint

Mit v2.0 wurde mit einer Reihen von Massnahmen der Speicherverbrauch reduziert:

  • Minimierung der Anzahl interner Attribute

  • Verwendung nativer Datentypen (soweit sinnvoll und möglich)

  • Verwendung von PackedDecimal (soweit sinnvoll und möglich)

  • Reduzierung von Duplikaten durch Einführung einer of-Methode zusammen mit Caching (s.u.).

Der minimale Footprint wird teilweise durch eine aufwendigere Objekt-Erzeugung oder/und Methoden-Zugriff erkauft. Dies kann evtl. bei Massenverarbeitung zum Flaschenhals werden. Hier kann man aber durch parallele Zugriffe entgegensteuern.

Damit sind Fachwerte auch für große Datenmengen geeignet, die im Hauptspeicher gehalten und parallel verarbeitet werden sollen.

9.6.1. of-Methode (valueOf)

Ursprünglich wurde auf eine statische of-Methode, wie sie seit Java 8 bei vielen Klassen aus dem JDK üblich ist (Beispiel: LocalDate.of(jahr, monat, tag)), bis v0.6 verzichtet, weil statische Methoden von abgeleiteten Klassen nicht überschrieben werden können. Mit der Einführung des math-Package in v0.6 wurde of bei einigen Klassen eine statische of-Methode aus folgenden Gründen eingeführt:

  • Die Anzahl (und Speicherverbrauch) lässt sich reduzieren, indem oft gebrauchte Werte "vorgehalten" werden.

  • erhöhte Lesbarkeit

Dies lohnt sich vor allem für Objekte, die immer den gleichen Werte oder nur einige Werte haben (z.B. Rechnungsmonat). Dies kann bei sehr vielen Objekten einen spürbaren Rückgang des Speicherverbraucs bedeuten.

Bei Klassen, die Ähnlichkeiten zur Number-Klasse haben (wie z.B. Geldbetrag), wird zusätzlich zur of-Methode eine valueOf-Methode bereitgestellt, analog zur valueOf-Methode der Number-Klasse.

9.6.2. Caching

Mit v1.2 wurde für alle wichtigen Klassen in interner Cache eingeführt, in dem erzeugte Objekte abgelegt werden. Dies dient vor allem zur Reduktion des Speicherbedarfs. Dies wird über die of(..)-Methode gesteuert.

Damit die Cache-Verwaltung selber nicht zu Speicherproblemen führt, wurde dazu eine WeakHashMap verwendet. Von daher werden Duplikate zwar reduziert, können aber auch nicht ganz ausgeschlossen werden.

9.7. Gleichheit

Die equals- und hashCode-Methode wurden jeweils überschrieben. Zwei Fachwerte sind gleich, wenn sie jeweils die gleichen Werte besitzen.

Bei Klassen im post-Paket findet ein fachlicher Vergleich statt (ab v2.1). So werden bei der Adresse "Göthestraße", "Goethestrasse" und "Göthestr." jeweils als die gleiche Straße angesehen. Möchte man es exakt, gibt es in diesen Klasse eine equalsExact(..)-Methode.

9.8. toString

Jeder Fachwert hat eine aussagekräftige toString-Implementierung. Meist ist dies der Wert selbst.

9.9. Eigene Money-Klasse (Geldbetrag)

Mit 1.0 kam die Geldbetrag-Klasse hinzu, die das MonetaryAmount-Interface aus "javax.money" implementiert. Gründe für eine eigene Implementierung waren:

  • Der Umgang mit dem Package "javax.money" und das Anlegen von MonetaryAmount-Objekten ist manchmal etwas sperrig. Ziel war es, den Umgang möglichst einfach, zumindestens aber so einfach wie der Umgang mit BigDicimal-Beträgen zu machen. D.h. das Erzeugen eines Geldbetrags sollte auch über Konstruktoren möglich sein.

  • Für Sonderfälle sollte es möglich sein, das Default-Verhalten einer MonetaryAmount-Klasse zu überschreiben. Dies ist mit der Referenz-Implementierung leider nicht möglich, da die Klassen 'final' sind.

  • Die Geldbetrag sollte als Alternative zur BigDecimal-Klasse eingesetzt werden können. Dies ist vor allem für die Portierung älterer Anwendungen gedacht, die noch mit BigDecimal arbeiten.

Der letzte Punkt ist auch der Grund, warum die Geldbetrag-Klasse zusätzlich zur of(..)-Methoden auch valueOf(..)-Methoden besitzen. Dabei werden die gleichen Parameter wie bei BigDecimal akzeptiert.

9.9.1. JSR 354 - Technical Compatibiliy Kit (TCK)

Auf GitHub gibt es unter JavaMoney/jsr354-tck ein TCK, das zur Verifizierung der korrekten Implementierung herangeogen wird. Resultat des TCKs war u.a. die GeldbetragFactory-Klasse, aber auch andere Klassen, die nur für den internen Gebrauch gedacht sind und dementsprechend auch unter de.jfachwert.bank.internal abgelegt wurden. Aufgerufen wird das TCK über den Integrationstest GeldbetragIT.

Aktuell schlägt ein Test mit dem TCK fehl. Dies betrifft den Vergleich zweier Geldbeträge mit unterschiedlichen Währungen mithilfe der compareTo-Methode. Dazu gibt es unter Issue #18 ein Bug-Report, da die verwendete omparable-Implementierung (s.u.) durch JSR 354 abgedeckt ist.

9.9.2. Operatoren

Alle Operatoren geben einen Geldbetrag zurück, da die Klasse selbst immutable ist. Um die Erzeugung neuer Objekte zu minimieren, wird der Geldbetrag selbst zurueckgegeben, falls dies moeglich ist (z.B. beim Absolutbetrag, wenn der Geldbetrag bereits positiv ist).

Bei Operatoren, die einen Geldbetrag als Operand erwarten (z.B. Addition), müssen die Währungen übereinstimmen. Ansonsten wird eine MonetaryException geworfen. Ausnahmen hiervon sind neutrale Operanden wie z.B. die Addtion oder Subtraktion von 0 EUR. Da dies den Wert eines Geldbetrags nicht ändert, wird hier die Währung ignoriert und keine MonetaryException ausgelöst.

9.9.3. Comparable-Implementierung

Die Comparable-Implementierung zweier Geldbeträge B1 und B2 richtet sich nach folgendem Schema:

  • gleiche Währung, B1 > B2: compareTo liefert Wert > 0

  • ungleiche Währung, B1 = 0, B2 > 0: compareTo liefert Wert < 0

  • ungleiche Währung, B1 != 0, B2 != 0: MonetaryException wird geworfen.

Wenn einer der Operatoren 0 ist, spielt die Währung für den Vergleich keine Rolle. Ansonsten muss die Währung übereinstimmen.

Dies gilt auch für die Implementierung der equals-Methode: zwei 0-Beträge werden als gleich angesehen, auch wenn sie unterschiedliche Währungseinheiten besitzen.

9.10. Kompatiblität der Kotlin-Portierung (v4)

Wegen KT-6653 ist das Kotlin Interface ILocalizedException nicht identisch zur Java-Variante LocalizedException. Da dieses Interface aber nur für den internen Gebrauch gedacht ist, wurde diese Inkompatibilität in Kauf genommen.

Einige weitere Interfaces wie Fachwert sind in Java verblieben, da Interfaces mit Default-Implementierung nicht 100%-ig kompatibel sind (s. KT-4779). Für die Verwendung in Kotlin gibt es das Interface mit vorangestelltem 'K' (KFachwert).

Ansosten wird die Kompatilität zur Java Version (v3) dadurch sichergestellt, das die Unit-Tests in Java verbleiben und auch in Java weiterentwickelt werden. Daneben wurden einige Klassen aus anderen Projekte, bei denen es Kompatibiltätsprobleme gab, als zusätzliche Testfälle hinzugefügt.

10. Qualitätsanforderungen

10.2. Qualitätsszenarien

Merkmal Untermerkmal Szenario

Funktionalität

Erweiterbarkeit

Falls die vorhanden Funktionalität nicht ausreicht, sollten Fachwerte erweitert werden können.

Performance

Performance spielt eine untergeordnete Rolle beim Anlegen von Objekten. Wichtiger ist, dass nur valide Objekte erzeugt werden können.

Zuverlässigkeit

Testabdeckung

Es wird eine Testabdeckung von über 70% angestrebt (aktuell: Coverage Status)

Validierung

Es sollen nur gültige Objekte angelegt werden können. Ungültige Objekte sollen mit einer ValidationException zurückgewiesen werden.

11. Risiken und technische Schulden

Durch den überwiegenden Verzicht auf Logging erschwert sich die Fehlersuche. Dies wird dadurch ausgeglichen, dass nur valide Objekte erzeugt werden können. Bei fehlerhaften Aufrufen wird eine aussagekräftige Exception geworfen, aus der die Fehlerursache hervorgeht.

Durch den Fokus auf Validierung und Optimierung des Speicherverbrauchs spielt Performance eine untergeordnete Rolle. Dies ist bei der Generierung vieler Objekte zu beachten.

Durch die Umstellung auf Kotlin in v4 kann es zu Kompatibiltaetsproblemen mit v3 kommen. Dieses Risiko wird durch zusätztliche Tests mit Demo-Klassen (die aus Projekten wie gdv.xport stammen) reduziert.

12. Glossar

Begriff Definition

Fachwert

Fachwerte sind Erweiterungen primitiver Datentypen. Sie sind unveränderlich und repräsentieren einen fachlichen Wert (z.B. IBAN).

WAM

Werkzeuge, Automation, Material ist ein objektorientierter Ansatz, aus dem der Begriff 'Fachwert' stammt.