Schematische Darstellung der heat-convection-app. Im Zentrum befindet sich das fachliche Modell des konvektiven Wärmeübergangs an einer längs überströmten Platte. Elliptisch darum angeordnet sind typische Bausteine einer Spring Boot-Enterprise-Anwendung wie Persistenz, Logik, Security, Authentication, Web, API, Konfiguration, Monitoring, Deployment und CI/CD. Für diesen Artikel ist der Bereich "Konfiguration" hervorgehoben.

Spring Boot für technische Anwendungen - Projektsetup und Basiskonfiguration

heat-convection-app: Referenzprojekt für moderne Java-Backend-Entwicklung

Spring Boot Java Projektsetup Applikationsstruktur Softwarearchitektur Backend heat-convection-app

← Zurück zur Übersicht

Einleitung

Bei meinem ersten Referenzprojekt, der heat-conduction-app stand die Frage im Mittelpunkt, wie sich ein technisches Ingenieurmodell in eine moderne Softwareanwendung überführen lässt. Der Schwerpunkt lag dabei auf Architekturprinzipien, fachlicher Modellierung und der schrittweisen Entwicklung einer Webanwendung. Dafür habe ich bewusst Python und Flask gewählt. Der leichtgewichtige Stack eignet sich hervorragend, um grundlegende Konzepte sichtbar zu machen un einzelne Architekturschritte zu erklären.

Mit zunehmenden Komplexität steigen jedoch auch der Implementierungs- und Konfigurationsaufwand. Viele Funktionen, die in größeren Anwendungen benötigt werden, müssen zunächst selbst integriert und miteinander verbunden werden. Aus diesem Grund entsteht mit der heat-convection-app ein zweites Referenzprojekt. Obwohl beide Anwendungen fachlich eng miteinander verwandt sind, verfolgen sie unterschiedliche Ziele.

Ziel der heat-convection-app

Bei der heat-convection-app liegt der Fokus auf der praktischen Umsetzung einer professionellen Java- und Spring Boot-Anwendung. Ich möchte zeigen welche Schritte notwendig sind, um aus einer technischen Fachanwendung eine erweiterbare Backend-Anwendung zu entwickeln. Java und Spring Boot bieten hierfür ein umfangreiches Ökosystem aus Frameworks und Bibliotheken. Viele typische Anforderungen produktiver Anwendungen, wie Persistenz, Validierung, Security und Testing, lassen sich dadurch strukturiert umsetzen.

Gerade diese Stärke kann zu Beginn jedoch auch eine Herausforderung sein. Durch die Vielzahl an Möglichkeiten und die umfangreiche Autokonfiguration verliert man leicht den Blick für die tatsächlich notwendigen ersten Schritte. An dieser Stelle möchte ich mit dem Artikel anknüpfen, nicht in dem ich die internen Abläufe von Spring Boot erkläre, sondern vielmehr, wie ich ein Spring Boot Projekt mit dem Fokus auf eine technische Anwendung initialisieren, konfigurieren und aufbauen würde.

Im Mittelpunkt stehen dabei:

  • sinnvolle Startabhängigkeiten
  • eine saubere Projekt- und Paketstruktur
  • erste Basiskonfigurationen

Die eigentliche Implementierung der Fachlogik folgt in den kommenden Artikeln schrittweise.

Die Projektinitialisierung

Bevor mit der Implementierung begonnen werden kann, muss das Projekt erstellt und konfiguriert werden. Dabei verfolge ich meist eine ähnliche Vorgehensweise.

Projektdefinition

Zunächst sollten die fachlichen und technischen Rahmenbedingungen definiert werden. Dazu gehören neben dem fachlichen Modell auch erste Entscheidungen zur Softwarearchitektur und zum Technologiestack.

Fachliches Modell

In der heat-convection-app starte ich mit der Berechnung des konvektiven Wärmeübergangs an einer längs überströmten Platte. Die technische Applikationslogik unterteile ich in 5 funktionale Bausteine:

  1. Domänenmodell - Welche fachlichen Objekte und Größen existieren?
  2. Berechnungslogik - Wie werden diese Größen fachlich miteinander verknüpft?
  3. Eingabeschicht - Wie gelangen konkrete Werte ins System?
  4. Ausgabeschicht - Wie werden Ergebnisse dargestellt oder weitergeben?
  5. Orchestrierung - Wie greifen die einzelnen Bausteine kontrolliert ineinander?

