Story 2.2: Event Management Service - Architecture Compliance Refactoring
Table of Contents
- Story 2.2: Event Management Service - Architecture Compliance Refactoring
Story 2.2: Event Management Service - Architecture Compliance Refactoring
Status
Done
Story
As an architect, I want the Event Management Service to comply with all architecture standards from ADR-001, OpenAPI Generator patterns, and shared-kernel integration, so that the service is consistent, maintainable, and follows established patterns across the platform.
Domain Context
Primary Domain
Event Management Domain - Architecture compliance refactoring of existing service (Story 1.15a.1)
Involved Services
- Event Management Service (refactoring) - Java 21 + Spring Boot 3.2
- Shared-Kernel (integration) - Exception hierarchy, DomainEvent, DomainEventPublisher, ErrorResponse
IMPORTANT: This is a REFACTORING STORY. Story 1.15a.1 already implemented the Event Management Service with all CRUD operations, filtering, caching, and 51 passing tests. This story brings that implementation to full architecture compliance.
Cross-Domain Dependencies
- Depends on: Story 1.15a.1 (Events API Consolidation) - DONE
- Depends on: Shared-Kernel (exception hierarchy, domain events, query utilities)
- Depends on: Company Management Service architecture compliance patterns (Story 2.1)
Requirements Context
Related Functional Requirements
- NFR-Architecture: All services must follow shared-kernel patterns and OpenAPI Generator approach
- NFR-Maintainability: Consistent patterns across services for easier maintenance
- NFR-Observability: Domain events with full metadata for distributed tracing
Acceptance Criteria Source
Epic 2 (docs/prd/epic-2-entity-crud-domain-services.md), Story 2.2, Lines 208-237 (Architecture Compliance Requirements)
- Reference: Company Management Service (Story 2.1) ARCHITECTURE-COMPLIANCE-FIXES.md as the pattern to follow
Architecture Context
Architecture Patterns
Current State (Story 1.15a.1 Implementation):
- β Event entity with UUID id, basic CRUD operations
- β EventController with all endpoints (list, get, create, update, delete, publish, workflow)
- β Caffeine caching (15min TTL)
- β GlobalExceptionHandler with custom exceptions
- β FilterParser, SortParser, PaginationUtils integration
- β 51 integration tests passing
- β
OpenAPI spec exists (
docs/api/events-api.openapi.yml)
Architecture Compliance Gaps (to fix in this story):
- β Event entity uses UUID as public ID (should use eventCode pattern like βBATbern56β)
- β Manual DTOs (should use OpenAPI Generator)
- β Custom exceptions donβt fully extend shared-kernel hierarchy
- β No DomainEventPublisher from shared-kernel (likely custom event publishing)
- β Domain events may not extend DomainEvent
with full metadata - β OpenAPI spec defines ErrorResponse/PaginationMetadata locally (should import from shared-kernel)
- β No schema validation test against architecture
[Source: docs/architecture/ADR-001-invitation-based-user-registration.md]
- Database-centric architecture, PostgreSQL as single source of truth
- NO AWS SDK calls for entity CRUD operations
[Source: services/company-user-management-service/OPENAPI-CODEGEN.md]
- OpenAPI Generator with hybrid approach (generate interfaces/DTOs, write controllers)
- Import shared-kernel types via importMappings
[Source: services/company-user-management-service/ARCHITECTURE-COMPLIANCE-FIXES.md]
- Exception hierarchy: extend shared-kernel NotFoundException, ValidationException
- ErrorResponse: use ch.batbern.shared.dto.ErrorResponse with correlationId, path, severity
- Domain events: extend DomainEvent
with eventId, correlationId, causationId, userId, metadata - Event publisher: use ch.batbern.shared.events.DomainEventPublisher (NO custom publishers)
Database Schema Reference
Architecture Schema (Source of Truth):
- Doc: docs/architecture/03-data-architecture.md, Section βEventβ (Lines 367-516)
- Key Requirement: eventCode as public identifier (e.g., βBATbern56β), internal UUID for database
Migration Check (Dev Agent completes in Task 0):
- Existing Migration: [To be checked - does it match architecture?]
- Status: [To be filled: β Matches | β οΈ Needs correction | β Create new]
- Action: [To be filled by dev agent in Task 0]
Acceptance Criteria
Note: These are REFACTORING criteria, NOT new features. Story 1.15a.1 already implemented the functionality.
- OpenAPI Generator Integration - Configure build.gradle with openApiGenerate task
- OpenAPI Spec Compliance - Update events-api.openapi.yml to import shared-kernel ErrorResponse and PaginationMetadata (NOT define locally)
- Meaningful ID Migration - Migrate Event entity from UUID public ID to eventCode pattern (e.g., βBATbern56β)
- Exception Hierarchy Compliance - EventNotFoundException extends shared-kernel NotFoundException, BusinessValidationException extends shared-kernel ValidationException
- ErrorResponse Compliance - GlobalExceptionHandler uses shared-kernel ErrorResponse with correlationId, path, severity, details
- Domain Event Compliance - All domain events extend DomainEvent
with full metadata (eventId, correlationId, causationId, userId, metadata) - DomainEventPublisher Integration - Replace custom event publishing with shared-kernel DomainEventPublisher
- Schema Validation Test - Create SchemaValidationTest to verify Event entity matches architecture exactly
- Database Migration - Update migration to match architecture schema (eventCode, eventNumber)
- Integration Tests Updated - All 51 tests still passing after refactoring
- No Functional Regressions - All Story 1.15a.1 functionality still works (CRUD, filtering, caching, resource expansion, etc.)
Test Specifications (TDD)
Test Scenarios by Acceptance Criteria
IMPORTANT: This is refactoring, so weβre updating EXISTING tests, not writing all new tests from scratch. Story 1.15a.1 has 51 passing tests that must continue to pass.
AC1-2 Tests: OpenAPI Generator & Spec Compliance
- Test 1.1: should_generateAPIsFromSpec_when_openApiGenerateRuns
- Test 1.2: should_importErrorResponseFromSharedKernel_when_specParsed (verify NOT locally defined)
- Test 1.3: should_importPaginationMetadataFromSharedKernel_when_specParsed (verify NOT locally defined)
AC3 Tests: Meaningful ID Migration (eventCode)
- Test 3.1: should_useEventCode_when_apiResponseReturned (NOT UUID)
- Test 3.2: should_generateEventCode_when_eventCreated (from eventNumber)
- Test 3.3: should_getEventByCode_when_codeProvided (e.g., βBATbern56β)
- Test 3.4: should_return404_when_invalidEventCodeProvided
AC4-5 Tests: Exception & ErrorResponse Compliance
- Test 4.1: should_extendNotFoundException_when_eventNotFound
- Test 4.2: should_extendValidationException_when_businessValidationFails
- Test 5.1: should_includeCorrelationId_when_errorResponseReturned
- Test 5.2: should_includePath_when_errorResponseReturned
- Test 5.3: should_includeSeverity_when_errorResponseReturned
AC6-7 Tests: Domain Event & Publisher Compliance
- Test 6.1: should_extendDomainEventUUID_when_eventPublished (DomainEvent
) - Test 6.2: should_includeEventMetadata_when_domainEventCreated (eventId, correlationId, causationId, userId)
- Test 7.1: should_useDomainEventPublisher_when_eventCreated (shared-kernel, NOT custom)
AC8-9 Tests: Schema Validation
- Test 8.1: should_matchArchitecture_when_entityMapped (SchemaValidationTest)
- Test 9.1: should_haveEventCodeColumn_when_migrationApplied
- Test 9.2: should_haveEventNumberColumn_when_migrationApplied
AC10-11 Tests: Regression Testing
- Test 10.1: should_pass51Tests_when_refactoringComplete (all Story 1.15a.1 tests)
- Test 11.1: should_performCRUD_when_endToEndFlow (functional regression)
Test File Locations
Existing Test Files (to update):
services/event-management-service/src/test/java/ch/batbern/events/controller/EventControllerIntegrationTest.java(51 tests)services/event-management-service/src/test/java/ch/batbern/events/service/EventServiceTest.java
New Test Files (to create):
services/event-management-service/src/test/java/ch/batbern/events/validation/SchemaValidationTest.java(schema validation)services/event-management-service/src/test/java/ch/batbern/events/openapi/OpenAPIGeneratorTest.java(verify spec compliance)
Test Data & Mocks
Use existing test data from Story 1.15a.1:
- EventTestDataBuilder
- TestSecurityConfig
- Testcontainers PostgreSQL
Tasks / Subtasks (Refactoring Workflow)
-
Task 0: Assessment & Planning
-
Task 1: OpenAPI Spec Compliance (AC: 2)
- Read
docs/api/events-api.openapi.yml
- Read
-
Task 2: OpenAPI Generator Configuration (AC: 1)
- Update
build.gradle - Run
- Verify generated interfaces in
- Update
-
Task 3: Exception Hierarchy Refactoring (AC: 4, 5)
-
Task 4: Domain Event Refactoring (AC: 6)
- Update all events to extend DomainEvent
- Update all events to extend DomainEvent
-
Task 5: DomainEventPublisher Integration (AC: 7)
-
Task 6: Meaningful ID Migration (AC: 3, 9)
- Database First
- Entity Next
- Repository Next
- Controller/Service Next
- Tests Next
- Database First
-
Task 7: Schema Validation Test (AC: 8)
-
Task 8: Regression Testing (AC: 10, 11)
- Run full test suite:
-
Task 9: Documentation Updates
-
Task 10: Final Verification
Dev Notes - Implementation Guide
Current Implementation (Story 1.15a.1)
Files to Refactor:
services/event-management-service/src/main/java/ch/batbern/events/
βββ domain/
β βββ Event.java # β Uses UUID as public ID β needs eventCode
β βββ Session.java # β
OK (sub-resource)
β βββ Registration.java # β
OK (sub-resource)
βββ repository/
β βββ EventRepository.java # β οΈ Add findByEventCode()
β βββ SessionRepository.java # β
OK
β βββ RegistrationRepository.java # β
OK
βββ service/
β βββ EventSearchService.java # β οΈ Update to use eventCode
β βββ EventAnalyticsService.java # β
OK
βββ controller/
β βββ EventController.java # β Uses UUID in path β needs eventCode
β βββ SessionController.java # β
OK
β βββ RegistrationController.java # β
OK
βββ dto/
β βββ EventResponse.java # β Manual DTO β should be generated
β βββ CreateEventRequest.java # β Manual DTO β should be generated
β βββ UpdateEventRequest.java # β Manual DTO β should be generated
β βββ PatchEventRequest.java # β Manual DTO β should be generated
βββ exception/
β βββ EventNotFoundException.java # β Extends RuntimeException β should extend shared-kernel NotFoundException
β βββ BusinessValidationException.java # β Extends RuntimeException β should extend shared-kernel ValidationException
β βββ WorkflowException.java # β Extends RuntimeException β should extend shared-kernel ValidationException
β βββ GlobalExceptionHandler.java # β Uses local ErrorResponse β should use shared-kernel ErrorResponse
βββ event/
β βββ EventCreatedEvent.java # β Likely custom POJO β should extend DomainEvent<UUID>
β βββ EventUpdatedEvent.java # β Likely custom POJO β should extend DomainEvent<UUID>
β βββ EventPublishedEvent.java # β Likely custom POJO β should extend DomainEvent<UUID>
β βββ EventCancelledEvent.java # β Likely custom POJO β should extend DomainEvent<UUID>
βββ config/
βββ CacheConfig.java # β
OK (Caffeine)
βββ TestSecurityConfig.java # β
OK
Refactoring Patterns
Pattern 1: Exception Hierarchy (from company-user-management-service)
// Before (Story 1.15a.1)
public class EventNotFoundException extends RuntimeException {
public EventNotFoundException(String eventId) {
super("Event not found with ID: " + eventId);
}
}
// After (Story 2.2 - Architecture Compliant)
public class EventNotFoundException extends NotFoundException {
public EventNotFoundException(String eventCode) {
super("Event", eventCode); // Uses shared-kernel constructor
}
}
Pattern 2: ErrorResponse (from company-user-management-service)
// Before (Story 1.15a.1) - Likely local ErrorResponse
ErrorResponse error = new ErrorResponse(
"NOT_FOUND",
"Event not found",
404
);
// After (Story 2.2 - Architecture Compliant)
ErrorResponse error = ErrorResponse.builder()
.timestamp(Instant.now())
.path(request.getRequestURI()) // β
New
.status(HttpStatus.NOT_FOUND.value())
.error("Not Found")
.message(ex.getMessage())
.correlationId(CorrelationIdGenerator.generate()) // β
New
.severity("WARNING") // β
New
.build();
Pattern 3: Domain Events (from company-user-management-service)
// Before (Story 1.15a.1) - Likely custom POJO
@Data
@Builder
public class EventCreatedEvent {
private UUID eventId;
private String title;
private Instant eventTimestamp;
}
// After (Story 2.2 - Architecture Compliant)
@Getter
@EqualsAndHashCode(callSuper = true)
public class EventCreatedEvent extends DomainEvent<UUID> {
private final String eventCode; // Public ID
private final Integer eventNumber;
private final String title;
// ... other fields
public EventCreatedEvent(
UUID eventId, // Internal UUID for DomainEvent
String eventCode, // Public eventCode
Integer eventNumber,
String title,
String userId) { // User from JWT
super(eventId, "EventCreated", userId); // DomainEvent<UUID>
this.eventCode = eventCode;
this.eventNumber = eventNumber;
this.title = title;
}
}
Pattern 4: DomainEventPublisher (from company-user-management-service)
// Before (Story 1.15a.1) - Likely custom publisher
@Service
public class EventService {
private final CustomEventPublisher eventPublisher; // β Custom
public void createEvent(CreateEventRequest request) {
Event saved = eventRepository.save(event);
eventPublisher.publishEventCreatedEvent(saved);
}
}
// After (Story 2.2 - Architecture Compliant)
@Service
public class EventService {
private final DomainEventPublisher eventPublisher; // β
Shared-kernel
private final SecurityContextHelper securityContextHelper;
public void createEvent(CreateEventRequest request) {
Event saved = eventRepository.save(event);
String userId = securityContextHelper.getCurrentUserId();
EventCreatedEvent event = new EventCreatedEvent(
saved.getId(), // UUID
saved.getEventCode(), // Public eventCode
saved.getEventNumber(),
saved.getTitle(),
userId
);
eventPublisher.publish(event); // β
Shared-kernel publisher
}
}
Pattern 5: Meaningful ID (eventCode)
// Before (Story 1.15a.1) - UUID as public ID
@Entity
public class Event {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id; // β Exposed in API
// ...
}
// Controller
@GetMapping("/{eventCode}")
public EventResponse getEvent(@PathVariable String eventCode) {
return eventService.getEventByCode(eventCode);
}
// After (Story 2.2 - Architecture Compliant)
@Entity
public class Event {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id; // β
Internal database key only
@Column(name = "event_code", unique = true, nullable = false)
private String eventCode; // β
Public identifier (e.g., "BATbern56")
@Column(name = "event_number", unique = true, nullable = false)
private Integer eventNumber; // β
Sequential number
@PrePersist
protected void onCreate() {
if (eventCode == null && eventNumber != null) {
eventCode = "BATbern" + eventNumber; // Auto-generate
}
}
}
// Controller
@GetMapping("/{eventCode}")
public EventResponse getEvent(@PathVariable String eventCode) {
return eventService.getEventByCode(eventCode);
}
// Response DTO (id field returns eventCode, NOT UUID)
{
"id": "BATbern56", // β
eventCode
"eventNumber": 56,
"title": "BATbern 2025",
// ...
}
OpenAPI Spec Updates Required
File: docs/api/events-api.openapi.yml
Remove (if present):
# Lines ~1438-1452 - Remove local PaginationMetadata definition
PaginationMetadata:
type: object
properties:
page:
type: integer
# ...
# Lines ~1454-1466 - Remove local ErrorResponse definition
ErrorResponse:
type: object
properties:
error:
type: string
# ...
Add Instead:
components:
schemas:
# Import shared-kernel types via external references
ErrorResponse:
$ref: '../../../shared-kernel/src/main/resources/openapi/shared-types.yaml#/components/schemas/ErrorResponse'
PaginationMetadata:
$ref: '../../../shared-kernel/src/main/resources/openapi/shared-types.yaml#/components/schemas/PaginationMetadata'
Update eventCode pattern:
paths:
/api/v1/events/{eventCode}: # β
Use eventCode, NOT {id}
get:
parameters:
- name: eventCode
in: path
required: true
schema:
type: string
pattern: '^BATbern[0-9]+$'
example: 'BATbern56'
Database Migration Update
File: services/event-management-service/src/main/resources/db/migration/VX__update_events_meaningful_id.sql
-- Add eventCode and eventNumber columns
ALTER TABLE events
ADD COLUMN event_code VARCHAR(50),
ADD COLUMN event_number INTEGER;
-- Populate eventNumber from existing data (sequential)
UPDATE events
SET event_number = ROW_NUMBER() OVER (ORDER BY created_at);
-- Populate eventCode from eventNumber
UPDATE events
SET event_code = 'BATbern' || event_number;
-- Add constraints
ALTER TABLE events
ALTER COLUMN event_code SET NOT NULL,
ALTER COLUMN event_number SET NOT NULL,
ADD CONSTRAINT uq_event_code UNIQUE (event_code),
ADD CONSTRAINT uq_event_number UNIQUE (event_number);
-- Create indexes
CREATE UNIQUE INDEX idx_event_code ON events(event_code);
CREATE UNIQUE INDEX idx_event_number ON events(event_number);
-- Create trigger for auto-generation
CREATE OR REPLACE FUNCTION generate_event_code()
RETURNS TRIGGER AS $$
BEGIN
IF NEW.event_code IS NULL AND NEW.event_number IS NOT NULL THEN
NEW.event_code := 'BATbern' || NEW.event_number;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_generate_event_code
BEFORE INSERT ON events
FOR EACH ROW
EXECUTE FUNCTION generate_event_code();
Regression Testing Strategy
Critical: All 51 tests from Story 1.15a.1 MUST still pass after refactoring.
Test Update Pattern:
// Before (Story 1.15a.1) - UUID assertions
@Test
void should_getEventById_when_eventExists() {
UUID eventId = UUID.randomUUID();
// ... create event with UUID
mockMvc.perform(get("/api/v1/events/" + eventId))
.andExpect(jsonPath("$.id").value(eventId.toString()));
}
// After (Story 2.2) - eventCode assertions
@Test
void should_getEventByCode_when_eventExists() {
String eventCode = "BATbern56";
// ... create event with eventCode
mockMvc.perform(get("/api/v1/events/" + eventCode))
.andExpect(jsonPath("$.id").value("BATbern56")); // Returns eventCode as id
}
Definition of Done Checklist
Development Complete
- Domain events extend DomainEvent
Schema Validation
Regression Testing
Review Ready
Change Log
| Date | Version | Description | Author |
|---|---|---|---|
| 2025-10-27 | 1.0 | Story created as REFACTORING story (not greenfield) to bring Story 1.15a.1 to architecture compliance | Bob (SM Agent) |
Dev Agent Record
Agent Model Used
claude-sonnet-4-5-20250929
Implementation Approach
REFACTORING story - Story 1.15a.1 already implemented most features. This story brings code to full architecture compliance.
Assessment Findings (Task 0 - Completed)
Current State Analysis:
- β Meaningful ID (AC3, AC9): ALREADY DONE - Event entity has eventCode/eventNumber, controller uses eventCode in paths
- β Exception Hierarchy (AC4): ALREADY DONE - All exceptions extend shared-kernel (NotFoundException, ValidationException)
- β ErrorResponse (AC5): ALREADY DONE - GlobalExceptionHandler uses shared-kernel ErrorResponse with correlationId, path, severity
- β OpenAPI Generator (AC1): ALREADY DONE - build.gradle configured with importMappings
- β οΈ OpenAPI Spec (AC2): PARTIALLY DONE - Generator configured but spec defines ErrorResponse/PaginationMetadata locally (lines 1438-1454+)
- β Domain Events (AC6): NEEDS FIX - Events extend DomainEvent
instead of DomainEvent - β DomainEventPublisher (AC7): NOT IMPLEMENTED - Events defined but never published
- β Schema Validation Test (AC8): DOESNβT EXIST - Need to create
- β Tests: 61 tests exist (more than 51 mentioned)
Architecture Gaps Identified:
- OpenAPI spec has local ErrorResponse/PaginationMetadata schemas β Need external $ref to shared-kernel
- EventCreatedEvent, EventUpdatedEvent, EventPublishedEvent, EventCancelledEvent extend DomainEvent
β Should be DomainEvent - No event publishing in controller/service β Need DomainEventPublisher integration
- Missing SchemaValidationTest
Tasks to Complete:
- Task 1: Fix OpenAPI spec (remove local schemas, add $ref)
- Task 2: SKIP (OpenAPI Generator already configured)
- Task 3: SKIP (Exception hierarchy already compliant)
- Task 4: Fix domain events to extend DomainEvent
- Task 5: Add DomainEventPublisher integration
- Task 6: SKIP (Meaningful ID already implemented)
- Task 7: Create SchemaValidationTest
- Task 8: Run regression tests (verify 61 tests pass)
- Task 9: Update documentation
- Task 10: Final verification
Debug Log References
[To be filled during implementation]
Completion Notes
[To be filled upon completion]
Files Changed
Files to Modify:
- docs/api/events-api.openapi.yml (remove local schemas, add $ref)
- services/event-management-service/src/main/java/ch/batbern/events/event/EventCreatedEvent.java (UUID)
- services/event-management-service/src/main/java/ch/batbern/events/event/EventUpdatedEvent.java (UUID)
- services/event-management-service/src/main/java/ch/batbern/events/event/EventPublishedEvent.java (UUID)
- services/event-management-service/src/main/java/ch/batbern/events/event/EventCancelledEvent.java (UUID)
- services/event-management-service/src/main/java/ch/batbern/events/controller/EventController.java (add event publishing)
Files to Create:
- services/event-management-service/src/test/java/ch/batbern/events/validation/SchemaValidationTest.java
- services/event-management-service/ARCHITECTURE-COMPLIANCE-FIXES.md
Deployment Notes
NO DATA MIGRATION needed - eventCode/eventNumber already in database schema from Story 1.15a.1
QA Results
[To be filled by QA agent after review]