Die Backend-Schnittstelle für die Finanzverwaltung (Teil 2)

Im ersten Teil habe ich das initiale Modell und die ersten Anforderungen der Anwendung beschrieben. Nun geht es um die Backend-Schnittstelle, die die beschriebenen Anwendungsfälle ermöglicht.

Das Projekt für die Anwendung

Als Basis für – insbesondere das Backend – dient Spring Boot. Mit Hilfe einer unterstützenden Entwicklungsumgebung oder auf der Startseite ist das Projekt schnell konfiguriert.

Ich ergänze das Projekt mit den Startern bzw. Abhängigkeiten

  • Spring Web,
  • Lombok,
  • Spring Boot DevTools,
  • Spring Data JPA und
  • H2

Für die (REST-)Schnittstelle benötige ich den Web-Starter. Mit Lombok spare ich mir den Standardcode (Getter, Setter, …). Die DevTools unterstützen die Entwicklung. Beispielsweise startet der Server nach Änderungen automatisch neu, damit die Änderungen wirksam werden.

Die übrigen Starter sind Grundlage für eine erste konkrete Implementierung der beschriebenen Anwendungsfälle.

Entwicklungshilfe

Für die Entwicklung und den Test füge ich Swagger hinzu. Die pom.xml erhält die Abhängigkeiten

<dependency>
  <groupId>io.springfox</groupId>
  <artifactId>springfox-swagger2</artifactId>
  <version>${springfox.swagger2.version}</version>
</dependency>

<dependency>
  <groupId>io.springfox</groupId>
  <artifactId>springfox-swagger-ui</artifactId>
  <version>${springfox.swagger2.version}</version>
</dependency>		

In den Property setze ich die aktuelle Version (aktuell 2.9.2) ein.

Es fehlt noch die Konfiguration, dann ist die Test-Oberfläche unter http://localhost:8080/swagger-ui.html verfügbar. 

@Configuration
@EnableSwagger2
public class SwaggerConfig {

	@Bean
    public Docket api() { 
        return new Docket(DocumentationType.SWAGGER_2)  
          .select()                                  
          .apis(RequestHandlerSelectors.any())              
          .paths(PathSelectors.any())                          
          .build();                                           
    }
}

Die Backend-Schnittstelle

Domain Modell

Zunächst lege ich das Domain-Modell in einem eigenen Package an. Die Umsetzung entspricht dem Diagramm aus dem vorhergehenden Beitrag.

@Data
public class Buchung {

	private String verwendung;
	private String empfaenger;
	private List<Umsatz> umsaetze = new ArrayList<>();
	
	public void addUmsatz(Umsatz umsatz) {
		getUmsaetze().add(umsatz);
	}
}
@Data
public class Umsatz {

	private LocalDate valuta;
	private BigDecimal betrag;
	private Konto konto;
	private Buchung buchung;
}

Das Konto enthält die Operation verbuche(.), die einen Umsatz zum Konto aufnimmt. Dabei übernimmt sie den Umsatz in die Liste der zum Konto gehörenden Umsätze und addiert gleichzeitig den Betrag aus dem Umsatz zum Saldo. Da Soll- und Haben-Beträge über das Vorzeichen unterschieden sind, ist keine weitere Logik notwendig.

@Data
public class Konto {

	private String name;
	private BigDecimal saldo;
	private List<Umsatz> umsaetze = new ArrayList<>();

	public void verbuche(Umsatz umsatz) {
		umsaetze.add(umsatz);
		saldo = saldo.add(umsatz.getBetrag());
	}
}

Die REST-Schnittstelle der Anwendung

Die Backend-Schnittstelle ist eine REST-Schnittstelle und entsteht in Spring durch eine Controller-Klasse. Dank der Spring Boot ist die Umsetzung einfach. Wer sich dabei unsicher ist, findet in den Spring Boot Guides schnelle Anleitungen.

Die BuchhaltungController-Klasse setzt dabei die im vorausgegangenen Beitrag definierten drei Anwendungsfälle um:

  • Buchen erstellen
  • Konten abfragen
  • Umsatz anzeigen