Diese fachlichen Bausteine stellen noch keine Softwarearchitektur dar. Sie dienen vielmehr als fachliche Referenzstruktur, um Ingenieurwissen unabhängig von konkreten Technologien oder Frameworks zu strukturieren.

Wer dazu mehr erfahren möchte, findet in meinem Artikel Vom Fourierschen Gesetz zur Softwarelogik eine ausführliche Beschreibung anhand der Wärmeleitung.

Softwarearchitektur

Die grundlegendste Architekturentscheidung ist zunächst die Umsetzung als Webanwendung. Darauf aufbauend folgen weitere Entscheidungen hinsichtlich des Technologie-Stacks, beispielsweise die Wahl der Programmiersprache, des Frameworks, der Persistenzlösung oder des Frontends.

Fragen zu Datenbank, Frontend und Deployment sollten bereits früh mitgedacht werden, auch wenn viele dieser Entscheidungen später noch angepasst oder schrittweise erweitert werden können. Eine mögliche Herangehensweise, die Anwendung in Form von Entwicklungsphasen aufzubauen, habe ich ausführlicher in meinem Artikel Moderne Architektur für technische Web-Applikationen beschrieben.

Beim Frontend ergeben sich grundsätzlich mehrere Optionen:

  • API-First / Headless Backend - die Anwendung stellt ausschließlich eine REST-API bereit.
  • SSR-Frontend (Server-Side Rendering) - die Benutzeroberfläche ist Bestandteil der Anwendung.
  • Externes Frontend - Frontend und Backend sind getrennte Anwendungen und kommunizieren über eine REST-API.

Welche Variante sinnvoll ist, hängt von der jeweiligen Aufgabenstellung ab. Für diesen Beitrag verzichte ich zunächst vollständig auf ein Frontend und konzentriere mich auf die Backend-Struktur.

Das Thema Datenbank werde ich dagegen von Beginn an berücksichtigen. Wer zunächst keinen eigenen Datenbankserver wie PostgreSQL betreiben möchte, kann problemlos mit einer eingebetteten Datenbank wie H2 starten. Eine spätere Migration auf PostgreSQL oder andere produktive Datenbanksysteme ist in der Regel mit überschaubarem Aufwand möglich.

Für dieses Projekt fällt die erste Festlegung daher wie folgt aus:

  • Backend: Java und Spring Boot
  • Datenbank: Spring Data JPA/Hibernate, Flyway Migrationen und H2
  • Testing: Junit5 und Mockito

Diese Entscheidungen bereits zu Beginn zu treffen ist hilfreich, da im nächsten Schritt bei der Erstellung des Projektgerüsts die entsprechenden Abhängigkeiten und Pakete ausgewählt werden.

Projektgerüst & Applikationsstruktur - Spring Initializr

Jede Implementierung startet zunächst mit der Erstellung des Projektgerüsts. Für Spring Boot-Projekte bietet es sich an hier den Spring Initializr zu verwenden. Basierend auf einigen grundlegenden Angaben, wie Sprache, Spring Boot Version, Project Metadata, Java Version und ausgewählten Dependencies wird ein lauffähiges Projekt angelegt, das als Ausgangspunkt für die Implementierung dient.

Bereits an dieser Stelle lohnt es sich, die voraussichtlich benötigten Dependencies mitzudenken. Zwar können weitere Bibliotheken jederzeit ergänzt werden, eine erste sinnvolle Auswahl erleichtert jedoch die spätere Projektstruktur und reduziert nachträgliche Anpassungen.

Für mein Referenzprojekt und die ersten Schritte würde ich folgende Dependencies auswählen:

  • Spring Boot DevTools (Developer Tools)
  • Spring Security (Security)
  • Spring Web (Web)
  • Spring Data JPA (SQL)
  • Flyway Migration (SQL)
  • H2 Database (SQL)
  • Validation (I/O)

