From 97e5255d7fe361629ff8237a11f2cb1d1312293c Mon Sep 17 00:00:00 2001 From: Marcel Date: Tue, 17 Mar 2026 08:28:30 +0100 Subject: [PATCH] refactor: move createPerson logic into PersonService MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Controller was directly calling personRepository.save() for person creation. Extracted into PersonService.createPerson() to enforce Controller → Service → Repository layering. Also documented the layering rules in CLAUDE.md. Co-Authored-By: Claude Sonnet 4.6 --- CLAUDE.md | 14 ++++++++++++++ .../controller/PersonController.java | 7 +------ .../familienarchiv/service/PersonService.java | 10 ++++++++++ 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index d85179ca..ededd67e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -89,6 +89,20 @@ Database migrations live in `src/main/resources/db/migration/` (Flyway). Configu Authentication: form login → backend sets session → `auth_token` cookie → hooks.server.ts injects into all backend requests. +### Backend Layering Rules + +Strict layering must be respected at all times: + +``` +Controller → Service → Repository → DB +``` + +- **Controllers** must never inject or call repositories directly. All business logic goes through a service. +- **Services** must never reach into another domain's repository directly. If Service A needs data owned by domain B, it calls Service B — not Repository B. + - ✅ `DocumentService` → `PersonService.findById()` → `PersonRepository` + - ❌ `DocumentService` → `PersonRepository` (bypasses the service layer) +- This keeps domain boundaries clear and business logic testable in isolation. + ### Key Design Patterns - **Search**: `DocumentSpecifications` (Spring Data JPA Specification pattern) enables composable, dynamic query building for the document search endpoint diff --git a/backend/src/main/java/org/raddatz/familienarchiv/controller/PersonController.java b/backend/src/main/java/org/raddatz/familienarchiv/controller/PersonController.java index e7da0a5a..260b0110 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/controller/PersonController.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/controller/PersonController.java @@ -51,12 +51,7 @@ public class PersonController { if (firstName == null || firstName.isBlank() || lastName == null || lastName.isBlank()) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Vor- und Nachname sind Pflichtfelder"); } - Person person = Person.builder() - .firstName(firstName.trim()) - .lastName(lastName.trim()) - .alias(body.get("alias")) - .build(); - return ResponseEntity.ok(personRepository.save(person)); + return ResponseEntity.ok(personService.createPerson(firstName.trim(), lastName.trim(), body.get("alias"))); } @PutMapping("/{id}") diff --git a/backend/src/main/java/org/raddatz/familienarchiv/service/PersonService.java b/backend/src/main/java/org/raddatz/familienarchiv/service/PersonService.java index 670e3385..84f3b072 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/service/PersonService.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/service/PersonService.java @@ -16,6 +16,16 @@ public class PersonService { private final PersonRepository personRepository; + @Transactional + public Person createPerson(String firstName, String lastName, String alias) { + Person person = Person.builder() + .firstName(firstName) + .lastName(lastName) + .alias(alias == null || alias.isBlank() ? null : alias.trim()) + .build(); + return personRepository.save(person); + } + @Transactional public Person updatePerson(UUID id, String firstName, String lastName, String alias) { Person person = personRepository.findById(id)