Die Umsetzung ist noch nicht final, denn beispielsweise ist die Abfrage der Buchungen je Konto ohne Paging nicht sinnvoll. Die Liste wird nach einiger Zeit einfach zu lang, was zu unnötigen Verzögerungen beim Laden, Übertragen und Darstellen in der Oberfläche führt. Auch mag ich das Konto lieber über eine technische Id abfragen, um Problemen mit Leerzeichen oder Sonderzeichen vorzubeugen.

@RestController
@RequestMapping("api/buchhaltung")
public class BuchhaltungController {

	@GetMapping("konto")
	public List<Konto> findAllKonten() {
	    throw new UnsupportedOperationException();
	}
	
	@GetMapping("konto/{kontoName}/umsatz")
	public List<Buchung> findKontoBuchungenByKontoName(
                @PathVariable("kontoName") String kontoName) {
	    throw new UnsupportedOperationException();
	}

	@PostMapping("buche")
	public ResponseEntity<String> buche(
                @RequestBody Buchung buchung) {
	    throw new UnsupportedOperationException();
	}
}

Eigene Klassen für die Datenübertragung in der Backend-Schnittstelle

Für die Datenübertragung in der Schnittstelle möchte ich eigene Transfer-Klassen einsetzten. Die Services sollen mit Domain-Objekten arbeiten. Deren Klassen möchte ich allerdings frei von Schnittstellen spezifischen Abhängigkeiten halten, und die wären für die Umwandlung in JSON erforderlich. Insbesondere sind die Relationen bidirektional implementiert, was ohne Zutun wegen zu endlosen Rekursionen führen würde. Die API-Klassen haben das folgende Aussehen:

@Data
public class Buchung {
	private String verwendung;
	private String empfaenger;
	private List<Umsatz> umsaetze = new ArrayList<>();
}

@Data
public class Umsatz {
	private LocalDate valuta;
	private BigDecimal betrag;
	private Konto konto;
}

@Data
@JsonInclude(Include.NON_NULL)
public class Konto {
	private String name;
	private BigDecimal saldo;
}

Im Konto sorgt die JsonInclude-Annation dafür, dass nur Felder in das JSON geschrieben werden, die auch tatsächlich Werte enthalten.

Die Übertragung erfolgt in Mapper-Klassen.

@Component
public class KontoMapper {
	public List<Konto> domainKontoListToApiKontoList(
                List<buchhaltung.domain.Konto> domainKonten) {
	    return domainKonten.stream()
		.map( domainKonto ->
                        domainKontoToApiKonto(domainKonto) )
				.collect(Collectors.toList());
	}

	public Konto domainKontoToApiKonto(
            buchhaltung.domain.Konto domainKonto) {
		Konto apiKonto = new Konto();
		apiKonto.setName(domainKonto.getName());
		apiKonto.setSaldo(domainKonto.getSaldo());
		return apiKonto;
	}
}

Der Konto-Mapper steht als Beispiel für das Codierungsmuster. Die Formalität sollte helfen, diese Transformation später ggf. durch Hilfsbibliotheken zu automatisieren, beispielsweise durch MapStruct.

Das folgende Bild zeigt in der Übersicht die Struktur der Schnittstelle.

Backend-Schnittstelle
Rest-Schnittstelle mit Mapper

Der Abschluss

Ergänzt um Service-Klassen mit Methoden-Signaturen entsteht als Schnittstelle der folgende Controller:

@RestController
@RequestMapping("api/buchhaltung")
public class BuchhaltungController {
	private KontoService kontoService;
	private BuchungService buchungService;
	private KontoMapper kontoMapper;
	private BuchungMapper buchungMapper;

	public BuchhaltungController(
			KontoService kontoService, 
			BuchungService buchungService, 
			KontoMapper kontoMapper,
			BuchungMapper buchungMapper) {

		this.kontoService = kontoService;
		this.buchungService = buchungService;
		this.kontoMapper = kontoMapper;
		this.buchungMapper = buchungMapper;
	}

	@GetMapping("konto")
	public List<Konto> findAllKonten() {
		return kontoMapper.domainKontoListToApiKontoList(
				kontoService.findAll());
	}
	
	@GetMapping("konto/{kontoName}/umsatz")
	public List<Buchung> findKontoBuchungenByKontoName(
           @PathVariable("kontoName") String kontoName) {
	    return buchungMapper
             .domainBuchungListToApiBuchungListe(
                buchungService
                  .findBuchungByKontoName(kontoName));
	}