Der Spring Initializr liefert neben den entsprechenden spring-boot-starter-*-Dependencies auch die entsprechenden spring-boot-starter-*-test Dependencies (ab Spring Boot 4), so dass die für das Testen notwendigen Bibliotheken auch vorhanden sind.

CleanUp und Basiskonfiguration

Das erstellte Projekt ist lauffähig und mvn test läuft erfolgreich durch.

Zunächst würde ich einen kleinen clean-up machen und die Testklasse HeatConvectionAppApplicationTests löschen. Diese Testklasse startet mittels @SpringBootTest den vollständigen Spring Application Context. Für die ersten Implementierungsschritte wird ein vollständiger Integrationstest jedoch meist noch nicht benötigt und kann zunächst entfernt werden.

pom.xml

Die pom.xml würde ich ebenfalls etwas aufräumen und zunächst die leeren Tags wie:

<url/>
<licenses>
  <license/>
</licenses>
<developers>
  <developer/>
</developers>
<scm>
  <connection/>
  <developerConnection/>
  <tag/>
  <url/>
</scm>

löschen. Eine Projekt-<description> ,<project.build.sourceEncoding> und <java.version> würde ich noch ausfüllen bzw. hinzufügen:

<description>Reference application for technical calculations using Spring Boot</description>

<properties>
  ...
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  <java.version>21</java.version>
</properties>

application*.yaml Profile

Teil der Basiskonfiguration ist die Erweiterung der application*.yaml. Hier würde ich von Anfang an auf ein übergeordnetes Default application.yml und eine Development spezifische application-dev.yml setzen. Zu einem späteren Zeitpunkt kann die Projektkonfiguration noch um eine application-test.yml und application-prod.yml ergänzt werden. Ich bevorzuge hier mehrere application*.yml Dateien, alternativ kann man auch Multi-Document-YAML-Files anlegen. Hier trennt man Profile mittels ---:

---
spring:
  config:
    activate:
      on-profile: dev
...
---

Welche Variante verwendet wird, hängt letztlich von Projektgröße und persönlicher Präferenz ab.

spring:
  application:
    name: "heat-convection-app"
    version: "0.0.1-SNAPSHOT"

  jpa:
    open-in-view: false
    show-sql: false
    hibernate:
      ddl-auto: none
    properties:
      hibernate:
        "[format.sql]": true

  flyway:
    enabled: true
    locations: classpath:db/migration

logging:
  pattern:
    console: "%d{yyyy-MM-dd HH:mm:ss} %-5level [%logger{36}] - %msg%n"

Neben den Anwendungsinformationen gehören dazu jpa und flyway-Konfiguration und ein angepasstes Logging-Pattern.

In der application-dev.yml geht es konkret darum die embedded H2-Datenbank anzubinden:

spring:
  devtools:
    add-properties: true

  datasource:
    url: jdbc:h2:file:./instance/heat-convection-db
    driver-class-name: org.h2.Driver
    username: sa
    password:

  jpa:
    hibernate:
      ddl-auto: validate

logging:
  level:
    root: INFO
    "[de.meshingbytes.heat_convection_app]": DEBUG
    "[org.hibernate.SQL]": DEBUG
    "[org.springframework.web]": DEBUG
    "[org.hibernate.orm.jdbc.bind]": TRACE

jpa.hibernate.ddl-auto=validate überschreibt den Wert aus der Default-Konfiguration. Dadurch wird beim Start geprüft, ob das Datenbankschema mit den definierten JPA-Entities kompatibel ist.

Während der Entwicklung nutze ich die Spring Boot DevTools, da Änderungen am Code automatisch erkannt werden und die Anwendung neu gestartet wird.

Zusätzlich aktiviere ich ausführlichere Log-Ausgaben für eigene Komponenten, Spring MVC sowie erzeugte SQL-Statements.

Um die Anwendung mit dem dev-Profil zu starten, kann entweder eine Bash-Variable gesetzt werden und anschließend gestartet werden:

export SPRING_PROFILES_ACTIVE=dev
mvn spring-boot:run

oder als Startargument übergeben werden:

mvn spring-boot:run -Dspring-boot.run.profiles=dev

alternativ unter Linux:

SPRING_PROFILES_ACTIVE=dev mvn spring-boot:run

