No description
Find a file
2026-10-17 10:30:05 +02:00
src step 4: rewrite borrow without the legacy 2026-10-17 10:30:05 +02:00
.gitignore legacy library loans app 2026-10-16 14:42:03 +02:00
pom.xml legacy library loans app 2026-10-16 14:42:03 +02:00
README.md add http test file 2026-10-16 14:42:03 +02:00
requests.http add http test file 2026-10-16 14:42:03 +02:00
settings.xml legacy library loans app 2026-10-16 14:42:03 +02:00

strangler-by-abstraction

Replace legacy code one method at a time, without a big bang.

A small Spring Boot app lending books to library members, whose whole loan logic lives in one legacy class. It is the starting point of a migration that mixes two patterns:

  • Branch by abstraction: put an interface between the legacy code and everything that calls it, first implemented by simply delegating to the legacy code;
  • Strangler fig: then replace the implementation behind that interface method by method, until the legacy code has nothing left to do and can be deleted.

The legacy code

LoanManager does everything about loans. It works, and it looks its age: SQL strings inline, a checked LoanException told apart by string codes, money as double, LocalDate.now() wherever it needs today, magic numbers, and a Loan that is both a table row and the API's JSON.

Method Rules
borrow(memberId, bookId) The member exists and their membership has not ended · the book exists and is not lent · no overdue loan · they owe less than 5 € · fewer than 3 loans at once (5 for PREMIUM members). The loan lasts 21 days.
renew(loanId) Not returned · never renewed before · not overdue. Adds 14 days.
returnBook(loanId) Not returned yet. 0.20 € a day late, 10 € at most, added to what the member owes.
getCurrentLoans(memberId) The books not returned yet, soonest due first.
getOverdueLoans() Every loan past its due date.
isAvailable(bookId) No loan of the book is open. Also used by borrow itself.

Its callers

The legacy class is used from four places — the ones an interface will have to replace it in:

Caller Uses
LoanController borrow, renew, returnBook
MemberController getCurrentLoans
BookController isAvailable
OverdueReminderJob getOverdueLoans

ApiExceptionHandler depends on LoanException too.

The safety net

LoanApiTest and ReadSideTest pin every rule above, edge cases included (a loan due today is not late, owing 4.99 € is fine, the membership is checked before the book…). They only go through the HTTP API, the reminder job and the database — never through LoanManager — so they must stay green, untouched, at every step of the migration.

Data

An in-memory H2 database, created at startup by schema.sql and filled by data.sql, with dates relative to today:

Member Story
1 · Alice STANDARD, can borrow; has Refactoring, due in 11 days
2 · Bruno PREMIUM, 9 days late with Working Effectively with Legacy Code
3 · Chloé Membership ended 10 days ago
4 · David Owes 6.40 €

Requirements

  • Java 25+
  • Maven 3.9+

Maven repositories. The commands below pass -s settings.xml, a checked-in settings file that resolves everything from Maven Central. Drop it if your default Maven setup already reaches Central.

Quick start

mvn -s settings.xml clean install
java -jar target/strangler-by-abstraction-1.0.0-SNAPSHOT.jar
curl http://localhost:8080/books
curl http://localhost:8080/members/1/loans

curl -X POST http://localhost:8080/loans -H 'Content-Type: application/json' \
     -d '{"memberId": 1, "bookId": 4}'                                       # → 201
curl -X POST http://localhost:8080/loans -H 'Content-Type: application/json' \
     -d '{"memberId": 2, "bookId": 5}'                                       # → 409 MEMBER_HAS_OVERDUE_LOANS

curl -X POST http://localhost:8080/loans/2/renew
curl -X POST http://localhost:8080/loans/1/return                            # → fee 1.80

requests.http walks through every rule, with the expected answer of each request (IntelliJ HTTP client or VS Code REST Client).

The reminder job runs every morning at 8 and only logs. To see it right away: java -jar target/*.jar --reminder.cron="*/10 * * * * *".

Technologies

  • Spring Boot 4.1 · Java 25 · Spring JDBC · H2

License

Apache 2.0