	@PostMapping("buche")
	public ResponseEntity<String> buche(
                @RequestBody Buchung buchung) {
	  try {
            buchungService.buche(
              buchungMapper.apiBuchungToDomainBuchung(buchung));
	  } catch (Exception e) {
	    return ResponseEntity
                .unprocessableEntity()
                .body("Fehler: " + e.getMessage());
	  }
	  return ResponseEntity.accepted().body("Verarbeitet");
	}
}

finally

Die Schnittstelle ist damit beschrieben und es steht die eigentliche Implementierung an. Mein erster Wurf wird auf einer relationalen Datenbank zur Speicherung basieren. Für die Anbindung auf der grünen Wiese, verwende ich die Java Persistente API (JPA). Doch dazu beim nächsten mal mehr.

Happy Coding!

Anwendung entwickeln für zu Hause – eine Finanzverwaltung

Mein Quicken ist in die Jahre gekommen. Mag ich das aber wirklich erneuern, schließlich arbeite ich als Software-Entwickler? Da reift die Überlegung eine Anwendung selbst zu entwickeln.

Die Anforderungen der Anwendung

Bevor ein erster Prototyp entsteht, gilt es erst mal einfache Anforderungen zusammen zu tragen.

Was sollte ein erster Prototyp können? Eine einfache Tabelle mit Datum, Beschreibung und Betrag reicht nicht. Das wäre praktisch ein Kassenbuch. Ich habe dann doch mehr als ein Konto und – Hand aufs Herz – ein Kassenbuch geht mit jeder Tabellenkalkulation.
Die doppelte Buchführung sollte es schon sein. Das ist es in Quicken auch, die Gegenkonten heißen dort halt nur Kategorie. Mehrteilige Buchungen sollten auch möglich sein. Vom Giro-Konto buchen beispielsweise die Stadtwerke ab, der Betrag setzt sich dabei aus den Strom- und den Gas-Kosten zusammen. Wollte ich einen Vergleich dieser Kosten für alternative Anbieter machen, müßte ich das schon trennen können.

Die Modellierung ist nicht neu. Was auf der Arbeit funktioniert, gilt auch für die Software zu Hause: das Rad nicht neu erfinden. Zumindest ist das Datenmodell schon da – zumindest im Prinzip. Martin Fowler hat sich schon vor einiger Zeit Gedanken dazu gemacht: https://martinfowler.com/eaaDev/AccountingNarrative.html

Das Datenmodell

Die erste Wurf für das Modell der Anwendung sieht wie folgt aus. Das ist eine eingedeutschte Variante des Fowler-Vorschlages.

Klassenmodell
Klassenmodell der Anwendung

Die gute doppelte Buchführung: eine Buchung ist immer Soll an Haben oder als Text-Format: “Per Soll-Konto, Betrag an Haben-Konto, Betrag”, wobei es jeweils mehrer Soll- und Haben-Konten geben kann. Die Summe der Soll und Haben-Beträge muss 0 sein. Soll-Beträge sind positiv und Haben-Beträge negativ. Das entspricht dem gewohnten Gefüge, dass Guthaben positiv sind und Schulden negativ.

Die Buchung fasst die (Konto-)Umsätze zusammen. Jeder Umsatz kennt seine Buchung – kann ohne diese auch gar nicht existieren – und er kennt das Konto, welches er anspricht.

Der Saldo des Kontos ergibt sich formal als die Summe der Beträge aller Umsätze des Kontos. Fraglich ist, ob der immer aufs Neue berechnet wird oder der letzte Stand gespeichert ist. Im Unternehmen wäre die Antwort das speichern des Saldos, da die Berechnung auf sehr viele Umsätze zu lange dauert und im Verlauf der Zeit immer länger. Ich lasses es dabei.

Erste Anwendungsfälle der Anwendung

Die folgenden Anwendungsfälle mag ist initial umsetzten:

  • Buchen erstellen
    Das ist praktisch das Aufbauen des Hauptbuches. Initial mag ich ein abgesprochenes Konto automatisch anlegen, wenn es noch nicht existiert. Das spart erst mal einen Anwendungsfall “Konto anlegen”.
  • Konten abfragen
    Ich bekomme eine Liste aller existierenden Konten mit deren aktuellen Salden. Das ist praktisch das Grundbuch.
  • Umsatz anzeigen
    Zuletzt mag ich noch die Umsätze eines Kontos ansehen können. Hier sind die Details sichtbar, um zum Beispiel Ausgaben zu vergleichen oder Positionen zu kontrollieren.