Anlegen einer README.md-Vorlage

Der Spring Intializr erzeugt keine README.md. Ich empfehle jedoch, diese direkt zu Beginn anzulegen und zunächst unvollständige Abschnitte mit Platzhaltern zu versehen:

> TODO: add configuration variables
i

Dadurch bleibt die Dokumentation während der Entwicklung sichtbar und kann schrittweise ergänzt werden.

# Heat-Convection App
## Overview
## Features
## Architecture
## Technology Stack
## Configuration
## Installation
## Database & Migrations
## REST API
## Testing
## Deployment
## Contributing
## Versioning
## License
## Attribution

Erstellen der Paketstruktur

Ebenfalls bewährt hat sich das frühzeitige ANlegen der grundlegenden Paketstruktur, die in den späteren Implementierungsschritten benötigt wird.Ich verwende dafür ein einfaches helper-Skript, das ich im Projekt-Root-Verzeichnis mit den Argumenten groupId und artifactId ausführe.

#!/bin/bash

GROUPID="$1"
ARTIFACTID="$2"

PACKAGE_NAME="${ARTIFACTID//-/_}"
SOURCE_MAIN_PATH="src/main/java/${GROUPID//./\/}/$PACKAGE_NAME"

# main layer structure
mkdir -p -v "$SOURCE_MAIN_PATH"/{config,domain,persistence,api,exception}
# domain layer structure
mkdir -p -v "$SOURCE_MAIN_PATH"/domain/{model,service,mapper}
# persistence layer structure
mkdir -p -v "$SOURCE_MAIN_PATH"/persistence/{entity,repository,mapper}
# api layer structure
mkdir -p -v "$SOURCE_MAIN_PATH"/api/{controller,dto,mapper}

Das Anlegen der paketstruktur dient zunächst lediglich der organisatorischen Vorbereitung. Die konkrete Implementierung der einzelnen Layer erfolgt erst in den späteren Schritten.

Git initialisieren

Spring Initializr legt eine .gitignore an, initialisiert jedoch kein Git-Repository. Dies erfolgt manuell:

git init

Für meine Projekte verwende ich einen develop-Branch als primären Entwicklungszweig und lege diesen direkt nach der Initialisierung an:

git checkout -b develop

Anschließend wird der initiale Projektstand versioniert:

git add .
git commit -m 'chore(init): initialize Spring Boot project'

Fazit und Ausblick

Damit steht die erste Grundkonfiguration der Anwendung sowie ein Projektgerüst, das die weiteren Implementierungsschritte vorbereitet.

├── HELP.md
├── mvnw
├── mvnw.cmd
├── pom.xml
├── README.md
└── src
    ├── main
    │   ├── java
    │   │   └── de
    │   │       └── meshingbytes
    │   │           └── heat_convection_app
    │   │               ├── api
    │   │               │   ├── controller
    │   │               │   ├── dto
    │   │               │   └── mapper
    │   │               ├── config
    │   │               ├── domain
    │   │               │   ├── mapper
    │   │               │   ├── model
    │   │               │   └── service
    │   │               ├── exception
    │   │               ├── HeatConvectionAppApplication.java
    │   │               └── persistence
    │   │                   ├── entity
    │   │                   ├── mapper
    │   │                   └── repository
    │   └── resources
    │       ├── application-dev.yml
    │       ├── application.yml
    │       ├── db
    │       │   └── migration
    │       ├── static
    │       └── templates
    └── test
        └── java
            └── de
                └── meshingbytes
                    └── heat_convection_app

Im Rahmen der ersten Schritte sind einige wichtige Entscheidungen getroffen:

  • grundlegender Software-Stack
  • Paket- und Projektstruktur
  • Konfigurations- und Profilkonzept
  • Datenpersistenz mit JPA und Flyway
  • Versionsverwaltung und Projektdokumentation

Im nächsten Schritt folgt die erste fachliche Implementierung. Dabei werden zunächst die Berechnungslogik für den konvektiven Wärmeübergang umgesetzt, erste Datenbankmigrationen erstellt und einfache REST-Endpunkte zur Nutzung der Berechnung und Abfrage der Stoffwerte implementiert.