Extract CountAttendeesByEventUseCase to decouple controller from repository
The EventController was directly accessing RsvpRepository (an outbound port) to count attendees, bypassing the application layer. Introduce a dedicated inbound port and implement it in RsvpService. Remove the now-unused Clock dependency from RsvpService. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -12,10 +12,10 @@ import de.fete.domain.model.CreateEventCommand;
|
|||||||
import de.fete.domain.model.Event;
|
import de.fete.domain.model.Event;
|
||||||
import de.fete.domain.model.EventToken;
|
import de.fete.domain.model.EventToken;
|
||||||
import de.fete.domain.model.Rsvp;
|
import de.fete.domain.model.Rsvp;
|
||||||
|
import de.fete.domain.port.in.CountAttendeesByEventUseCase;
|
||||||
import de.fete.domain.port.in.CreateEventUseCase;
|
import de.fete.domain.port.in.CreateEventUseCase;
|
||||||
import de.fete.domain.port.in.CreateRsvpUseCase;
|
import de.fete.domain.port.in.CreateRsvpUseCase;
|
||||||
import de.fete.domain.port.in.GetEventUseCase;
|
import de.fete.domain.port.in.GetEventUseCase;
|
||||||
import de.fete.domain.port.out.RsvpRepository;
|
|
||||||
import java.time.Clock;
|
import java.time.Clock;
|
||||||
import java.time.DateTimeException;
|
import java.time.DateTimeException;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
@@ -32,20 +32,20 @@ public class EventController implements EventsApi {
|
|||||||
private final CreateEventUseCase createEventUseCase;
|
private final CreateEventUseCase createEventUseCase;
|
||||||
private final GetEventUseCase getEventUseCase;
|
private final GetEventUseCase getEventUseCase;
|
||||||
private final CreateRsvpUseCase createRsvpUseCase;
|
private final CreateRsvpUseCase createRsvpUseCase;
|
||||||
private final RsvpRepository rsvpRepository;
|
private final CountAttendeesByEventUseCase countAttendeesByEventUseCase;
|
||||||
private final Clock clock;
|
private final Clock clock;
|
||||||
|
|
||||||
/** Creates a new controller with the given use cases, repository, and clock. */
|
/** Creates a new controller with the given use cases and clock. */
|
||||||
public EventController(
|
public EventController(
|
||||||
CreateEventUseCase createEventUseCase,
|
CreateEventUseCase createEventUseCase,
|
||||||
GetEventUseCase getEventUseCase,
|
GetEventUseCase getEventUseCase,
|
||||||
CreateRsvpUseCase createRsvpUseCase,
|
CreateRsvpUseCase createRsvpUseCase,
|
||||||
RsvpRepository rsvpRepository,
|
CountAttendeesByEventUseCase countAttendeesByEventUseCase,
|
||||||
Clock clock) {
|
Clock clock) {
|
||||||
this.createEventUseCase = createEventUseCase;
|
this.createEventUseCase = createEventUseCase;
|
||||||
this.getEventUseCase = getEventUseCase;
|
this.getEventUseCase = getEventUseCase;
|
||||||
this.createRsvpUseCase = createRsvpUseCase;
|
this.createRsvpUseCase = createRsvpUseCase;
|
||||||
this.rsvpRepository = rsvpRepository;
|
this.countAttendeesByEventUseCase = countAttendeesByEventUseCase;
|
||||||
this.clock = clock;
|
this.clock = clock;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,7 +90,7 @@ public class EventController implements EventsApi {
|
|||||||
response.setTimezone(event.getTimezone().getId());
|
response.setTimezone(event.getTimezone().getId());
|
||||||
response.setLocation(event.getLocation());
|
response.setLocation(event.getLocation());
|
||||||
response.setAttendeeCount(
|
response.setAttendeeCount(
|
||||||
(int) rsvpRepository.countByEventId(event.getId()));
|
(int) countAttendeesByEventUseCase.countByEvent(eventToken));
|
||||||
response.setExpired(
|
response.setExpired(
|
||||||
event.getExpiryDate().isBefore(LocalDate.now(clock)));
|
event.getExpiryDate().isBefore(LocalDate.now(clock)));
|
||||||
|
|
||||||
|
|||||||
@@ -4,28 +4,25 @@ import de.fete.domain.model.Event;
|
|||||||
import de.fete.domain.model.EventToken;
|
import de.fete.domain.model.EventToken;
|
||||||
import de.fete.domain.model.Rsvp;
|
import de.fete.domain.model.Rsvp;
|
||||||
import de.fete.domain.model.RsvpToken;
|
import de.fete.domain.model.RsvpToken;
|
||||||
|
import de.fete.domain.port.in.CountAttendeesByEventUseCase;
|
||||||
import de.fete.domain.port.in.CreateRsvpUseCase;
|
import de.fete.domain.port.in.CreateRsvpUseCase;
|
||||||
import de.fete.domain.port.out.EventRepository;
|
import de.fete.domain.port.out.EventRepository;
|
||||||
import de.fete.domain.port.out.RsvpRepository;
|
import de.fete.domain.port.out.RsvpRepository;
|
||||||
import java.time.Clock;
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
/** Application service implementing RSVP creation. */
|
/** Application service implementing RSVP creation. */
|
||||||
@Service
|
@Service
|
||||||
public class RsvpService implements CreateRsvpUseCase {
|
public class RsvpService implements CreateRsvpUseCase, CountAttendeesByEventUseCase {
|
||||||
|
|
||||||
private final EventRepository eventRepository;
|
private final EventRepository eventRepository;
|
||||||
private final RsvpRepository rsvpRepository;
|
private final RsvpRepository rsvpRepository;
|
||||||
private final Clock clock;
|
|
||||||
|
|
||||||
/** Creates a new RsvpService. */
|
/** Creates a new RsvpService. */
|
||||||
public RsvpService(
|
public RsvpService(
|
||||||
EventRepository eventRepository,
|
EventRepository eventRepository,
|
||||||
RsvpRepository rsvpRepository,
|
RsvpRepository rsvpRepository) {
|
||||||
Clock clock) {
|
|
||||||
this.eventRepository = eventRepository;
|
this.eventRepository = eventRepository;
|
||||||
this.rsvpRepository = rsvpRepository;
|
this.rsvpRepository = rsvpRepository;
|
||||||
this.clock = clock;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -40,4 +37,11 @@ public class RsvpService implements CreateRsvpUseCase {
|
|||||||
|
|
||||||
return rsvpRepository.save(rsvp);
|
return rsvpRepository.save(rsvp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long countByEvent(EventToken eventToken) {
|
||||||
|
Event event = eventRepository.findByEventToken(eventToken)
|
||||||
|
.orElseThrow(() -> new EventNotFoundException(eventToken.value()));
|
||||||
|
return rsvpRepository.countByEventId(event.getId());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package de.fete.domain.port.in;
|
||||||
|
|
||||||
|
import de.fete.domain.model.EventToken;
|
||||||
|
|
||||||
|
/** Inbound port for counting attendees of an event. */
|
||||||
|
public interface CountAttendeesByEventUseCase {
|
||||||
|
|
||||||
|
/** Counts the number of confirmed attendees for the given event. */
|
||||||
|
long countByEvent(EventToken eventToken);
|
||||||
|
}
|
||||||
@@ -12,8 +12,6 @@ import de.fete.domain.model.OrganizerToken;
|
|||||||
import de.fete.domain.model.Rsvp;
|
import de.fete.domain.model.Rsvp;
|
||||||
import de.fete.domain.port.out.EventRepository;
|
import de.fete.domain.port.out.EventRepository;
|
||||||
import de.fete.domain.port.out.RsvpRepository;
|
import de.fete.domain.port.out.RsvpRepository;
|
||||||
import java.time.Clock;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
@@ -30,9 +28,6 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
|||||||
class RsvpServiceTest {
|
class RsvpServiceTest {
|
||||||
|
|
||||||
private static final ZoneId ZONE = ZoneId.of("Europe/Berlin");
|
private static final ZoneId ZONE = ZoneId.of("Europe/Berlin");
|
||||||
private static final Instant FIXED_INSTANT =
|
|
||||||
LocalDate.of(2026, 3, 5).atStartOfDay(ZONE).toInstant();
|
|
||||||
private static final Clock FIXED_CLOCK = Clock.fixed(FIXED_INSTANT, ZONE);
|
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private EventRepository eventRepository;
|
private EventRepository eventRepository;
|
||||||
@@ -44,7 +39,7 @@ class RsvpServiceTest {
|
|||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setUp() {
|
void setUp() {
|
||||||
rsvpService = new RsvpService(eventRepository, rsvpRepository, FIXED_CLOCK);
|
rsvpService = new RsvpService(eventRepository, rsvpRepository);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -109,7 +104,7 @@ class RsvpServiceTest {
|
|||||||
event.setDateTime(OffsetDateTime.of(2026, 6, 15, 20, 0, 0, 0, ZoneOffset.ofHours(2)));
|
event.setDateTime(OffsetDateTime.of(2026, 6, 15, 20, 0, 0, 0, ZoneOffset.ofHours(2)));
|
||||||
event.setTimezone(ZONE);
|
event.setTimezone(ZONE);
|
||||||
event.setExpiryDate(LocalDate.of(2026, 7, 15));
|
event.setExpiryDate(LocalDate.of(2026, 7, 15));
|
||||||
event.setCreatedAt(OffsetDateTime.now(FIXED_CLOCK));
|
event.setCreatedAt(OffsetDateTime.now());
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user