Die technische Basis – ein erster Ansatz

Finanzdaten mag ich nicht unnötig offen haben, also ist der private Rechner meine Zielplattform. Was liegt näher als das für eine kaufmännische Anwendung gewohnte Setup zu wählen.

Auf einem Rechner einen Browser für die Anwendung? Warum nicht, denn was ist mit HTML, CSS und Javascript nicht machbar. Diesen Ansatz verfolgt beispielsweise das Project Electronjs. Hier kommt vollständig Javascript zum Einsatz und es entsteht eine ausführbare Binärdatei, die für unterschiedliche Betriebssysteme erstellt werden kann.

Nun bin ich in – gerade was die Fachlogik angeht – mehr in der Java-Welt zu Hause. Dank der Graal VM ist auch hier mal eine kompilierte Version denkbar und die Entwicklung geht schneller von der Hand.

Aus der täglichen Gewohnheit mag ich mit den Mitteln arbeiten, die ich bereits gut kenne. Das bedeutet: relationale Datenbank, Spring Boot als Backend und Angular für die Oberfläche.

Natürlich bleibt das ganze zu überdenken. Für den Zugang ist ein Browser mit der entsprechenden URL zu starten. Das ist allerdings lösbar.

Für die Datenbank bietet sich eine eingebundene (embedded) Variante an, um zusätzliche Installationen zu vermeiden, z.B. H2.
Da das Datenmodell auf der “grünen Wiese” entsteht, möchte ich als Anbindung an die Datenbank die Java Persistente API (JPA) nutzen, da JPA das Schema direkt erstellen kann und somit Arbeit spart.

Die nächsten Schritte zur Anwendung

Im nächsten Beitrag beschreibe ich zunächst die Schnittstelle des Backends, welches die umzusetzenden Anwendungsfälle darstellt.

Dann schau ich mir die zugehörige Implementierung an. Da steckt etwas Forschung drin. Denn diese Anwendung ist nicht für ein großes Finanzunternehmen gedacht. Im Sinne des KISS-Prinzips gibt es vielleicht Alternativen.

Bis dahin: Happy coding!

Tutorial – JavaFX Custom Dialog

JavaFX enthält ab Version 8 auch Dialogklassen. Dazu gehören die vorgefertigten Standarddialoge Alert (Warnung), TextInputDialog (Texteingabe) und ChoiceDialog (Auswahl). Über deren Verwendung ist im Internet auch einiges zu finden. Das Erstellen eigener Dialoge wird dagegen kaum beschrieben. Am Beispiel eines Login-Dialogs soll dieser Blog das ändern. Der Beispielcode erstellt die Dialogoberfläche einmal programmatisch und einmal per FXML. Der Beispielcode ist auf Github  zu finden.

Das Basisvorgehen in JavaFX

Für eigene Dialoge bietet sich ein Blick auf die Struktur der vorgefertigten Dialoge an

Klassenstruktur der Dialoge
Klassenstruktur der Java FX Dialoge und der darstellenden DialogPane

Am einfachsten leitet sich der eigene Dialog – wie bei den vorhandenen Dialogen – von der Klasse javafx.scene.control.Dialog<R> ab. <R> ist der Rückgabetyp. Für die Darstellung der Oberfläche inklusive der Buttons ist die DialogPane-Eigenschaft des eigenen Dialogs zu setzen oder zu bestücken. Abschließend benötigt der eigene Dialog noch einen Konverter, um die relvanten Daten aus den Eingabefeldern in ein Rückgabeobjekt zu transferieren.

Der Aufruf einer eigenen Dialogklasse ist dann z.B.

CodedLoginDialog codedDialog = new CodedLoginDialog();
Optional result = codedDialog.showAndWait();
result.ifPresent(login -> System.out.println(login));

Bei Abbruch des Dialogs enthält das Optional keinen Wert, die Ausgabe findet entsprechend nicht statt. Die Javadoc zur Dialog-Klasse zeigt weitere Aufrufvarianten.

Erstellen der Dialogoberfläche

DieDialogPaneeines Dialogs bestimmt das Aussehen.

Dialoglayout
Standardlayout der DialogPane

