Introduce optional bonus tasks in part4 covering: - CSV-based exchange rate provider - historical exchange rate provider with date handling - custom currency (DKP) with bidirectional conversion Provide exercise scaffolding and tests for all scenarios. Signed-off-by: Marcus Fihlon <marcus@fihlon.swiss>
1122 lines
21 KiB
Markdown
1122 lines
21 KiB
Markdown
---
|
||
title: "Beyond BigDecimal: Money and Currency API in Java (JSR-354)"
|
||
author: "Marcus Fihlon"
|
||
theme: default
|
||
transition: null
|
||
class: text-center
|
||
drawings:
|
||
persist: false
|
||
comark: true
|
||
duration: 180min
|
||
exportFilename: money-currency-api-slides
|
||
subtitle: Slides for the Workshop
|
||
meta: April 20th, 2026
|
||
---
|
||
|
||
# Beyond BigDecimal
|
||
|
||
## Money and Currency API in Java (JSR-354)
|
||
|
||
20\. April 2026
|
||
|
||
---
|
||
layout: two-cols
|
||
---
|
||
|
||
# Marcus Fihlon
|
||
|
||
::left::
|
||
|
||
Wer bin ich?
|
||
|
||
- Agile Coach und Scrum Master
|
||
- 40+ Jahre Software-Entwicklung
|
||
- 25+ Jahre im Java-Umfeld
|
||
- Organisator von Konferenzen
|
||
- Autor von Fachartikeln
|
||
- Vorträge und Workshops
|
||
- Open Source Committer
|
||
- Adventure Cyclist
|
||
|
||
<img src="https://www.fihlon.swiss/qr-code.png" class="mt-4" style="width: auto; height: auto; max-width: none; image-rendering: pixelated;" />
|
||
|
||
::right::
|
||
|
||
<img src="https://www.fihlon.swiss/portrait.webp" class="w-3/4 mt-8" />
|
||
|
||
---
|
||
|
||
# Workshop
|
||
|
||
Agenda
|
||
|
||
- Problem verstehen: Warum einfache Zahlen nicht reichen
|
||
- Grundlagen: Geldbeträge und Währungen korrekt modellieren
|
||
- Arbeiten mit Geldbeträgen: Rechnen, Rundung, Formatierung
|
||
|
||
<div class="my-6"></div>
|
||
|
||
**- Pause -**
|
||
|
||
<div class="my-6"></div>
|
||
|
||
- Fortgeschrittene Konzepte: Umrechnung, fachliche Operationen
|
||
- Architektur & Praxis: Einsatz im echten Code
|
||
|
||
---
|
||
|
||
# Das Problem
|
||
|
||
Geld ist keine Zahl
|
||
|
||
```java
|
||
double price1 = 0.1;
|
||
double price2 = 0.2;
|
||
|
||
double total = price1 + price2;
|
||
|
||
System.out.println(total);
|
||
```
|
||
|
||
<div class="my-6"></div>
|
||
|
||
<ul v-click>
|
||
<li>Erwartet: <code>0.3</code></li>
|
||
<li>Ergebnis: <code>0.30000000000000004</code></li>
|
||
<li>Grund: binäre Gleitkommazahlen</li>
|
||
</ul>
|
||
|
||
<div class="my-6"></div>
|
||
|
||
<span v-click>
|
||
```java
|
||
BigDecimal price1 = new BigDecimal("0.1");
|
||
BigDecimal price2 = new BigDecimal("0.2");
|
||
|
||
BigDecimal total = price1.add(price2);
|
||
|
||
System.out.println(total); // 0.3
|
||
```
|
||
</span>
|
||
|
||
---
|
||
|
||
# Das Problem
|
||
|
||
Währung ist nicht optional
|
||
|
||
```java
|
||
BigDecimal price = new BigDecimal("10.00");
|
||
BigDecimal tax = new BigDecimal("0.77");
|
||
|
||
BigDecimal total = price.add(tax);
|
||
```
|
||
|
||
<div class="my-6"></div>
|
||
|
||
<ul v-click>
|
||
<li>CHF? EUR? USD?</li>
|
||
<li>Wie viele Nachkommastellen?</li>
|
||
<li>Unterschiedliche Rundung je nach Währung</li>
|
||
<li>Fehler entstehen nicht im Code, sondern im Modell</li>
|
||
</ul>
|
||
|
||
---
|
||
|
||
# Das Problem
|
||
|
||
`Currency` ist eingeschränkt
|
||
|
||
```java
|
||
Currency currency = Currency.getInstance("CHF");
|
||
```
|
||
|
||
<div class="my-6"></div>
|
||
|
||
- Nur ISO-4217 Währungen
|
||
- Neue Währungen fehlen oft initial
|
||
- Keine historischen Währungen
|
||
- Keine Unterstützung für Kryptowährungen
|
||
- Keine projektspezifischen oder virtuellen Währungen
|
||
- Keine Kontextabhängigkeit, z. B. CHF:
|
||
- elektronische Zahlung: 2 Nachkommastellen
|
||
- Barzahlung: Rundung auf 0.05
|
||
|
||
---
|
||
|
||
# Das Problem
|
||
|
||
Nachkommastellen sind nicht einheitlich
|
||
|
||
```java
|
||
BigDecimal amount = new BigDecimal("10.123") // gültig oder nicht?
|
||
```
|
||
|
||
<div class="my-6"></div>
|
||
|
||
<ul v-click>
|
||
<li>CHF: 2 Nachkommastellen</li>
|
||
<li>JPY: keine Nachkommastellen</li>
|
||
<li>TND: 3 Nachkommastellen</li>
|
||
</ul>
|
||
|
||
---
|
||
|
||
# Das Problem
|
||
|
||
Rundung ist fachlich, nicht technisch
|
||
|
||
```java
|
||
BigDecimal value = new BigDecimal("10.005");
|
||
|
||
value.setScale(2, RoundingMode.HALF_UP); // 10.01
|
||
value.setScale(2, RoundingMode.HALF_DOWN); // 10.00
|
||
```
|
||
|
||
<div class="my-6"></div>
|
||
|
||
**Welche Regel ist korrekt?**
|
||
|
||
<span v-click>
|
||
Das kommt auf den fachlichen Kontext an!
|
||
</span>
|
||
|
||
---
|
||
|
||
# Das Problem
|
||
|
||
Zusammengefasst
|
||
|
||
**Falsche Abstraktion**
|
||
|
||
- `double` → ungenau
|
||
- `BigDecimal` → nur eine Zahl
|
||
- `Currency` → zu wenig Kontext
|
||
|
||
**Was fehlt**
|
||
|
||
- Betrag und Währung gehören zusammen
|
||
- Rundung ist Teil der Fachlogik
|
||
- Währungsregeln sind nicht universell
|
||
- Es fehlen wichtige Metadaten
|
||
|
||
👉 Geld wird nicht als Domänenkonzept modelliert
|
||
|
||
---
|
||
|
||
# Grundlagen
|
||
|
||
Die Lösung: JSR-354
|
||
|
||
**Money and Currency API for Java**
|
||
|
||
- Fachlich modelliert, nicht technisch
|
||
- Geld als Domänenmodell statt primitiver Typen
|
||
- Betrag und Währung als Einheit
|
||
- Fachlich korrekte Operationen und Rundung
|
||
- Erweiterbar für eigene Anforderungen
|
||
|
||
👉 Weg von `double`, `BigDecimal` und `Currency` als Einzelteile
|
||
👉 Hin zu einem konsistenten Modell für Geldbeträge
|
||
|
||
---
|
||
|
||
# Grundlagen
|
||
|
||
Was ist JSR-354?
|
||
|
||
**Money and Currency API for Java**
|
||
|
||
- Teil der Java-Spezifikation (JSR = Java Specification Request)
|
||
- Standardisierte API für Geldbeträge und Währungen
|
||
- Trennt API und Implementierung
|
||
|
||
**Wer steckt dahinter?**
|
||
|
||
- Spezifikation: Java Community Process (JCP)
|
||
- Referenzimplementierung: Moneta
|
||
- Entwickelt und gepflegt von der Java-Community
|
||
- Spec Leads: Anatole Tresch, Werner Keil und Otavio Santana
|
||
|
||
👉 Open Source und gemeinschaftlich weiterentwickelt
|
||
|
||
---
|
||
|
||
# Grundlagen
|
||
|
||
Der zentrale Typ
|
||
|
||
**MonetaryAmount**
|
||
|
||
- Repräsentiert einen Geldbetrag
|
||
- Kombiniert Wert und Währung
|
||
- Definiert fachliche Operationen
|
||
|
||
**Warum ein Interface?**
|
||
|
||
- Trennung von API und Implementierung
|
||
- Austauschbare Implementierungen
|
||
- Anpassbar an unterschiedliche Anforderungen
|
||
- Präzision vs. Performance
|
||
- Eigene Implementierungen möglich
|
||
|
||
👉 Fokus auf Modell, nicht auf Implementierungen
|
||
|
||
---
|
||
|
||
# Grundlagen
|
||
|
||
Die Währung
|
||
|
||
**CurrencyUnit**
|
||
|
||
- Repräsentiert eine Währung
|
||
- Teil des Geldbetrags
|
||
- Eindeutig über Code (z. B. CHF, EUR)
|
||
- Liefert grundlegende Metadaten
|
||
|
||
**Warum ein Interface?**
|
||
|
||
- Trennung von API und Implementierung
|
||
- Erweiterbar über ISO-4217 hinaus
|
||
- Unterstützung für neue und virtuelle Währungen
|
||
- Einheitliches Modell für alle Währungen
|
||
|
||
👉 Währungen sind Teil des Modells, nicht nur ein Zusatz
|
||
|
||
---
|
||
|
||
# Grundlagen
|
||
|
||
Die Standard-Implementierung
|
||
|
||
**Money**
|
||
|
||
- Implementiert `MonetaryAmount`
|
||
- Repräsentiert einen konkreten Geldbetrag
|
||
- Basierend auf `BigDecimal`
|
||
- Hohe Präzision für fachlich korrekte Berechnungen
|
||
|
||
**Warum diese Implementierung?**
|
||
|
||
- Präzise und verlässlich
|
||
- Geeignet für die meisten Anwendungsfälle
|
||
- Einfache Verwendung im Code
|
||
|
||
👉 Einstiegspunkt für die praktische Nutzung der API
|
||
|
||
---
|
||
|
||
# Grundlagen
|
||
|
||
Ein erstes Beispiel
|
||
|
||
- `Monetary` ist die zentrale Factory der API
|
||
- Bietet Zugriff auf Währungen und weitere Funktionen
|
||
|
||
```java
|
||
CurrencyUnit chf = Monetary.getCurrency("CHF");
|
||
MonetaryAmount amount = Money.of(19.95, chf);
|
||
|
||
System.out.println(amount);
|
||
System.out.println(amount.getNumber());
|
||
System.out.println(amount.getCurrency().getCurrencyCode());
|
||
```
|
||
|
||
Ausgabe:
|
||
|
||
```
|
||
CHF 19.95
|
||
19.95
|
||
CHF
|
||
```
|
||
|
||
---
|
||
|
||
# Grundlagen
|
||
|
||
Arithmetische Operationen
|
||
|
||
```java
|
||
CurrencyUnit chf = Monetary.getCurrency("CHF");
|
||
|
||
MonetaryAmount price = Money.of(19.95, chf);
|
||
MonetaryAmount shipping = Money.of(4.95, chf);
|
||
|
||
MonetaryAmount total = price.add(shipping);
|
||
|
||
System.out.println(total); // CHF 24.90
|
||
```
|
||
|
||
<div class="my-6"></div>
|
||
|
||
- Operationen sind direkt auf Geldbeträgen möglich
|
||
- Währung wird automatisch berücksichtigt
|
||
- Ergebnis bleibt ein Geldbetrag
|
||
|
||
---
|
||
|
||
# Grundlagen
|
||
|
||
Weitere Operationen
|
||
|
||
```java
|
||
MonetaryAmount price = Money.of(19.95, chf);
|
||
MonetaryAmount shipping = Money.of(4.95, chf);
|
||
MonetaryAmount discount = Money.of(10, chf);
|
||
|
||
MonetaryAmount total =
|
||
price
|
||
.add(shipping)
|
||
.subtract(discount)
|
||
.multiply(2)
|
||
.divide(3);
|
||
```
|
||
|
||
<div class="my-6"></div>
|
||
|
||
- Fluent API für gut lesbaren Code
|
||
- Operationen bleiben im Geldmodell
|
||
- Ergebnis ist immer wieder ein Geldbetrag
|
||
|
||
---
|
||
|
||
# Grundlagen
|
||
|
||
Rundung
|
||
|
||
```java
|
||
MonetaryAmount amount = Money.of(10, "CHF");
|
||
|
||
MonetaryAmount result = amount.divide(3);
|
||
|
||
System.out.println(result); // ?
|
||
```
|
||
|
||
<div class="my-6"></div>
|
||
|
||
- Ergebnis nicht exakt darstellbar
|
||
- Rundung notwendig
|
||
- Wie wird gerundet?
|
||
|
||
---
|
||
|
||
# Grundlagen
|
||
|
||
Rundung ist fachlich
|
||
|
||
- Keine „technische“ Entscheidung
|
||
- Abhängig von:
|
||
- Währung
|
||
- Anwendungsfall
|
||
- Geschäftsregeln
|
||
|
||
👉 Rundung ist Teil der Fachlogik
|
||
|
||
---
|
||
|
||
# Grundlagen
|
||
|
||
Rundung mit der API
|
||
|
||
```java
|
||
MonetaryAmount amount = Money.of(10, "CHF");
|
||
|
||
MonetaryAmount result =
|
||
amount
|
||
.divide(3)
|
||
.with(Monetary.getDefaultRounding());
|
||
|
||
System.out.println(result); // CHF 3.33
|
||
```
|
||
|
||
<div class="my-6"></div>
|
||
|
||
- Rundung erfolgt explizit
|
||
- Standard-Rundung abhängig von der Währung
|
||
- Rundung ist eine externe Fachregel (Operator)
|
||
- Wird bewusst auf den Geldbetrag angewendet
|
||
|
||
👉 Rundung ist kein Nebeneffekt, sondern Teil der Fachlogik
|
||
|
||
---
|
||
|
||
# Grundlagen
|
||
|
||
Formatierung
|
||
|
||
```java
|
||
MonetaryAmount amount = Money.of(1234.56, "CHF");
|
||
|
||
Locale locale = Locale.forLanguageTag("de-CH");
|
||
MonetaryAmountFormat format = MonetaryFormats.getAmountFormat(locale);
|
||
|
||
String formatted = format.format(amount);
|
||
|
||
System.out.println(formatted); // CHF 1’234.56
|
||
```
|
||
|
||
<div class="my-6"></div>
|
||
|
||
- Formatierung abhängig von Locale
|
||
- Währung wird korrekt dargestellt
|
||
- Kein manuelles String-Handling
|
||
|
||
👉 Darstellung ist kontextabhängig, nicht Teil des Betrags
|
||
|
||
---
|
||
|
||
# Grundlagen
|
||
|
||
Formatierung nach Locale
|
||
|
||
| **Locale** | **Ausgabe** |
|
||
|---------------|------------------|
|
||
| Deutschland | 1.234,56 CHF |
|
||
| USA | CHF1,234.56 |
|
||
| Schweiz (de) | CHF 1’234.56 |
|
||
|
||
<div class="my-6"></div>
|
||
|
||
- Gleicher Betrag, unterschiedliche Darstellung
|
||
- Formatierung ist kontextabhängig
|
||
|
||
---
|
||
|
||
# Grundlagen
|
||
|
||
Parsen
|
||
|
||
```java
|
||
Locale locale = Locale.forLanguageTag("de-CH");
|
||
MonetaryAmountFormat format = MonetaryFormats.getAmountFormat(locale);
|
||
|
||
MonetaryAmount amount = format.parse("CHF 1’234.56");
|
||
|
||
System.out.println(amount); // CHF 1234.56
|
||
```
|
||
|
||
<div class="my-6"></div>
|
||
|
||
- Parsing ist abhängig von Locale und Format
|
||
- Währung und Betrag werden gemeinsam eingelesen
|
||
- Kein manuelles Zerlegen von Strings
|
||
|
||
👉 Auch das Einlesen ist kontextabhängig
|
||
|
||
---
|
||
|
||
# Grundlagen
|
||
|
||
Einlesen nach Locale
|
||
|
||
| **Eingabe** | **Locale** | **Ergebnis** |
|
||
|-----------------|------------|--------------|
|
||
| CHF 1’234.56 | de-CH | gültig |
|
||
| 1.234,56 CHF | de-CH | ungültig |
|
||
| CHF1,234.56 | en-US | gültig |
|
||
| CHF 1’234.56 | en-US | ungültig |
|
||
|
||
<div class="my-6"></div>
|
||
|
||
- Parsing ist strikt formatabhängig
|
||
- Falsches Locale: Fehler oder falsches Ergebnis
|
||
|
||
---
|
||
|
||
# Grundlagen
|
||
|
||
Was haben wir gelernt?
|
||
|
||
- Geldbeträge korrekt modellieren (Betrag + Währung)
|
||
- Mit Geldbeträgen rechnen statt mit Zahlen
|
||
- Rundung als fachliche Entscheidung anwenden
|
||
- Geldbeträge formatieren und einlesen
|
||
- Kontext (Locale, Regeln) bewusst berücksichtigen
|
||
|
||
👉 Jetzt seid ihr dran: ab in die Praxis
|
||
|
||
---
|
||
|
||
# Übungen (Teil 1)
|
||
|
||
Empfohlene Reihenfolge
|
||
|
||
1. Geldbeträge und Währungen erzeugen
|
||
- `MoneyExercises`
|
||
2. Rechnen mit Geldbeträgen
|
||
- `ArithmeticExercises`
|
||
3. Rundung anwenden
|
||
- `RoundingExercises`
|
||
4. Formatierung
|
||
- `FormattingExercises`
|
||
5. Parsing
|
||
- `ParsingExercises`
|
||
6. Alles zusammenführen
|
||
- `IntegrationExercises`
|
||
|
||
---
|
||
|
||
# Pause ☕
|
||
|
||
30 Minuten
|
||
|
||
- Kurz durchatmen
|
||
- Beine vertreten
|
||
- Kaffee holen
|
||
|
||
👉 In 30 Minuten geht’s weiter
|
||
|
||
---
|
||
|
||
# Fortgeschrittene Konzepte
|
||
|
||
Weiter geht’s
|
||
|
||
- Zurück in den Flow
|
||
- Jetzt wird es interessanter
|
||
- Wir gehen einen Schritt tiefer
|
||
|
||
**Was schauen wir uns an?**
|
||
|
||
- Währungsumrechnung
|
||
- Wiederverwendbare Operationen
|
||
- Unterschiedliche Rundungsstrategien
|
||
|
||
👉 Mehr Möglichkeiten als in den Grundlagen
|
||
|
||
---
|
||
|
||
# Fortgeschrittene Konzepte
|
||
|
||
Währungsumrechnung: Herausforderungen
|
||
|
||
- Wechselkurse sind nicht stabil
|
||
- Zeitpunkt der Umrechnung ist relevant
|
||
- Rundung beeinflusst das Ergebnis
|
||
- Richtung der Umrechnung macht einen Unterschied
|
||
- Datenquelle muss vertrauenswürdig sein
|
||
|
||
👉 Umrechnung ist mehr als eine einfache Berechnung
|
||
|
||
---
|
||
|
||
# Fortgeschrittene Konzepte
|
||
|
||
Währungsumrechnung: Lösung
|
||
|
||
```java
|
||
MonetaryAmount amountCHF = Money.of(10, "CHF");
|
||
|
||
CurrencyConversion conversion = MonetaryConversions.getConversion("EUR", "ECB");
|
||
|
||
MonetaryAmount amountEUR = amountCHF.with(conversion);
|
||
|
||
System.out.println(amountEUR); // EUR 10.83306250677066
|
||
```
|
||
|
||
<div class="my-6"></div>
|
||
|
||
- Umrechnung ist eine eigene Operation
|
||
- Wechselkurs wird extern bereitgestellt
|
||
- Ergebnis bleibt ein Geldbetrag
|
||
|
||
---
|
||
|
||
# Fortgeschrittene Konzepte
|
||
|
||
Währungsumrechnung: Wechselkursquelle
|
||
|
||
- Der Geldbetrag kennt keinen Wechselkurs
|
||
- Der Wechselkurs kommt von einem Provider
|
||
- Beispiele verfügbarer Provider:
|
||
- `ECB` – European Central Bank (EZB)
|
||
- `IMF` – International Monetary Fund (IWF)
|
||
- Unterschiedliche Provider liefern unterschiedliche Daten
|
||
- Ohne Provider gibt es keine Umrechnung
|
||
|
||
👉 Der Wechselkurs ist externer Kontext, nicht Teil des Betrags
|
||
|
||
---
|
||
|
||
# Fortgeschrittene Konzepte
|
||
|
||
Währungsumrechnung: Fallstricke
|
||
|
||
- Unterschiedliche Kurse je nach Zeitpunkt
|
||
- Unterschiedliche Ergebnisse je nach Datenquelle
|
||
- Rundung beeinflusst das Ergebnis
|
||
- Mehrere Umrechnungen verstärken Abweichungen
|
||
|
||
👉 Es gibt nicht das eine „richtige“ Ergebnis
|
||
|
||
---
|
||
|
||
# Fortgeschrittene Konzepte
|
||
|
||
Währungsumrechnung: Zeitpunkt
|
||
|
||
| **Zeitpunkt** | **Kurs (CHF → EUR)** | **Ergebnis** |
|
||
|:--------------|---------------------:|--------------:|
|
||
| Gestern | 1.08 | EUR 10.80 |
|
||
| Heute | 1.10 | EUR 11.00 |
|
||
|
||
<div class="my-6"></div>
|
||
|
||
- Gleicher Betrag, unterschiedliches Ergebnis
|
||
- Wechselkurse sind Momentaufnahmen
|
||
|
||
---
|
||
|
||
# Fortgeschrittene Konzepte
|
||
|
||
Währungsumrechnung: Zeitpunkt im Code
|
||
|
||
```java
|
||
MonetaryAmount amountCHF = Money.of(10, "CHF");
|
||
|
||
ConversionQuery query = ConversionQueryBuilder.of()
|
||
.setTermCurrency("EUR")
|
||
.set(LocalDate.class, LocalDate.of(2026, 3, 13))
|
||
.build();
|
||
|
||
CurrencyConversion conversion = MonetaryConversions.getConversion(query);
|
||
|
||
MonetaryAmount amountEUR = amountCHF.with(conversion);
|
||
```
|
||
|
||
<div class="my-6"></div>
|
||
|
||
- Zeitpunkt ist Teil der Anfrage
|
||
- Provider bestimmt den passenden Kurs
|
||
- Nicht jeder Provider unterstützt Zeitpunkte
|
||
- Manche liefern nur Tageskurse, manche ignorieren den Zeitpunkt
|
||
|
||
👉 Ergebnis hängt vom Provider ab
|
||
|
||
---
|
||
|
||
# Fortgeschrittene Konzepte
|
||
|
||
Fachliche Operationen
|
||
|
||
**MonetaryOperator**
|
||
|
||
- Kapselt fachliche Operationen auf Geldbeträgen
|
||
- Wird auf einen `MonetaryAmount` angewendet
|
||
- Macht Fachlogik wiederverwendbar
|
||
- Hält Berechnung und Absicht zusammen
|
||
|
||
👉 Zum Beispiel: Rabatt, Steuer, Gebühren, Rundung
|
||
|
||
---
|
||
|
||
# Fortgeschrittene Konzepte
|
||
|
||
Fachliche Operationen: Beispiel
|
||
|
||
```java
|
||
MonetaryOperator discount = amount -> amount.multiply(0.9);
|
||
|
||
MonetaryAmount price = Money.of(100, "CHF");
|
||
MonetaryAmount reduced = price.with(discount);
|
||
```
|
||
|
||
<div class="my-6"></div>
|
||
|
||
- Ein `MonetaryOperator` verändert einen Geldbetrag
|
||
- Anwendung mit `with(...)`
|
||
- Fachlogik wird benennbar und wiederverwendbar
|
||
- Rabattlogik nicht mehrfach schreiben
|
||
- Steuerberechnung zentral kapseln
|
||
- Rundungsregeln wiederverwenden
|
||
|
||
---
|
||
|
||
# Fortgeschrittene Konzepte
|
||
|
||
Informationen gezielt auslesen
|
||
|
||
**MonetaryQuery**
|
||
|
||
- Liest gezielt Informationen aus einem Geldbetrag
|
||
- Verändert den Geldbetrag nicht
|
||
- Kapselt wiederverwendbare Ausleselogik
|
||
|
||
👉 Das Gegenstück zum `MonetaryOperator`
|
||
|
||
---
|
||
|
||
# Fortgeschrittene Konzepte
|
||
|
||
Informationen gezielt auslesen: Beispiel
|
||
|
||
```java
|
||
MonetaryQuery<String> currencyCode =
|
||
amount -> amount.getCurrency().getCurrencyCode();
|
||
|
||
MonetaryAmount amount = Money.of(100, "CHF");
|
||
String code = amount.query(currencyCode);
|
||
```
|
||
|
||
<div class="my-6"></div>
|
||
|
||
- Anwendung mit `query(...)`
|
||
- Ergebnis ist frei wählbar
|
||
- Fachliche Ausleselogik wird wiederverwendbar
|
||
|
||
---
|
||
|
||
# Fortgeschrittene Konzepte
|
||
|
||
Der Kontext entscheidet
|
||
|
||
**Was beeinflusst eigentlich einen Geldbetrag?**
|
||
|
||
- Wie viele Nachkommastellen sind erlaubt?
|
||
- Wie präzise wird gerechnet?
|
||
- Welche Rundung wird verwendet?
|
||
|
||
👉 Diese Eigenschaften gehören nicht zum Wert selbst
|
||
|
||
---
|
||
|
||
# Fortgeschrittene Konzepte
|
||
|
||
Der Kontext entscheidet
|
||
|
||
**MonetaryContext**
|
||
|
||
- Beschreibt Eigenschaften eines Geldbetrags
|
||
- Zum Beispiel:
|
||
- Präzision
|
||
- maximale Nachkommastellen
|
||
- Wird von Implementierungen verwendet
|
||
|
||
👉 Der Kontext bestimmt, wie gerechnet wird
|
||
|
||
---
|
||
|
||
# Fortgeschrittene Konzepte
|
||
|
||
Der Kontext entscheidet
|
||
|
||
**MonetaryContext**
|
||
|
||
- Ein Teil ist für alle gleich
|
||
- Implementierungen können den Kontext erweitern
|
||
- Unterschiedliche Implementierungen können sich unterschiedlich Verhalten
|
||
- Beispiel:
|
||
- `Money` → hohe Präzision
|
||
- `FastMoney` → feste Skalierung, begrenzte Präzision
|
||
|
||
👉 Gleicher Wert, unterschiedliches Verhalten
|
||
|
||
---
|
||
|
||
# Fortgeschrittene Konzepte
|
||
|
||
Rundungsstrategien
|
||
|
||
- Anzeige für den Benutzer
|
||
- Berechnung von Steuern
|
||
- Barzahlung (z. B. CHF 0.05)
|
||
- Interne Berechnungen
|
||
|
||
**Das bedeutet**
|
||
|
||
- Rundung ist eine fachliche Entscheidung
|
||
- Kann je Use Case unterschiedlich sein
|
||
- Wird explizit angewendet
|
||
|
||
👉 Unterschiedliche Situationen, unterschiedliche Regeln
|
||
|
||
---
|
||
|
||
# Fortgeschrittene Konzepte
|
||
|
||
Rundungsstrategien im Code
|
||
|
||
```java
|
||
MonetaryOperator vat = amount -> amount.multiply(0.077);
|
||
MonetaryOperator rounding = Monetary.getDefaultRounding();
|
||
|
||
MonetaryAmount result =
|
||
amount
|
||
.with(vat)
|
||
.with(rounding);
|
||
```
|
||
|
||
<div class="my-6"></div>
|
||
|
||
- Fachlogik wird kapselbar
|
||
- Rundung ist ein eigener Schritt
|
||
- Operatoren lassen sich kombinieren
|
||
|
||
---
|
||
|
||
# Fortgeschrittene Konzepte
|
||
|
||
Was haben wir gesehen?
|
||
|
||
- Währungsumrechnung ist kontextabhängig
|
||
- Fachlogik lässt sich kapseln (`MonetaryOperator`)
|
||
- Informationen gezielt auslesen (`MonetaryQuery`)
|
||
- Kontext beeinflusst das Verhalten (`MonetaryContext`)
|
||
- Rundung ist eine bewusste Entscheidung
|
||
|
||
👉 Jetzt seid ihr dran: ab in die Praxis
|
||
|
||
---
|
||
|
||
# Übungen (Teil 2)
|
||
|
||
Empfohlene Reihenfolge
|
||
|
||
1. Währungsumrechnung
|
||
`CurrencyConversionExercises`
|
||
|
||
2. Fachlogik kapseln
|
||
`MonetaryOperatorExercises`
|
||
|
||
3. Informationen auslesen
|
||
`MonetaryQueryExercises`
|
||
|
||
4. Kontext verstehen
|
||
`MonetaryContextExercises`
|
||
|
||
5. Rundungsstrategien anwenden
|
||
`RoundingStrategyExercises`
|
||
|
||
---
|
||
|
||
# Architektur & Praxisintegration
|
||
|
||
Worum geht es jetzt?
|
||
|
||
- Geldbeträge im Domänenmodell
|
||
- Persistenz und Mapping
|
||
- Fachlogik an der richtigen Stelle
|
||
- Übergang von API-Wissen zu Anwendungsarchitektur
|
||
|
||
---
|
||
|
||
# Architektur & Praxisintegration
|
||
|
||
Geld gehört ins Domänenmodell
|
||
|
||
- Geld ist kein `double`
|
||
- Geld ist kein `BigDecimal`
|
||
- Geld ist kein Paar aus Zahl und String
|
||
|
||
👉 Geld ist ein eigener fachlicher Typ
|
||
|
||
---
|
||
|
||
# Architektur & Praxisintegration
|
||
|
||
Wo gehört die Logik hin?
|
||
|
||
- Nicht in die UI
|
||
- Nicht in SQL
|
||
- Nicht verstreut in Utility-Klassen
|
||
|
||
**Sondern:**
|
||
|
||
- ins Domänenmodell
|
||
- in fachliche Services
|
||
- in klar benannte Operationen
|
||
|
||
---
|
||
|
||
# Architektur & Praxisintegration
|
||
|
||
Persistenz
|
||
|
||
- Datenbank kennt meist keinen `MonetaryAmount`
|
||
- Betrag und Währung müssen gespeichert werden
|
||
- Mapping ist eine bewusste Entscheidung
|
||
|
||
👉 Fachmodell und Persistenzmodell sind nicht dasselbe
|
||
|
||
---
|
||
|
||
# Architektur & Praxisintegration
|
||
|
||
Typische Mapping-Strategien
|
||
|
||
**Zwei Spalten:**
|
||
|
||
- Betrag
|
||
- Währung
|
||
|
||
**Optional:**
|
||
|
||
- Embeddable / Value Object
|
||
- Converter für Persistenz
|
||
|
||
👉 Wichtig ist Konsistenz
|
||
|
||
---
|
||
|
||
# Architektur & Praxisintegration
|
||
|
||
Was ist eine gute Lösung?
|
||
|
||
- Fachlich korrekt
|
||
- Im Code lesbar
|
||
- Persistierbar
|
||
- Testbar
|
||
- Ohne versteckte Logik
|
||
|
||
---
|
||
|
||
# Übungen (Teil 3)
|
||
|
||
Warenkorb / Bestellung
|
||
|
||
- Zwischensumme berechnen
|
||
- Rabatt anwenden
|
||
- Steuer berechnen
|
||
- Gesamtbetrag bestimmen
|
||
- Ergebnis formatieren
|
||
|
||
👉 Setzt die Schritte nacheinander um
|
||
👉 Verwendet die bisherigen Konzepte bewusst
|
||
|
||
**Zusatzaufgabe**
|
||
|
||
- Gesamtbetrag zusätzlich in EUR anzeigen
|
||
- Währungsumrechnung verwenden
|
||
|
||
👉 Optional für schnelle Teilnehmer
|
||
|
||
---
|
||
|
||
# Bonusaufgabe 1
|
||
|
||
Eigener Exchange Rate Provider
|
||
|
||
Package: `swiss.fihlon.workshop.money.part4`
|
||
|
||
- Implementiere einen eigenen `ExchangeRateProvider`
|
||
- Lade Wechselkurse aus `exchange-rates.csv`
|
||
- CSV enthält:
|
||
- Quellwährung
|
||
- Zielwährung
|
||
- Wechselkurs
|
||
- Stelle Umrechnungen zwischen den Währungen bereit
|
||
- Noch **ohne** Historisierung
|
||
|
||
Relevante Interfaces: `ExchangeRateProvider`
|
||
|
||
👉 Ziel: eigener Provider mit lokal geladenen Kursen
|
||
|
||
---
|
||
|
||
# Bonus 2
|
||
|
||
Historische Kurse
|
||
|
||
Package: `swiss.fihlon.workshop.money.part4`
|
||
|
||
- Erweitere deinen Provider aus Bonus 1
|
||
- Verwende `exchange-rates-hist.csv`
|
||
- CSV enthält zusätzlich:
|
||
- Datum pro Wechselkurs
|
||
- Wähle den passenden Kurs für ein angefragtes Datum
|
||
- Fallback: sinnvoller Standard (z. B. letzter verfügbarer Kurs)
|
||
|
||
Relevante Interfaces: `ExchangeRateProvider`
|
||
|
||
👉 Ziel: zeitabhängige Wechselkurse unterstützen
|
||
|
||
---
|
||
|
||
# Bonus 3
|
||
|
||
Eigene Währung
|
||
|
||
Package: `swiss.fihlon.workshop.money.part4`
|
||
|
||
- Implementiere die eigene Währung `DKP`
|
||
- Vorgegebener Umrechnungsfaktor:
|
||
- 10 CHF = 1 DKP
|
||
- Baue einen eigenen Converter
|
||
- Unterstütze:
|
||
- CHF → DKP
|
||
- DKP → CHF
|
||
|
||
Relevante Interfaces: `CurrencyUnit` und `ExchangeRateProvider`
|
||
|
||
👉 Ziel: eigene Währung ins Modell integrieren
|
||
|
||
---
|
||
|
||
# Voraussetzungen
|
||
|
||
Java-Versionen
|
||
|
||
- JSR-354 läuft auf modernen Java-Versionen
|
||
- Typischerweise:
|
||
- Java 11+
|
||
- Java 17 (LTS)
|
||
- Java 21 (LTS)
|
||
- Wichtig:
|
||
- Einheitliche Version im Team
|
||
- Gleiche Version für Workshop und Übungen
|
||
|
||
👉 Unterschiedliche Versionen können Verhalten beeinflussen
|
||
|
||
---
|
||
|
||
# Voraussetzungen
|
||
|
||
Dependencies
|
||
|
||
- Basis:
|
||
- `moneta-core`
|
||
- Für Währungsumrechnung zusätzlich nötig:
|
||
- z. B. `moneta-convert-ecb`, `moneta-convert-imf`
|
||
- oder andere Provider
|
||
- Wichtig:
|
||
- Ohne Provider keine Currency Conversion
|
||
- Verhalten hängt vom Provider ab
|
||
|
||
👉 Dependencies bewusst wählen
|
||
|
||
---
|
||
|
||
# Wrap-up
|
||
|
||
Was bleibt hängen?
|
||
|
||
- Geld ist ein Domänenkonzept
|
||
- Rundung ist Fachlogik
|
||
- Kontext beeinflusst Verhalten
|
||
- Umrechnung braucht externe Daten
|
||
- Die API kann mehr als nur rechnen
|
||
|
||
---
|
||
|
||
# Wrap-up
|
||
|
||
Worauf kommt es in der Praxis an?
|
||
|
||
- Fachmodell vor Technik
|
||
- Klare Verantwortung im Code
|
||
- Explizite Rundung
|
||
- Konsistentes Mapping
|
||
- Wiederverwendbare Logik
|
||
|
||
---
|
||
layout: center
|
||
---
|
||
|
||
# Fragen?
|
||
|
||
---
|
||
layout: center
|
||
---
|
||
|
||
# Danke!
|