refactor(geschichte): route all reads through GeschichteQueryService

JourneyItemService no longer injects GeschichteRepository directly.
GeschichteQueryService gains findById() so JourneyItemService.append()
can load the Geschichte entity via the service layer, satisfying the
cross-domain layering rule.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-06-08 20:04:21 +02:00
parent 7ba6342a84
commit 4a0fed617a
3 changed files with 19 additions and 19 deletions

View File

@@ -3,13 +3,15 @@ package org.raddatz.familienarchiv.geschichte;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.Optional;
import java.util.UUID;
/**
* Thin read-only service owning {@link GeschichteRepository}.
* Exists so that {@code JourneyItemService} can check Geschichte existence
* without holding a direct reference to the Geschichte repository
* (cross-domain repository access is not allowed per layering rules).
* and load Geschichte instances without holding a direct reference to the
* Geschichte repository (cross-domain repository access is not allowed per
* layering rules).
*/
@Service
@RequiredArgsConstructor
@@ -20,4 +22,8 @@ public class GeschichteQueryService {
public boolean existsById(UUID id) {
return geschichteRepository.existsById(id);
}
public Optional<Geschichte> findById(UUID id) {
return geschichteRepository.findById(id);
}
}

View File

@@ -11,7 +11,6 @@ import org.raddatz.familienarchiv.exception.DomainException;
import org.raddatz.familienarchiv.exception.ErrorCode;
import org.raddatz.familienarchiv.geschichte.Geschichte;
import org.raddatz.familienarchiv.geschichte.GeschichteQueryService;
import org.raddatz.familienarchiv.geschichte.GeschichteRepository;
import org.raddatz.familienarchiv.geschichte.GeschichteType;
import org.raddatz.familienarchiv.geschichte.DocumentSummary;
import org.raddatz.familienarchiv.person.Person;
@@ -34,7 +33,6 @@ public class JourneyItemService {
static final int MAX_NOTE_LENGTH = 5000;
private final JourneyItemRepository journeyItemRepository;
private final GeschichteRepository geschichteRepository;
private final GeschichteQueryService geschichteQueryService;
private final DocumentService documentService;
private final AuditService auditService;
@@ -42,7 +40,7 @@ public class JourneyItemService {
@Transactional
public JourneyItemView append(UUID geschichteId, JourneyItemCreateDTO dto) {
Geschichte g = geschichteRepository.findById(geschichteId)
Geschichte g = geschichteQueryService.findById(geschichteId)
.orElseThrow(() -> DomainException.notFound(ErrorCode.GESCHICHTE_NOT_FOUND,
"Journey not found: " + geschichteId));

View File

@@ -16,7 +16,6 @@ import org.raddatz.familienarchiv.exception.DomainException;
import org.raddatz.familienarchiv.exception.ErrorCode;
import org.raddatz.familienarchiv.geschichte.Geschichte;
import org.raddatz.familienarchiv.geschichte.GeschichteQueryService;
import org.raddatz.familienarchiv.geschichte.GeschichteRepository;
import org.raddatz.familienarchiv.geschichte.GeschichteStatus;
import org.raddatz.familienarchiv.geschichte.GeschichteType;
import org.raddatz.familienarchiv.person.Person;
@@ -26,7 +25,6 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import java.time.LocalDate;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
@@ -36,7 +34,6 @@ import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.lenient;
@@ -48,7 +45,6 @@ import static org.mockito.Mockito.when;
class JourneyItemServiceTest {
@Mock JourneyItemRepository journeyItemRepository;
@Mock GeschichteRepository geschichteRepository;
@Mock GeschichteQueryService geschichteQueryService;
@Mock DocumentService documentService;
@Mock AuditService auditService;
@@ -148,7 +144,7 @@ class JourneyItemServiceTest {
@Test
void append_to_empty_journey_starts_at_10() {
Geschichte journey = journey(geschichteId);
when(geschichteRepository.findById(geschichteId)).thenReturn(Optional.of(journey));
when(geschichteQueryService.findById(geschichteId)).thenReturn(Optional.of(journey));
when(journeyItemRepository.countByGeschichteId(geschichteId)).thenReturn(0L);
when(journeyItemRepository.findMaxPositionByGeschichteId(geschichteId)).thenReturn(Optional.empty());
JourneyItem saved = savedItem(itemId, journey, 10, null, "Note");
@@ -165,7 +161,7 @@ class JourneyItemServiceTest {
@Test
void append_after_reorder_continues_from_max_position() {
Geschichte journey = journey(geschichteId);
when(geschichteRepository.findById(geschichteId)).thenReturn(Optional.of(journey));
when(geschichteQueryService.findById(geschichteId)).thenReturn(Optional.of(journey));
when(journeyItemRepository.countByGeschichteId(geschichteId)).thenReturn(2L);
when(journeyItemRepository.findMaxPositionByGeschichteId(geschichteId)).thenReturn(Optional.of(40));
JourneyItem saved = savedItem(itemId, journey, 50, null, "Note");
@@ -182,7 +178,7 @@ class JourneyItemServiceTest {
@Test
void append_returns400_when_neither_documentId_nor_note() {
Geschichte journey = journey(geschichteId);
when(geschichteRepository.findById(geschichteId)).thenReturn(Optional.of(journey));
when(geschichteQueryService.findById(geschichteId)).thenReturn(Optional.of(journey));
when(journeyItemRepository.countByGeschichteId(geschichteId)).thenReturn(0L);
JourneyItemCreateDTO dto = new JourneyItemCreateDTO();
@@ -195,7 +191,7 @@ class JourneyItemServiceTest {
@Test
void append_returns400_when_note_trims_to_empty_and_no_document() {
Geschichte journey = journey(geschichteId);
when(geschichteRepository.findById(geschichteId)).thenReturn(Optional.of(journey));
when(geschichteQueryService.findById(geschichteId)).thenReturn(Optional.of(journey));
when(journeyItemRepository.countByGeschichteId(geschichteId)).thenReturn(0L);
JourneyItemCreateDTO dto = new JourneyItemCreateDTO();
@@ -208,7 +204,7 @@ class JourneyItemServiceTest {
@Test
void append_returns400_when_note_exceeds_5000_chars() {
Geschichte journey = journey(geschichteId);
when(geschichteRepository.findById(geschichteId)).thenReturn(Optional.of(journey));
when(geschichteQueryService.findById(geschichteId)).thenReturn(Optional.of(journey));
when(journeyItemRepository.countByGeschichteId(geschichteId)).thenReturn(0L);
JourneyItemCreateDTO dto = new JourneyItemCreateDTO();
@@ -228,7 +224,7 @@ class JourneyItemServiceTest {
.type(GeschichteType.STORY)
.status(GeschichteStatus.DRAFT)
.build();
when(geschichteRepository.findById(geschichteId)).thenReturn(Optional.of(story));
when(geschichteQueryService.findById(geschichteId)).thenReturn(Optional.of(story));
JourneyItemCreateDTO dto = new JourneyItemCreateDTO();
dto.setNote("Note");
@@ -242,7 +238,7 @@ class JourneyItemServiceTest {
@Test
void append_returns404_when_documentId_does_not_exist() {
Geschichte journey = journey(geschichteId);
when(geschichteRepository.findById(geschichteId)).thenReturn(Optional.of(journey));
when(geschichteQueryService.findById(geschichteId)).thenReturn(Optional.of(journey));
when(journeyItemRepository.countByGeschichteId(geschichteId)).thenReturn(0L);
when(documentService.getSummaryById(docId))
.thenThrow(DomainException.notFound(ErrorCode.DOCUMENT_NOT_FOUND, "not found"));
@@ -259,7 +255,7 @@ class JourneyItemServiceTest {
@Test
void append_returns409_when_100_items_exist() {
Geschichte journey = journey(geschichteId);
when(geschichteRepository.findById(geschichteId)).thenReturn(Optional.of(journey));
when(geschichteQueryService.findById(geschichteId)).thenReturn(Optional.of(journey));
when(journeyItemRepository.countByGeschichteId(geschichteId)).thenReturn(100L);
JourneyItemCreateDTO dto = new JourneyItemCreateDTO();
@@ -275,7 +271,7 @@ class JourneyItemServiceTest {
void cap_is_COUNT_based_not_MAX_position_based() {
// 99 rows with MAX(position)=2000 should still accept the 100th append
Geschichte journey = journey(geschichteId);
when(geschichteRepository.findById(geschichteId)).thenReturn(Optional.of(journey));
when(geschichteQueryService.findById(geschichteId)).thenReturn(Optional.of(journey));
when(journeyItemRepository.countByGeschichteId(geschichteId)).thenReturn(99L);
when(journeyItemRepository.findMaxPositionByGeschichteId(geschichteId)).thenReturn(Optional.of(2000));
JourneyItem saved = savedItem(itemId, journey, 2010, null, "Note");
@@ -290,7 +286,7 @@ class JourneyItemServiceTest {
@Test
void append_audits_JOURNEY_ITEM_ADDED() {
Geschichte journey = journey(geschichteId);
when(geschichteRepository.findById(geschichteId)).thenReturn(Optional.of(journey));
when(geschichteQueryService.findById(geschichteId)).thenReturn(Optional.of(journey));
when(journeyItemRepository.countByGeschichteId(geschichteId)).thenReturn(0L);
when(journeyItemRepository.findMaxPositionByGeschichteId(geschichteId)).thenReturn(Optional.empty());
JourneyItem saved = savedItem(itemId, journey, 10, null, "Note");