Das wichtigste dürfte der Content sein. Im Bild ist ihm eine Grid mit Labeln und Eingabefeldern eines Logins zugewiesen.

Diverse optionale Ergänzungen sind möglich:

  • Zusätzlich zum Titel des Dialogs ist noch ein Headertext möglich.
  • Ein Bild/Icon (graphic-Eigenschaft) ist im Bild nicht dargestellt. Dessen Position hängt vom Headertext ab. Ist ein Headertext angegeben, erscheint ein Bild rechts vom Headertext, andernfalls links vom Content.
  • Ein expandierbarer Content sorgt wie im Bild für einen Button, der auf Anforderung den Inhalt des expandable Contents anzeigt.

Programmatische Oberfläche

In der eigenen Dialogklasse ist der Content der DialogPane zu befüllen

this.grid = new GridPane();
...

final DialogPane dialogPane = getDialogPane();
dialogPane.setContent(grid);

...

FXML-Oberfläche

Seit der Version 8 des SceneBuilder bietet dieser die Möglichkeit eine DialogPane grafisch zu erstellen und als FXML-File zu speichern.

Mit Hilfe des FXMLLoader erhält eigenen Dialog die gewünsche DialogPane:

DialogPane dialogPane =
    (DialogPane)FXMLLoader.load(
        getClass().getResource("/fxml/DialogPane.fxml"));

...

setDialogPane(dialogPane);

Buttons ergänzen

Die Buttons eines Dialoges speichert die DialogPane in der Eigenschaft Buttontypes. Es gibt diverse vordefinierte Buttons, die der Buttontype-Collection hinzugefügt werden können. Im Beispiel enthält der Dialog einen Abbruch und einen Ok-Button:

dialogPane.getButtonTypes().addAll(ButtonType.CANCEL, ButtonType.OK);

Obwohl in der DialogPane verborgen, sind die vordefinierten Button im Nachgang noch anpassbar, z.B. der Text des Ok-Buttons

Button okButton = (Button)dialogPane.lookupButton(ButtonType.OK);
    okButton.setText("Login");

Der Konverter

Nach dem Ok soll der Dialog nun auch die Eingaben verpackt in der Ergebnisklasse zurück liefern. Das ist Aufgabe des Konverters, der in eigenen Klassen entsprechend zu definieren ist

setResultConverter((dialogButton) -> {
    ButtonData data =
        dialogButton == null ? null : dialogButton.getButtonData();
    return data == ButtonData.OK_DONE
        ? new Login(usernameField.getText(), passwordField.getText())
        : null;
});

Ist im Beispiel der Dialog mit dem Ok-Button beendet worden, so legt das Lambda einen neues Ergebnisobjekt an und übergibt dem Konstruktur die Daten aus den Eingabefeldern.

Für die Konvertierung benötigt das Lambda referenzen auf die Eingabefelder. Bei der programmatischen Erstellung der Oberfläche sind diese schnell als Attribute der neuen Dialog-Klasse deklariert. Anders verhält es sich, wenn die Oberfläche per FXMLLoader erstellt ist. In diesem Fall muss die Dialogklasse die Referenzen im FXML-Objektbaum suchen. Die gewünschten Elemente können im FXML per Id gekennzeichnet werden

<TextField fx:id="usernameField"...

um anschließend nach dem Laden im Elementbaum der DialogPane gesucht zu werden

final TextField usernameField =
   (TextField)dialogPane.lookup("#usernameField");

Validierung

Die Eingaben eines Dialogs sollten natürlich nur weiter gegeben werden, wenn sie sinnvoll sind. Über Data-Binding könnten nun Hinweise, Markierungen, etc. in den Content eingeblendet werden. Aber auch der Ok-Button sollte nur bei vollständigen Eingaben überhaupt aktiv sein. In Beispiel muss mindestens ein User angegeben sein, bevor ein Login möglich ist.

Zuvor wurde der Text des Ok-Buttons angepasst. Aber auch die übrigen Buttoneigenschaften sind ansprechbar

okButton.setDisable(true);
usernameField.textProperty().addListener(
            (event, oldValue, newValue)
                -> okButton.setDisable(newValue.trim().isEmpty()));

Fazit

Solange das vorgegebene Layout der für die eigenen Zwecke ausreicht, sind eigene Dialoge seit Java 8 recht einfach zu erstellen.