Implement event domain model and application service

Add Event entity, CreateEventCommand, ports (CreateEventUseCase,
EventRepository), and EventService with Clock injection for
deterministic testing. Expiry date must be in the future.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-05 10:56:28 +01:00
parent eeadaf58c7
commit 830ca55f20
9 changed files with 361 additions and 2 deletions

View File

@@ -0,0 +1,44 @@
package de.fete.application.service;
import de.fete.domain.model.CreateEventCommand;
import de.fete.domain.model.Event;
import de.fete.domain.port.in.CreateEventUseCase;
import de.fete.domain.port.out.EventRepository;
import java.time.Clock;
import java.time.LocalDate;
import java.time.OffsetDateTime;
import java.util.UUID;
import org.springframework.stereotype.Service;
/** Application service implementing event creation. */
@Service
public class EventService implements CreateEventUseCase {
private final EventRepository eventRepository;
private final Clock clock;
/** Creates a new EventService with the given repository and clock. */
public EventService(EventRepository eventRepository, Clock clock) {
this.eventRepository = eventRepository;
this.clock = clock;
}
@Override
public Event createEvent(CreateEventCommand command) {
if (!command.expiryDate().isAfter(LocalDate.now(clock))) {
throw new ExpiryDateInPastException(command.expiryDate());
}
var event = new Event();
event.setEventToken(UUID.randomUUID());
event.setOrganizerToken(UUID.randomUUID());
event.setTitle(command.title());
event.setDescription(command.description());
event.setDateTime(command.dateTime());
event.setLocation(command.location());
event.setExpiryDate(command.expiryDate());
event.setCreatedAt(OffsetDateTime.now(clock));
return eventRepository.save(event);
}
}

View File

@@ -0,0 +1,20 @@
package de.fete.application.service;
import java.time.LocalDate;
/** Thrown when an event's expiry date is not in the future. */
public class ExpiryDateInPastException extends RuntimeException {
private final LocalDate expiryDate;
/** Creates a new exception for the given invalid expiry date. */
public ExpiryDateInPastException(LocalDate expiryDate) {
super("Expiry date must be in the future: " + expiryDate);
this.expiryDate = expiryDate;
}
/** Returns the invalid expiry date. */
public LocalDate getExpiryDate() {
return expiryDate;
}
}

View File

@@ -1,6 +1,8 @@
package de.fete.config;
import java.io.IOException;
import java.time.Clock;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
@@ -14,6 +16,11 @@ import org.springframework.web.servlet.resource.PathResourceResolver;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Bean
Clock clock() {
return Clock.systemDefaultZone();
}
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.addPathPrefix("/api", c -> c.isAnnotationPresent(RestController.class));

View File

@@ -0,0 +1,13 @@
package de.fete.domain.model;
import java.time.LocalDate;
import java.time.OffsetDateTime;
/** Command carrying the data needed to create an event. */
public record CreateEventCommand(
String title,
String description,
OffsetDateTime dateTime,
String location,
LocalDate expiryDate
) {}

View File

@@ -0,0 +1,109 @@
package de.fete.domain.model;
import java.time.LocalDate;
import java.time.OffsetDateTime;
import java.util.UUID;
/** Domain entity representing an event. */
public class Event {
private Long id;
private UUID eventToken;
private UUID organizerToken;
private String title;
private String description;
private OffsetDateTime dateTime;
private String location;
private LocalDate expiryDate;
private OffsetDateTime createdAt;
/** Returns the internal database ID. */
public Long getId() {
return id;
}
/** Sets the internal database ID. */
public void setId(Long id) {
this.id = id;
}
/** Returns the public event token (UUID). */
public UUID getEventToken() {
return eventToken;
}
/** Sets the public event token. */
public void setEventToken(UUID eventToken) {
this.eventToken = eventToken;
}
/** Returns the secret organizer token (UUID). */
public UUID getOrganizerToken() {
return organizerToken;
}
/** Sets the secret organizer token. */
public void setOrganizerToken(UUID organizerToken) {
this.organizerToken = organizerToken;
}
/** Returns the event title. */
public String getTitle() {
return title;
}
/** Sets the event title. */
public void setTitle(String title) {
this.title = title;
}
/** Returns the event description. */
public String getDescription() {
return description;
}
/** Sets the event description. */
public void setDescription(String description) {
this.description = description;
}
/** Returns the event date and time with UTC offset. */
public OffsetDateTime getDateTime() {
return dateTime;
}
/** Sets the event date and time. */
public void setDateTime(OffsetDateTime dateTime) {
this.dateTime = dateTime;
}
/** Returns the event location. */
public String getLocation() {
return location;
}
/** Sets the event location. */
public void setLocation(String location) {
this.location = location;
}
/** Returns the expiry date after which event data is deleted. */
public LocalDate getExpiryDate() {
return expiryDate;
}
/** Sets the expiry date. */
public void setExpiryDate(LocalDate expiryDate) {
this.expiryDate = expiryDate;
}
/** Returns the creation timestamp. */
public OffsetDateTime getCreatedAt() {
return createdAt;
}
/** Sets the creation timestamp. */
public void setCreatedAt(OffsetDateTime createdAt) {
this.createdAt = createdAt;
}
}

View File

@@ -0,0 +1,11 @@
package de.fete.domain.port.in;
import de.fete.domain.model.CreateEventCommand;
import de.fete.domain.model.Event;
/** Inbound port for creating a new event. */
public interface CreateEventUseCase {
/** Creates an event from the given command and returns the persisted event. */
Event createEvent(CreateEventCommand command);
}

View File

@@ -0,0 +1,15 @@
package de.fete.domain.port.out;
import de.fete.domain.model.Event;
import java.util.Optional;
import java.util.UUID;
/** Outbound port for persisting and retrieving events. */
public interface EventRepository {
/** Persists the given event and returns it with generated fields populated. */
Event save(Event event);
/** Finds an event by its public event token. */
Optional<Event> findByEventToken(UUID eventToken);
}