Fachlicher Vertrag: unverändert zu erhalten
- Eine Überweisung ist atomar: beide Konten oder keines werden geändert.
- Rollback-Regeln für fachliche und technische Exceptions sind explizit.
- Beträge werden ohne Gleitkommafehler verarbeitet.
manuelles Commit/Rollback wird nachvollziehbar in @Transactional überführt. Die Akte zeigt nicht nur das Ziel, sondern die tatsächlichen Dateien, APIs, Dependencies, Codebelege, Tests, Risiken und Cutover-Schritte.
| Dimension | Legacy | Modern | Migrationskonsequenz |
|---|---|---|---|
| Programmiermodell | imperativ / ohne Framework-Annotationen | Repository, Service, SpringBootApplication, Transactional | Annotationen und Containerfunktionen werden nur dort eingesetzt, wo sie eine konkrete technische Verantwortung übernehmen. |
| Abhängigkeiten | 2 direkte Dependencies | 3 direkte Dependencies | 2 neu, 1 entfernt; Versionen und transitive Auswirkungen im erfolgreichen Online-Build prüfen. |
| Öffentliche API | 3 erkannte Methoden | 4 erkannte Methoden | Methoden werden nach fachlicher Rolle gemappt; reine Bootstrap- und Framework-Methoden sind kein fachlicher Vertrag. |
| Datenmodell | class LegacyTransferService, class TransactionsLegacyApplication | class AccountRepository, class TransactionsModernApplication, class TransferService | Feldnamen, IDs, Null-Semantik, Gleichheit und Serialisierungsform werden separat regressionstestet. |
| Fehlerverhalten | Legacy-Exceptions und Rückgabewerte | explizitere Fach-/Framework-Fehlerabbildung | Fehler dürfen nicht nur technisch übersetzt werden; Status, Ursache, Retrybarkeit und Client-Vertrag müssen erhalten oder versioniert werden. |
| Tests | Bestands- und Golden-Master-Tests | Unit-, Slice-, Contract- und Integrationstests | Der Modern-Pfad wird zuerst gegen denselben fachlichen Vektor geprüft und danach um neue technische Risiken ergänzt. |
| Betrieb | Legacy-Start/Lifecycle | modernes Packaging, Health und externe Konfiguration | Migration mit produktionsnahen Daten, Backups, Restore-Probe und Query-/Lock-Monitoring absichern. |
| Rollback | Legacy-Artefakt bleibt unverändert | Modern-Artefakt getrennt deploybar | Kein Rollback über Datenverlust: Schema, Nachrichten und verschlüsselte Daten müssen rückwärtslesbar oder durch Dual-Read abgesichert sein. |
partial writes and incorrect rollback rules
success, checked/unchecked failure and rollback assertions
Die Zuordnung ist semantisch: Eine Legacy-Klasse kann in mehrere moderne Rollen zerlegt werden.
| Legacy-Rolle / Datei | Modern-Rolle / Datei | Bedeutung |
|---|---|---|
| LegacyTransferService.java | TransferService.java + AccountRepository.java | Manuelle Connection-Transaktion wird deklarativer Service plus Datenadapter. |
| TransactionsLegacyApplication.java | TransactionsModernApplication.java | Bootstrap wechselt auf Spring Transaction Management. |
| Datei | Typ | Annotationen | öffentliche API | Pattern | Zeilen |
|---|---|---|---|---|---|
| LegacyTransferService.java | class LegacyTransferService | — | void transfer(long from, long to, BigDecimal amount, boolean simulateFailure) BigDecimal balance(long id) | Transaction Script | 69 |
| TransactionsLegacyApplication.java | class TransactionsLegacyApplication | — | void main(String[] args) | — | 15 |
| Datei | Typ | Annotationen | öffentliche API | Pattern | Zeilen |
|---|---|---|---|---|---|
| AccountRepository.java | class AccountRepository | Repository | void changeBalance(long id, BigDecimal delta) BigDecimal balance(long id) | — | 23 |
| TransactionsModernApplication.java | class TransactionsModernApplication | SpringBootApplication | void main(String[] args) | — | 11 |
| TransferService.java | class TransferService | Service, Transactional | void transfer(long from, long to, BigDecimal amount, boolean simulateFailure) | Service Layer + declarative Transaction Boundary | 22 |
| Status | Dependency | Version | Scope | Prüfung |
|---|---|---|---|---|
| ENTFERNT | org.junit.jupiter:junit-jupiter | BOM/Parent | test | Legacy-Laufzeit entfällt oder wird durch Plattform/BOM ersetzt. |
| NEU | org.springframework.boot:spring-boot-starter-jdbc | BOM/Parent | compile | Neue Laufzeit-/Testabhängigkeit; transitiv, lizenz- und security-seitig prüfen. |
| NEU | org.springframework.boot:spring-boot-starter-test | BOM/Parent | test | Neue Laufzeit-/Testabhängigkeit; transitiv, lizenz- und security-seitig prüfen. |
| GEÄNDERT | com.h2database:h2 | BOM/Parent → BOM/Parent | compile → runtime | Gemeinsame Dependency; Version und Scope im effektiven POM prüfen. |
Die folgenden Ausschnitte stammen direkt aus den enthaltenen Projekten. Dadurch ist sichtbar, welche Verantwortung tatsächlich verschoben wurde.
Migrationsbedeutung: Manuelle Connection-Transaktion wird deklarativer Service plus Datenadapter.
package at.aydin.lab.transactions.legacy;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
// Design Pattern: Transaction Script
// Zweck: Ein kompletter fachlicher Ablauf wird explizit in einer Methode transaktional ausgeführt.
public final class LegacyTransferService {
private final String url;
public LegacyTransferService(String url) {
this.url = url;
initialize();
}
private Connection connection() throws SQLException {
return DriverManager.getConnection(url, "sa", "");
}
private void initialize() {
try (Connection c = connection(); Statement s = c.createStatement()) {
s.executeUpdate("create table if not exists account(id bigint primary key, balance decimal(15,2))");
s.executeUpdate("merge into account key(id) values (1,1000.00),(2,200.00)");
} catch (SQLException e) {
throw new IllegalStateException(e);
}
}
public void transfer(long from, long to, BigDecimal amount, boolean simulateFailure) {
try (Connection c = connection()) {
c.setAutoCommit(false);
try {
update(c, from, amount.negate());
if (simulateFailure) throw new IllegalStateException("Simulierter Fehler");
update(c, to, amount);
c.commit();
} catch (Exception e) {
c.rollback();
throw new IllegalStateException("Überweisung zurückgerollt", e);
}
} catch (SQLException e) {
throw new IllegalStateException(e);
}
}
private void update(Connection c, long id, BigDecimal delta) throws SQLException {
try (PreparedStatement ps = c.prepareStatement("update account set balance=balance+? where id=?")) {
ps.setBigDecimal(1, delta);
ps.setLong(2, id);
if (ps.executeUpdate() != 1) throw new SQLException("Konto fehlt: " + id);
}
}
public BigDecimal balance(long id) {
try (Connection c = connection(); PreparedStatement ps = c.prepareStatement("select balance from account where id=?")) {
ps.setLong(1, id);
try (ResultSet rs = ps.executeQuery()) {
if (!rs.next()) throw new IllegalArgumentException();
return rs.getBigDecimal(1);
}
} catch (SQLException e) {
throw new IllegalStateException(e);
}
}
}
projects/05-transactions/modern/src/main/java/at/aydin/lab/transactions/modern/TransferService.java
package at.aydin.lab.transactions.modern;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
@Service
// Design Pattern: Service Layer + declarative Transaction Boundary
// Zweck: Rollback-Regeln werden vom Framework statt durch manuelles JDBC gesteuert.
public class TransferService {
private final AccountRepository repository;
public TransferService(AccountRepository repository) {
this.repository = repository;
}
@Transactional
public void transfer(long from, long to, BigDecimal amount, boolean simulateFailure) {
repository.changeBalance(from, amount.negate());
if (simulateFailure) throw new IllegalStateException("Simulierter Fehler");
repository.changeBalance(to, amount);
}
}
package at.aydin.lab.transactions.modern;
import org.springframework.jdbc.core.simple.JdbcClient;
import org.springframework.stereotype.Repository;
import java.math.BigDecimal;
@Repository
public class AccountRepository {
private final JdbcClient jdbc;
public AccountRepository(JdbcClient jdbc) {
this.jdbc = jdbc;
}
public void changeBalance(long id, BigDecimal delta) {
int count = jdbc.sql("update account set balance=balance+:delta where id=:id").param("delta", delta).param("id", id)
.update();
if (count != 1) throw new IllegalArgumentException("Konto fehlt: " + id);
}
public BigDecimal balance(long id) {
return jdbc.sql("select balance from account where id=:id").param("id", id).query(BigDecimal.class).single();
}
}
Migrationsbedeutung: Bootstrap wechselt auf Spring Transaction Management.
package at.aydin.lab.transactions.legacy;
import java.math.BigDecimal;
public final class TransactionsLegacyApplication {
private TransactionsLegacyApplication() {
}
public static void main(String[] args) {
LegacyTransferService service = new LegacyTransferService("jdbc:h2:mem:txlegacy;DB_CLOSE_DELAY=-1");
service.transfer(1, 2, new BigDecimal("50.00"), false);
System.out.println("Konto 1: " + service.balance(1));
System.out.println("Konto 2: " + service.balance(2));
}
}
package at.aydin.lab.transactions.modern;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class TransactionsModernApplication {
public static void main(String[] args) {
SpringApplication.run(TransactionsModernApplication.class, args);
}
}
--- TransactionsLegacyApplication.java
+++ TransactionsModernApplication.java
@@ -1,15 +1,11 @@
-package at.aydin.lab.transactions.legacy;
+package at.aydin.lab.transactions.modern;
-import java.math.BigDecimal;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
-public final class TransactionsLegacyApplication {
- private TransactionsLegacyApplication() {
- }
-
+@SpringBootApplication
+public class TransactionsModernApplication {
public static void main(String[] args) {
- LegacyTransferService service = new LegacyTransferService("jdbc:h2:mem:txlegacy;DB_CLOSE_DELAY=-1");
- service.transfer(1, 2, new BigDecimal("50.00"), false);
- System.out.println("Konto 1: " + service.balance(1));
- System.out.println("Konto 2: " + service.balance(2));
+ SpringApplication.run(TransactionsModernApplication.class, args);
}
}
| ID | Ebene | Prüfung | erforderlicher Nachweis |
|---|---|---|---|
| 05-TRANSACTIONS-A01 | Integration/Contract | Saldo-Summe bleibt vor und nach Transfer gleich. | Automatisierter Test und CI-Protokoll |
| 05-TRANSACTIONS-A02 | Integration/Contract | Fehler nach erster Buchung führt zum vollständigen Rollback. | Automatisierter Test und CI-Protokoll |
| 05-TRANSACTIONS-A03 | Integration/Contract | Unzureichende Deckung erzeugt definierte Fachfehler. | Automatisierter Test und CI-Protokoll |
| 05-TRANSACTIONS-A04 | Integration/Contract | Paralleltests zeigen keine Lost Updates. | Automatisierter Test und CI-Protokoll |
| 05-TRANSACTIONS-A05 | Integration/Contract | Transaktions-Timeout und Isolation sind dokumentiert. | Automatisierter Test und CI-Protokoll |
| 05-TRANSACTIONS-T01 | Unit | Fachlogik ohne Container oder externen Dienst testen. | Unit-Test |
| 05-TRANSACTIONS-T02 | Regression | Legacy- und Modern-Ergebnis für denselben Golden-Master-Vektor vergleichen. | Vergleichsreport |
| 05-TRANSACTIONS-T03 | Negative | Fehlerhafte, leere und grenzwertige Eingaben prüfen. | Negativtest |
| 05-TRANSACTIONS-T04 | Operations | Start, Health, Shutdown und Konfigurationsfehler prüfen. | Deployment-/Startprotokoll |
| 05-TRANSACTIONS-T05 | Database | Rollback, Constraints, Locking und Schema-Kompatibilität mit produktionsnahen Daten prüfen. | DB-Integrationstest |
| 05-TRANSACTIONS-F01 | Fokus | success, checked/unchecked failure and rollback assertions | Modulspezifischer Testreport |
| Risiko | Auswirkung | Gegenmaßnahme | Gate |
|---|---|---|---|
| partial writes and incorrect rollback rules | NIEDRIG | success, checked/unchecked failure and rollback assertions | vor Cutover |
| Neue Framework-/Library-Laufzeitabhängigkeiten | MITTEL | SBOM, Lizenz-, CVE- und transitive Dependency-Prüfung im Online-Build | vor Release |
| Legacy-Verhalten war implizit an entfernte Library gekoppelt | MITTEL | Contract-/Golden-Master-Tests und gezielte Fehlerpfade | vor Abschaltung |
| Seite | Ressource | Zeilen |
|---|---|---|
| Modern | modern/src/main/resources/application.properties | 2 |
| Modern | modern/src/main/resources/schema.sql | 2 |
TransferService kann auf den LegacyTransferService delegieren; niemals beide Implementierungen gleichzeitig schreibend ausführen.