Skip to content

berkayerdemsoy/EventManagementSystem

Repository files navigation


Typing SVG

Spring Boot Java Spring Cloud PostgreSQL Apache Kafka Docker JWT GitHub Packages Maven


License Build Status


TABLE OF CONTENTS

Overview

Event Management System is a production-grade microservices platform built with Spring Boot 4 and Spring Cloud 2025. The system is designed from the ground up following distributed architecture principles — featuring service discovery, an API gateway, custom JWT-based stateless authentication, a shared common library distributed via GitHub Packages, email verification with SHA-256 hashed tokens, role-based access control, Apache Kafka event-driven async notifications, Quartz distributed scheduling, and full Docker containerization with health checks.

This is not just a CRUD application; it is an architectural showcase demonstrating how to build, structure, and deploy a real-world microservices ecosystem that is ready for cloud-native environments.


Architecture

                            +---------------------+
                            |     Client Apps      |
                            | (Web / Mobile / CLI) |
                            +----------+----------+
                                       |
                                       | HTTP (REST)
                                       v
                            +---------------------+
                            |    API Gateway       |
                            |  (Spring Cloud GW)   |
                            |    Port: 8090        |
                            +----------+----------+
                                       |
                           +-----------+-----------+
                           |   Eureka Discovery    |
                           |     Port: 8761        |
                           +-----------+-----------+
                                       |
                          Load Balanced | (lb://)
                                       |
                  +--------------------+--------------------+
                  |                                         |
       +----------+----------+               +-------------+----------+
       |    User Service      |               |    Event Service       |
       |    Port: 8080        |               |    Port: 8081          |
       +----------+----------+               +-------------+----------+
                  |                                         |
       +----------+----------+               +-------------+----------+
       |   PostgreSQL 17      |               |   PostgreSQL 17        |
       |  user_service_db     |               |  event_service_db      |
       |    Port: 5432        |               |    Port: 5433          |
       +---------------------+               +-----------------------+
                  |                                         |
                  +-------------------+---------------------+
                                      |
                                      | Kafka Producer
                                      v
                            +---------------------+
                            |  Apache Kafka 3.7.0  |
                            |  (KRaft — ZK yok)    |
                            +----------+----------+
                                       |
                                       | Kafka Consumer
                                       v
                            +---------------------+
                            | Notification Service |
                            |  (Strategy Pattern)  |
                            +----------+----------+
                                       |
                                       v
                            +---------------------+
                            |   Resend SMTP        |
                            +---------------------+
+-------------------------------------------------------------------+
|                     SHARED LIBRARIES                               |
|                                                                    |
|  +-----------------------+    +-----------------------------+     |
|  |     ems-common        |    |   user-service-client       |     |
|  | (GitHub Packages)     |    |  (Local Maven Module)       |     |
|  |                       |    |                             |     |
|  | - GlobalExceptionHdl  |    | - DTOs & Enums              |     |
|  | - Custom Exceptions   |    | - Declarative HTTP Client   |     |
|  | - ErrorResponseDto    |    | - RestClient + Proxy        |     |
|  | - Auth Interceptor    |    | - Jakarta Validation        |     |
|  | - NotificationEvent   |    | - PageResponse<T>           |     |
|  | - NotificationEvType  |    |                             |     |
|  | - Auto-Configuration  |    +-----------------------------+     |
|  +-----------------------+                                        |
|                                                                    |
|                          +-----------------------------+           |
|                          |   event-service-client      |           |
|                          |  (Local Maven Module)       |           |
|                          |                             |           |
|                          | - DTOs & Enums              |           |
|                          | - Declarative HTTP Client   |           |
|                          | - EventStatus enum          |           |
|                          | - CategoryDto               |           |
|                          +-----------------------------+           |
+-------------------------------------------------------------------+

Tech Stack

Core Framework

Technology Version Purpose
Spring Boot 4.0.5 Application framework
Spring Cloud 2025.1.1 Microservices infrastructure
Java 21 (LTS) Programming language

Data & Persistence

Technology Purpose
PostgreSQL Primary relational database (per-service isolation)
Spring Data JPA ORM & repository abstraction
Hibernate JPA implementation

Messaging & Async

Technology Version Purpose
Apache Kafka 3.7.0 (KRaft) Async event-driven notifications — no ZooKeeper
Kafka UI latest Topic & DLQ monitoring dashboard (port 8085)
Quartz Spring Boot Starter Distributed cron scheduling via JdbcJobStore

Security & Authentication

Technology Purpose
Spring Security Security framework
JWT Stateless token authentication (JJWT 0.13.0)
BCrypt Password hashing
SHA-256 Email verification token hashing

Infrastructure & DevOps

Technology Purpose
Docker Multi-stage containerization
Docker Compose Service orchestration with health checks (8 services)
Netflix Eureka Service discovery & registry
Spring Cloud Gateway API Gateway with load balancing

Developer Tooling

Technology Purpose
MapStruct Compile-time object mapping
Lombok Boilerplate reduction
GitHub Packages Maven artifact distribution
Spring Actuator Production monitoring endpoints
Resend Transactional email delivery

Project Structure

EventManagementSystem/
|
+-- api-gateway/                    # API Gateway Service
|   +-- Dockerfile                  # Multi-stage Docker build
|   +-- src/main/
|       +-- java/.../ApiGatewayApplication.java
|       +-- resources/application.yaml  # Route definitions, CORS, Eureka config
|
+-- EurekaServer/                   # Service Discovery Server
|   +-- Dockerfile
|   +-- src/main/
|       +-- resources/application.yml   # Standalone Eureka config
|
+-- UserService/                    # User Microservice (Multi-Module)
|   +-- Dockerfile                  # Builds both client & app modules
|   +-- user-service-client/        # Shared Client Library
|   |   +-- pom.xml
|   |   +-- src/main/java/.../
|   |       +-- client/
|   |       |   +-- UserServiceClient.java         # Declarative HTTP interface
|   |       |   +-- UserServiceClientConfig.java    # RestClient + ProxyFactory
|   |       +-- dto/
|   |       |   +-- UserCreateDto.java      # Jakarta Validation
|   |       |   +-- UserResponseDto.java
|   |       |   +-- UserLoginDto.java
|   |       |   +-- UserUpdateDto.java
|   |       |   +-- AuthResponseDto.java
|   |       |   +-- PageResponse.java       # Generic pagination wrapper
|   |       +-- enums/
|   |           +-- Roles.java              # ADMIN, USER, EVENT_OWNER
|   |
|   +-- user-service-app/           # Core Application
|       +-- pom.xml                 # GitHub Packages repo reference
|       +-- src/main/java/.../
|           +-- configs/
|           |   +-- security/
|           |   |   +-- JwtUtil.java        # HMAC-SHA token generation/validation
|           |   |   +-- JwtAuthFilter.java  # OncePerRequestFilter (stateless)
|           |   |   +-- SecurityConfig.java # Filter chain, RBAC rules
|           |   +-- adminLoginConfigs/
|           |   |   +-- AdminProperties.java    # @ConfigurationProperties
|           |   +-- emailConfigs/
|           |       +-- HashUtil.java           # SHA-256 token hashing
|           |       +-- VerificationToken.java  # Token entity (24h expiry)
|           |       +-- VerificationTokenRepository.java
|           +-- controller/
|           |   +-- UserController.java     # REST endpoints
|           +-- entity/
|           |   +-- User.java               # User entity with profile relation
|           |   +-- UserProfile.java        # One-to-one profile entity
|           +-- kafka/
|           |   +-- NotificationEventProducer.java  # Async notification producer
|           +-- mapper/
|           |   +-- UserMapper.java         # MapStruct with null-safe updates
|           +-- repository/
|           |   +-- UserRepository.java     # JPA repository with custom queries
|           +-- service/
|           |   +-- UserService.java        # Service interface
|           |   +-- EmailService.java       # Email service interface (fallback)
|           +-- serviceImpl/
|               +-- UserServiceImpl.java    # Business logic implementation
|               +-- EmailServiceImpl.java   # Resend SMTP (direct fallback)
|
+-- event-service/                  # Event Microservice (Multi-Module)
|   +-- Dockerfile                  # Builds both client & app from project root
|   +-- event-service-client/       # Shared Client Library
|   |   +-- pom.xml
|   |   +-- src/main/java/.../
|   |       +-- client/
|   |       |   +-- EventServiceClient.java         # Declarative HTTP interface
|   |       |   +-- EventServiceClientConfig.java    # RestClient + ProxyFactory
|   |       +-- dto/
|   |       |   +-- EventCreateDto.java
|   |       |   +-- EventResponseDto.java
|   |       |   +-- EventUpdateDto.java
|   |       |   +-- CategoryDto.java
|   |       |   +-- ParticipationCreateDto.java
|   |       |   +-- ParticipationResponseDto.java
|   |       +-- enums/
|   |           +-- EventStatus.java        # UPCOMING, ONGOING, COMPLETED, CANCELLED
|   |
|   +-- event-service-app/          # Core Application
|       +-- pom.xml
|       +-- src/main/java/.../
|           +-- configs/
|           |   +-- security/
|           |   |   +-- SecurityConfig.java # JWT auth filter chain
|           |   +-- QuartzConfig.java       # Distributed cron job setup
|           +-- controller/
|           |   +-- EventController.java        # REST: CRUD + filter endpoints
|           |   +-- CategoryController.java     # REST: category management
|           |   +-- ParticipationController.java # REST: event registration
|           +-- entity/
|           |   +-- Event.java              # ownerEmail denormalized field
|           |   +-- Category.java
|           |   +-- Participation.java      # participantEmail + reminderSent fields
|           +-- kafka/
|           |   +-- NotificationEventProducer.java  # recipientEmail partition key
|           +-- mapper/
|           |   +-- EventMapper.java
|           |   +-- CategoryMapper.java
|           +-- repository/
|           |   +-- EventRepository.java
|           |   +-- CategoryRepository.java
|           |   +-- ParticipationRepository.java    # findPendingReminders query
|           +-- scheduler/
|           |   +-- EventReminderJob.java   # QuartzJobBean + @DisallowConcurrentExecution
|           +-- service/ & serviceImpl/
|               +-- EventService(Impl).java
|               +-- CategoryService(Impl).java
|               +-- ParticipationService(Impl).java
|
+-- notification-service/           # Notification Microservice
|   +-- Dockerfile
|   +-- src/main/java/.../
|       +-- config/
|       |   +-- KafkaConfig.java        # DLQ template + error handler + concurrency
|       +-- dto/
|       |   +-- NotificationEvent.java  # (from ems-common)
|       +-- service/
|           +-- ConsumerService.java    # @KafkaListener + EnumMap dispatch
|           +-- handler/
|               +-- NotificationHandler.java          # Strategy interface
|               +-- EmailVerificationHandler.java
|               +-- EventOwnerWelcomeHandler.java
|               +-- ParticipantRegisteredHandler.java
|               +-- EventReminderHandler.java
|
+-- ems-common/                     # Shared Common Library (GitHub Packages)
|   +-- pom.xml                     # distributionManagement -> GitHub Packages
|   +-- src/main/java/.../
|   |   +-- config/
|   |   |   +-- CommonAutoConfiguration.java       # Auto-imports GlobalExceptionHandler
|   |   |   +-- InterceptorAutoConfiguration.java  # Conditional bean registration
|   |   +-- dto/
|   |   |   +-- ErrorResponseDto.java              # Standardized error format
|   |   |   +-- NotificationEvent.java             # Shared Kafka message DTO
|   |   |   +-- NotificationEventType.java         # EMAIL_VERIFICATION, EVENT_OWNER_WELCOME,
|   |   |                                          # PARTICIPANT_REGISTERED, EVENT_REMINDER
|   |   +-- exceptions/
|   |   |   +-- GlobalExceptionHandler.java        # @RestControllerAdvice
|   |   |   +-- NotFoundException.java
|   |   |   +-- AlreadyExistsException.java
|   |   |   +-- InvalidCredentialsException.java
|   |   |   +-- ForbiddenException.java
|   |   +-- interceptor/
|   |       +-- AuthRequestInterceptor.java    # JWT propagation for inter-service calls
|   +-- src/main/resources/META-INF/spring/
|       +-- ...AutoConfiguration.imports       # Spring Boot SPI auto-config registration
|
+-- docker-compose.yml              # Full orchestration (8 services + 2 DBs + Kafka)
+-- .env                            # Environment variables (git-ignored)
+-- .gitignore                      # Comprehensive exclusion rules
+-- .dockerignore                   # Docker build context optimization
+-- api-tests.http                  # IntelliJ HTTP Client test file
+-- kafka.md                        # Kafka architecture deep-dive documentation

Modules In-Depth

ems-common — Shared Library via GitHub Packages

This module is the backbone of cross-cutting concerns across all microservices. It is published as a Maven artifact to GitHub Packages and consumed by other services as a versioned dependency.

Why this matters:

  • Eliminates code duplication across microservices
  • Centralized exception handling — every service gets the same error response format by just adding the dependency
  • Spring Boot Auto-Configuration SPI — beans are registered automatically, zero manual config in consumer services
  • @ConditionalOnClass ensures the AuthRequestInterceptor is only activated in servlet-based services (not in the reactive gateway)
  • NotificationEvent + NotificationEventType are co-located here so all Kafka producers and consumers share the same fully-qualified class name — preventing JSON deserialization type-mismatch errors
<!-- Consumer service just adds this dependency -->
<dependency>
    <groupId>com.example</groupId>
    <artifactId>ems-common</artifactId>
    <version>1.1.4</version>
</dependency>

Auto-Configuration Registration (META-INF/spring/...AutoConfiguration.imports):

com.example.ems_common.config.CommonAutoConfiguration
com.example.ems_common.config.InterceptorAutoConfiguration

user-service-client — Declarative HTTP Interface

This module provides a type-safe, declarative HTTP client for other microservices to communicate with the User Service. Built with Spring 6's HttpServiceProxyFactory and RestClient.

@HttpExchange("/users")
public interface UserServiceClient {
    @GetExchange("/id/{id}")
    UserResponseDto getUserById(@PathVariable("id") Long id);

    @PostExchange("/login")
    AuthResponseDto login(@RequestBody UserLoginDto dto);
    // ...
}

Key Design:

  • Other services just inject UserServiceClient — no manual HTTP calls
  • AuthRequestInterceptor from ems-common automatically propagates JWT tokens to outgoing requests
  • DTOs with Jakarta Validation constraints are shared, ensuring contract consistency

user-service-app — Core Business Logic

The main application service handling user management, authentication, and email verification.

Security Architecture (Custom JWT — No UserDetailsService):

Request → JwtAuthFilter → Extract Token → Validate → Set SecurityContext → Controller
  • Completely stateless — no sessions, no database lookup on every request
  • JWT contains userId, username, and role as claims
  • JwtAuthFilter extends OncePerRequestFilter and sets UsernamePasswordAuthenticationToken directly
  • Role-based endpoint protection via SecurityFilterChain

Email Verification Flow (Async via Kafka):

1. User registers → account created (isVerified=false)
2. POST /verify-email/{id} → generates UUID token
3. Token is SHA-256 hashed before DB storage (never stored in plain text)
4. NotificationEventProducer.send(EMAIL_VERIFICATION) → Kafka topic
5. NotificationService consumes → EmailVerificationHandler → Resend SMTP
6. GET /confirm-email?token=... → hash comparison → account verified
7. Token auto-expires after 24 hours

Previously: SMTP was called synchronously, blocking the HTTP thread. Now the HTTP response returns immediately; Kafka handles delivery asynchronously with retry + DLQ.

Admin Seeding via Environment Variables:

admin:
  usernames: ${ADMIN_USERNAMES:}  # admin,admin2,admin3
  passwords: ${ADMIN_PASSWORDS:}  # matched by index

Admins are automatically detected during registration by matching username/password pairs from environment configuration — no hardcoded values.

event-service — Event Management (Multi-Module)

The Event Service follows the same client-app split pattern as the User Service. It handles event CRUD, category management, participation tracking, and distributed reminders.

event-service-client

@HttpExchange("/events")
public interface EventServiceClient {
    @PostExchange
    EventResponseDto createEvent(@RequestBody EventCreateDto dto);

    @GetExchange("/category/{categoryId}")
    List<EventResponseDto> getEventsByCategory(@PathVariable("categoryId") Long categoryId);

    @GetExchange("/city/{city}")
    List<EventResponseDto> getEventsByCity(@PathVariable("city") String city);
    // ...
}

event-service-app — Key Design Decisions

Denormalized ownerEmail on Event entity: When an event is created, the owner's email is fetched via UserServiceClient (already called for ownership verification) and stored directly on the event row. This eliminates repeated HTTP calls to User Service for every notification or listing operation.

Denormalized participantEmail on Participation entity: Participant emails are passed by the client at registration time and persisted directly. When the Quartz reminder job fires, it can build thousands of NotificationEvent objects purely from local DB data — no HTTP fan-out to User Service.

Quartz JdbcJobStore — Horizontal Scale Safe:

spring:
  quartz:
    job-store-type: jdbc          # PostgreSQL-backed, not in-memory
    jdbc:
      initialize-schema: always   # Auto-creates QRTZ_* tables
    properties:
      org.quartz.jobStore.isClustered: true
      org.quartz.scheduler.instanceId: AUTO
      org.quartz.jobStore.driverDelegateClass: org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
      org.quartz.jobStore.useProperties: true
@DisallowConcurrentExecution   // No two nodes run the same job simultaneously
@PersistJobDataAfterExecution  // Stateful job — persists data after each run
public class EventReminderJob extends QuartzJobBean { ... }

Every hour Quartz acquires a pessimistic DB lock (QRTZ_LOCKS table). Only one node across the cluster wins the lock and runs the job — preventing duplicate reminder emails even at N replicas.

Kafka Producer — Partition Key:

kafkaTemplate.send(topic, event.getRecipientEmail(), event)
//                         ↑ recipientEmail as partition key

Messages for the same recipient always land on the same partition — guaranteeing delivery order per user.

api-gateway — Single Entry Point & CORS

All external traffic enters the system exclusively through the API Gateway (port 8090). No microservice is directly exposed; they only communicate via the Docker internal network.

Routing Rules:

Route ID Predicate Upstream Service
user-service-app Path=/users/** lb://user-service-app
event-service-events Path=/events/** lb://event-service-app
event-service-categories Path=/categories/** lb://event-service-app
event-service-participants Path=/participations/** lb://event-service-app

Global CORS Configuration (spring.cloud.gateway.server.webmvc.globalcors):

globalcors:
  cors-configurations:
    '[/**]':
      allowed-origins: ${CORS_ALLOWED_ORIGINS:-http://localhost:4200}
      allowed-methods: [GET, POST, PUT, DELETE, PATCH, OPTIONS]
      allowed-headers: "*"
      allow-credentials: true
      exposed-headers:
        - Authorization
      max-age: 3600

Note: The globalcors block must sit under spring.cloud.gateway.server.webmvc (not at the top-level spring.cloud.gateway key) because this project uses the WebMVC variant of Spring Cloud Gateway. Placing it at the wrong level silently ignores the configuration — all browser preflight (OPTIONS) requests would be rejected with CORS error.

  • CORS_ALLOWED_ORIGINS is injected via environment variable in docker-compose.yml — no rebuild needed to switch from localhost:4200 to a production domain.
  • Authorization is in exposed-headers so Angular's HttpClient can read the JWT token from the response.
  • allow-credentials: true enables cookie / auth header propagation.

notification-service — Event-Driven Email Delivery

A dedicated, stateless consumer service. No database, no REST endpoints — pure Kafka consumer.

Strategy Pattern + EnumMap Dispatch:

@Service
public class ConsumerService {
    private final Map<NotificationEventType, NotificationHandler> handlerMap;

    @PostConstruct
    void initHandlerMap() {
        handlers.forEach(h -> handlerMap.put(h.getHandledType(), h));
    }

    @KafkaListener(topics = "${kafka.topics.notification-events:notification-events}")
    public void consume(NotificationEvent event) {
        handlerMap.get(event.getEventType()).handle(event);  // zero if-else
    }
}

Adding a new notification type requires:

  1. Adding a value to NotificationEventType enum in ems-common
  2. Writing a new @Component that implements NotificationHandler

Nothing else changes — ConsumerService, KafkaConfig, producers are all untouched (Open/Closed Principle).

Supported Notification Types:

Type Trigger Payload
EMAIL_VERIFICATION POST /users/verify-email/{id} verificationLink
EVENT_OWNER_WELCOME POST /events (event created) ownerName, eventTitle, eventId
PARTICIPANT_REGISTERED POST /participations eventTitle, eventDate, eventCity, eventId
EVENT_REMINDER Quartz job (hourly) eventTitle, eventDate, eventCity, eventId

Retry + Dead Letter Queue (DLQ):

// KafkaConfig.java
DefaultErrorHandler errorHandler = new DefaultErrorHandler(
    new DeadLetterPublishingRecoverer(
        dlqKafkaTemplate,
        (record, ex) -> new TopicPartition(dlqTopic, 0)
    ),
    new FixedBackOff(2000L, 3)  // 3 retries × 2s interval
);
factory.setConcurrency(3);      // 3 parallel consumer threads

A separate dlqKafkaTemplate bean is used specifically for DLQ publishing — using the main consumer's template would create a circular Spring bean dependency.

Failed messages after 3 retries are routed to notification-events-dlq and can be inspected and replayed via the Kafka UI dashboard at http://localhost:8085.

DLQ Consumer — DlqConsumerService:

A dedicated consumer listens to notification-events-dlq under its own consumer group (notification-service-dlq-group).

notification-events-dlq
          │
          ▼
  DlqConsumerService.consumeDlq()
          │
          ├── Extract DLQ headers (original topic, exception class/message, partition, offset)
          ├── log.error() ── structured failure log
          │
          ├── X-DLQ-Retry-Count < MAX (1) ──► requeue to notification-events
          │       └── ProducerRecord + incremented X-DLQ-Retry-Count header
          │               └── mainTopicKafkaTemplate.send()
          │
          └── X-DLQ-Retry-Count ≥ MAX ──► log.error("permanent failure — manual intervention required")

Infinite-loop prevention — two layers:

Layer Mechanism
Factory isolation DLQ listener uses dlqKafkaListenerContainerFactory — no DLQ redirect, 0 retries, log & skip
Re-queue counter X-DLQ-Retry-Count header tracked per message; processing stops at MAX_DLQ_RETRIES = 1

Three KafkaTemplate / Factory beans in notification-service:

Bean Used By Purpose
dlqKafkaTemplate kafkaListenerContainerFactory Main consumer → write to DLQ on failure
mainTopicKafkaTemplate DlqConsumerService DLQ consumer → re-queue to main topic
dlqKafkaListenerContainerFactory DlqConsumerService Isolated factory — no DLQ redirect, breaks infinite loop

Key Engineering Decisions

1. GitHub Packages for Shared Libraries

Instead of local mvn install or monorepo setups, ems-common is published to GitHub Packages as a versioned Maven artifact. This mirrors real-world enterprise workflows where shared libraries have their own release lifecycle.

2. Multi-Module Service Pattern

Both the User Service and Event Service are split into *-client (DTOs + HTTP interface) and *-app (implementation) modules. Other microservices only depend on the lightweight client module — they never need the full service code.

3. Spring Boot Auto-Configuration SPI

The ems-common library uses META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports to register beans automatically. Consumer services get global exception handling and auth interceptors without any @Import or @ComponentScan.

4. Conditional Bean Registration

@AutoConfiguration
@ConditionalOnClass(name = "jakarta.servlet.http.HttpServletRequest")
public class InterceptorAutoConfiguration { ... }

This prevents the servlet-based AuthRequestInterceptor from being loaded in reactive services (like Spring Cloud Gateway with WebFlux).

5. Kafka KRaft — No ZooKeeper

Apache Kafka 3.7.0 runs in KRaft mode (Kafka Raft Metadata). ZooKeeper is completely removed from the stack, reducing infrastructure footprint and startup time.

KAFKA_PROCESS_ROLES: "broker,controller"
KAFKA_LISTENERS: "INTERNAL://:29092,EXTERNAL://:9092,CONTROLLER://:9093"
KAFKA_ADVERTISED_LISTENERS: "INTERNAL://kafka:29092,EXTERNAL://localhost:9092"
KAFKA_CONTROLLER_QUORUM_VOTERS: "1@kafka:9093"

Three listener types serve distinct purposes:

  • INTERNAL (port 29092) — inter-container communication within Docker network
  • EXTERNAL (port 9092) — external access from host machine (dev tools, local producers)
  • CONTROLLER (port 9093) — Raft leader election and metadata replication

6. JSON Serialization — Type Header Disabled

# Producer (user-service, event-service):
spring.json.add.type.headers: false   # Don't embed __TypeId__ header

# Consumer (notification-service):
spring.json.value.default.type: com.example.ems_common.dto.NotificationEvent

If type headers were enabled, the consumer would look for com.example.user_service_app.kafka.NotificationEvent — a class that doesn't exist in notification-service. By disabling headers and specifying value.default.type, the consumer always deserializes to the shared ems-common DTO regardless of which service produced the message.

7. Quartz over @Scheduled for Distributed Environments

@Scheduled fires on every running instance simultaneously. With 2+ event-service replicas, the same user would receive duplicate reminder emails.

Quartz JdbcJobStore with isClustered: true uses a pessimistic DB lock (QRTZ_LOCKS table) to ensure only one node fires the job. @DisallowConcurrentExecution adds an additional guard within a single JVM. The PostgreSQLDelegate driver delegate is mandatory — without it, Quartz would attempt to store job data as binary blobs which PostgreSQL rejects.

8. Multi-Stage Docker Builds

Each Dockerfile uses a two-stage approach: build with maven:3.9.9-eclipse-temurin-21, run with eclipse-temurin:21-jre. This dramatically reduces final image size and eliminates build tools from production.

9. Docker Compose Health Checks & Startup Order

Services start in strict dependency order using condition: service_healthy:

PostgreSQL (pg_isready) → User Service, Event Service
Eureka (actuator/health) → API Gateway, User Service, Event Service
Kafka (kafka-topics.sh --list) → Notification Service, User Service, Event Service

10. Token Hashing for Email Verification

Verification tokens are never stored in plain text. The UUID token is hashed with SHA-256 before persistence. On confirmation, the incoming token is hashed and compared — even if the database is compromised, tokens cannot be reversed.

11. Environment-Based Configuration

All secrets (JWT secret, DB credentials, API keys, admin passwords) are injected via .env files that are git-ignored. The application uses Spring's optional:file:.env[.properties] import for seamless local development.

12. DLQ Consumer — Closed-Loop Failure Handling

Rather than relying solely on Kafka UI for manual DLQ inspection, a dedicated DlqConsumerService closes the failure loop automatically.

Why a separate dlqKafkaListenerContainerFactory? If the DLQ consumer reused the main kafkaListenerContainerFactory, any exception thrown during DLQ processing would trigger DeadLetterPublishingRecoverer again — writing the same message back to notification-events-dlqinfinite DLQ loop. The dedicated factory has FixedBackOff(0, 0) (no retries) and a simple log-and-skip recoverer.

Why X-DLQ-Retry-Count header? Re-queuing from DLQ back to the main topic risks another DLQ failure cycle. The header travels with the Kafka message through re-queue → retry → DLQ again. On the second DLQ arrival, the counter signals permanent failure and processing stops — a stateless, DB-free infinite-loop guard.

Main topic → [fail × 3] → DLQ (count=0) → re-queue (count=1) → Main topic
→ [fail × 3] → DLQ (count=1) → count ≥ MAX → log.error + stop ✓

13. CORS — Centralized at Gateway Layer

CORS is handled exclusively at the API Gateway (spring.cloud.gateway.server.webmvc.globalcors). Individual microservices (User Service, Event Service) do not configure CORS — they only accept traffic from the gateway's internal Docker network, not from browsers directly.

Why globalcors and not a CorsFilter bean? Spring Cloud Gateway WebMVC integrates CORS natively through its GlobalCorsProperties. A custom CorsFilter bean would conflict with the gateway's own request routing and could be ordered incorrectly relative to the security filter chain.

Critical gotcha — YAML nesting level:

# ✅ CORRECT — under spring.cloud.gateway.server.webmvc
spring.cloud.gateway.server.webmvc.globalcors.cors-configurations[/**]...

# ❌ WRONG — falls into spring.cloud.gateway (reactive variant's key), silently ignored
spring.cloud.gateway.globalcors.cors-configurations[/**]...

Placing globalcors at the wrong indentation level causes browser OPTIONS preflight requests to receive no Access-Control-Allow-Origin header — resulting in hard-to-debug CORS errors in the frontend with no backend log output.


Getting Started

Prerequisites

Requirement Version
Java 21+
Maven 3.9+
Docker & Docker Compose Latest
GitHub Account For GitHub Packages access

1. Clone the Repository

git clone https://github.com/berkayerdemsoy/EventManagementSystem.git
cd EventManagementSystem

2. Configure Environment Variables

Create a .env file in the project root:

# GitHub Packages Authentication
GITHUB_ACTOR=your_github_username
GITHUB_TOKEN=your_github_personal_access_token

# Admin Seeding
ADMIN_USERNAMES=admin
ADMIN_PASSWORDS=your_secure_password

# Email Service (Resend)
RESEND_API_KEY=your_resend_api_key

# JWT Configuration
JWT_SECRET=your_64_char_hex_secret_key
JWT_EXPIRATION=3600000

3. Configure Maven for GitHub Packages

Add to your ~/.m2/settings.xml:

<settings>
  <servers>
    <server>
      <id>github</id>
      <username>YOUR_GITHUB_USERNAME</username>
      <password>YOUR_GITHUB_TOKEN</password>
    </server>
  </servers>
</settings>

4. Run with Docker Compose

docker compose up --build -d

This will start 8 containers:

Container Port Description
Eureka Server 8761 Service discovery dashboard
API Gateway 8090 Single entry point for all clients
User Service internal 8080 Auth, user management
Event Service internal 8081 Events, categories, participations
Notification Service internal 8083 Kafka consumer, email delivery
Apache Kafka 9092 Message broker (KRaft mode)
Kafka UI 8085 Topic & DLQ monitoring
PostgreSQL - User 5432 User service database
PostgreSQL - Event 5433 Event service database

5. Local Development (Without Docker)

# Start PostgreSQL databases
docker run -d --name user-db -p 5432:5432 \
  -e POSTGRES_DB=user_service_db -e POSTGRES_USER=postgres \
  -e POSTGRES_PASSWORD=postgres postgres:17-alpine

docker run -d --name event-db -p 5433:5432 \
  -e POSTGRES_DB=event_service_db -e POSTGRES_USER=postgres \
  -e POSTGRES_PASSWORD=postgres postgres:17-alpine

# Start Kafka (KRaft)
docker run -d --name kafka -p 9092:9092 \
  -e KAFKA_PROCESS_ROLES=broker,controller \
  -e KAFKA_NODE_ID=1 \
  -e CLUSTER_ID=MkU3OEVBNTcwNTJENDM2Qk \
  apache/kafka:3.7.0

# Build shared modules
cd ems-common && mvn clean install -DskipTests
cd ../UserService/user-service-client && mvn clean install -DskipTests
cd ../../event-service/event-service-client && mvn clean install -DskipTests

# Run services
cd ../../EurekaServer && mvn spring-boot:run
cd ../api-gateway && mvn spring-boot:run
cd ../UserService/user-service-app && mvn spring-boot:run
cd ../../event-service/event-service-app && mvn spring-boot:run
cd ../../notification-service && mvn spring-boot:run

API Reference

All requests go through the API Gateway at http://localhost:8090.

User Service Endpoints

Public Endpoints (No Auth Required)

Method Endpoint Description
POST /users/create Register a new user
POST /users/login Authenticate and receive JWT
GET /users/confirm-email?token=... Confirm email verification

Authenticated Endpoints (Bearer Token Required)

Method Endpoint Description
GET /users/id/{id} Get user by ID
GET /users/username/{username} Get user by username
POST /users/update/{id} Update user profile
POST /users/owner/{id} Upgrade to EVENT_OWNER role
POST /users/verify-email/{id} Send verification email (async via Kafka)

Admin-Only Endpoints (ADMIN Role Required)

Method Endpoint Description
GET /users/all?page=0&size=20 List all users (paginated)
DELETE /users/{id} Delete a user

Event Service Endpoints

Event Endpoints (EVENT_OWNER / ADMIN for writes)

Method Endpoint Description
POST /events Create event → triggers EVENT_OWNER_WELCOME notification
GET /events/{id} Get event by ID
PUT /events/{id} Update event
DELETE /events/{id} Delete event
GET /events?page=0&size=20 List all events (paginated)
GET /events/category/{categoryId} Filter events by category
GET /events/city/{city} Filter events by city
GET /events/date-range?start=...&end=... Filter events by date range

Category Endpoints

Method Endpoint Description
POST /categories Create category (ADMIN)
GET /categories List all categories
GET /categories/{id} Get category by ID
PUT /categories/{id} Update category (ADMIN)
DELETE /categories/{id} Delete category (ADMIN)

Participation Endpoints

Method Endpoint Description
POST /participations Register for event → triggers PARTICIPANT_REGISTERED notification
GET /participations/{id} Get participation by ID
GET /participations/event/{eventId} List participants for an event
DELETE /participations/{id} Cancel participation

Example Request

POST http://localhost:8090/users/login
Content-Type: application/json

{
  "username": "admin",
  "password": "password"
}

Example Response

{
  "token": "eyJhbGciOiJIUzI1NiJ9...",
  "user": {
    "id": 1,
    "username": "admin",
    "email": "admin@gmail.com",
    "role": "ADMIN",
    "verified": true,
    "createdAt": "2025-04-01T12:00:00"
  }
}

Roadmap

Roadmap

Phase 1 — Backend Microservices ✓

  • Microservices architecture with Spring Boot 4
  • Netflix Eureka service discovery
  • Spring Cloud Gateway with load balancing
  • Custom JWT authentication (stateless, no UserDetailsService)
  • Role-based access control (ADMIN, USER, EVENT_OWNER)
  • Email verification with hashed tokens
  • Shared library via GitHub Packages (ems-common)
  • Declarative HTTP client pattern (user-service-client, event-service-client)
  • Docker Compose orchestration with health checks (8 services)
  • Multi-stage Docker builds

Phase 2 — Event & Async Notification ✓

  • Event Service (CRUD, search by category / city / date range)
  • Category Service
  • Participation Service (event registration)
  • Apache Kafka 3.7.0 (KRaft — no ZooKeeper)
  • Async notification pipeline (email verification, owner welcome, registration, reminder)
  • Strategy Pattern handler dispatch in Notification Service
  • Dead Letter Queue (DLQ) with 3-retry FixedBackOff
  • DLQ Consumer with auto re-queue & infinite-loop protection (X-DLQ-Retry-Count header)
  • Kafka UI monitoring dashboard
  • Quartz JdbcJobStore (distributed cron — horizontal scale safe)
  • Per-recipient partition key for message ordering
  • Denormalized ownerEmail / participantEmail for service autonomy
  • Environment-based secret management

Phase 3 — Frontend

  • Angular / React frontend application
  • Full-stack integration through API Gateway
  • Responsive UI with event browsing, booking, and user dashboard

Phase 4 — AWS Cloud Deployment

  • AWS ECS / EKS container orchestration
  • AWS RDS for managed PostgreSQL
  • AWS MSK for managed Kafka
  • AWS Application Load Balancer (ALB) configuration
  • AWS ECR for container registry
  • CI/CD pipeline with GitHub Actions
  • Infrastructure as Code (Terraform / CloudFormation)

Phase 5 — Production Hardening

  • Idempotency — Redis eventId cache to prevent duplicate emails
  • Circuit Breaker — Resilience4j for SMTP fast-fail
  • Security vulnerability scanning (OWASP, Trivy, Snyk)
  • Penetration testing — local & production
  • Rate limiting and DDoS protection (AWS WAF)
  • Centralized logging (ELK Stack / CloudWatch)
  • Performance testing with JMeter / Gatling
  • SSL/TLS termination at load balancer
  • Database migration with Flyway/Liquibase
  • Kafka Schema Registry (Avro — typed schema vs Map<String,String> payload)
  • Micrometer + Prometheus metrics (consumer lag, retry count, DLQ count)


Genel Bakis

Event Management System, Spring Boot 4 ve Spring Cloud 2025 ile sifirdan insa edilmis, uretim ortamina hazir bir mikroservis platformudur. Sistem; servis kesfinden (Eureka), API Gateway'den, ozel JWT tabanli durumsuz (stateless) kimlik dogrulamadan, GitHub Packages uzerinden dagitilan ortak kutuphane modulunden, SHA-256 ile hashlenmis e-posta dogrulama tokenlarindan, rol tabanli erisim kontrolunden, Apache Kafka ile asenkron bildirim mimarisinden, Quartz dagitik zamanlayicidan ve saglik kontrolleri iceren tam Docker konteynerizasyonundan olusmaktadir.

Bu yalnizca bir CRUD uygulamasi degildir; gercek dunya mikroservis ekosisteminin nasil insa edilecegini, yapilandirilacagini ve deploy edilecegini gosteren mimari bir vitrindir.


Mimari Yaklasim

                            +---------------------+
                            |   Istemci (Client)   |
                            | (Web / Mobil / CLI)  |
                            +----------+----------+
                                       |
                                       v
                            +---------------------+
                            |    API Gateway       |
                            |  (Spring Cloud GW)   |
                            |    Port: 8090        |
                            +----------+----------+
                                       |
                           +-----------+-----------+
                           |   Eureka Discovery    |
                           |     Port: 8761        |
                           +-----------+-----------+
                                       |
                  +--------------------+--------------------+
                  |                                         |
       +----------+----------+               +-------------+----------+
       |    User Service      |               |    Event Service       |
       |    Port: 8080        |               |    Port: 8081          |
       +----------+----------+               +-------------+----------+
                  |                                         |
                  +-------------------+---------------------+
                                      |
                                      | Kafka Producer
                                      v
                            +---------------------+
                            |  Apache Kafka 3.7.0  |
                            |  (KRaft — ZK yok)    |
                            +----------+----------+
                                       |
                                       | Kafka Consumer
                                       v
                            +---------------------+
                            | Notification Service |
                            |  (Strategy Pattern)  |
                            +----------+----------+
                                       |
                                       v
                            +---------------------+
                            |   Resend SMTP        |
                            +---------------------+

Kullanilan Teknolojiler

Kategori Teknoloji Aciklama
Framework Spring Boot 4.0.5 + Spring Cloud 2025.1.1 Temel uygulama cercevesi
Dil Java 21 (LTS) Programlama dili
Veritabani PostgreSQL 17 + Spring Data JPA Servis basina izole veri katmani
Mesajlasma Apache Kafka 3.7.0 (KRaft) Asenkron bildirim pipeline'i
Zamanlayici Quartz JdbcJobStore Dagitik cron — yatay olcekleme uyumlu
Guvenlik Spring Security + Custom JWT (JJWT 0.13.0) Kimlik dogrulama ve yetkilendirme
Sifreleme BCrypt (sifre) + SHA-256 (token) Hassas veri koruma
API Gateway Spring Cloud Gateway WebMVC Yonlendirme, yuk dengeleme, CORS
Servis Kesfi Netflix Eureka Dinamik servis kayit ve kesif
Nesne Esleme MapStruct 1.6.3 Derleme zamanli DTO-Entity donusumu
E-posta Notification Service → Resend SMTP Kafka consumer uzerinden islemsel e-posta
Konteyner Docker + Docker Compose (8 servis) Cok asamali build ve orkestrasyon
Paket Yonetimi GitHub Packages Maven artifact dagitimi
Izleme Spring Actuator + Kafka UI Saglik kontrolu ve mesaj izleme

Onemli Muhendislik Kararlari ve Teknik Detaylar

1. GitHub Packages ile Paylasimli Kutuphane (ems-common)

ems-common modulu, tum mikroservislerin ortaklasa kullandigi exception handling, error response formati, auth interceptor ve Kafka mesaj DTO'larini (NotificationEvent, NotificationEventType) icerir. Bu modul GitHub Packages uzerinde Maven artifact olarak yayinlanir.

2. Kafka KRaft — ZooKeeper'siz Mimari

Apache Kafka 3.7.0, KRaft modunda calisir. ZooKeeper tamamen stack'ten cikarilmistir. Uc listener turu farkli amaclar icin kullanilir:

  • INTERNAL (29092): Docker icindeki container'lar arasi iletisim
  • EXTERNAL (9092): Host makineden dis erisim
  • CONTROLLER (9093): Raft lider secimi ve metadata replikasyonu

3. JSON Serializasyon — Type Header Devre Disi

# Producer: type header ekleme
spring.json.add.type.headers: false

# Consumer: her mesaji bu tip olarak parse et
spring.json.value.default.type: com.example.ems_common.dto.NotificationEvent

Type header acik olsaydi, Consumer farkli bir package'dan gelen NotificationEvent'i bulamazdi — deserialization hatasi. ems-common'daki tek DTO + header kapali = her producer uyumlu.

4. Quartz JdbcJobStore — Dagitik Kilit

@Scheduled her node'da calisir → duplicate reminder. Quartz isClustered: true ile sadece bir node QRTZ_LOCKS tablosundan kilidi alir, job'i calistirir. @DisallowConcurrentExecution ayni node icerisinde ekstra guvence saglar. PostgreSQLDelegate zorunludur — binary blob yerine String saklama.

5. Strategy Pattern — Bildirim Handler'lari

ConsumerService if-else yerine EnumMap<NotificationEventType, NotificationHandler> kullanir. Yeni bildirim tipi eklemek icin yalnizca yeni bir @Component yazmak yeterli — mevcut koda dokunulmaz (Open/Closed).

6. Denormalizasyon Kararlari

  • participantEmail Participation entity'sinde saklanir — her bildirimde User Service'e HTTP atmak gerekmez
  • ownerEmail Event entity'sinde saklanir — etkinlik olusturulurken zaten yapilan getUserById cagrisi kullanilir

7. DLQ ile Hata Yonetimi

3 retry × 2 saniye → basarisiz mesaj notification-events-dlq topic'ine tasinir. Ayri dlqKafkaTemplate circular bean dependency'yi onler.

DlqConsumerService — kapalı döngü hata isleyici:

  • notification-events-dlq topic'ini notification-service-dlq-group consumer group'u ile dinler
  • Kafka DLQ header'larindan (orijinal topic, exception sinifi/mesaji, partition/offset) hata detayini cikarir ve log.error ile yapilandirilmis log basar
  • X-DLQ-Retry-Count < 1 ise mesaji mainTopicKafkaTemplate araciligiyla notification-events'e geri gonderir (re-queue), header sayaci arttirilir
  • Sayi limite ulasinca log.error("kalici hata — manuel mudahale") basip durur

Sonsuz dongu koruması — iki katman:

  • dlqKafkaListenerContainerFactory: DLQ redirect YOK, 0 retry, hata olursa sadece logla ve gec
  • X-DLQ-Retry-Count header'i: mesajla birlikte tasinar, MAX_DLQ_RETRIES = 1 sinirinda islem durur

DLQ mesajlari Kafka UI (port 8085) uzerinden izlenebilir ve replay edilebilir.

8. Cok Asamali Docker Build

Her Dockerfile: build asamas maven:3.9.9-eclipse-temurin-21, run asamasi eclipse-temurin:21-jre. Final image boyutu kucuk, build araclari uretim ortamina gitmez.

9. Docker Compose Saglik Kontrolleri

Servisler katmanlari bagimlilik sirasina gore baslar:

PostgreSQL (pg_isready)
  → User Service, Event Service

Eureka (actuator/health)
  → API Gateway, User Service, Event Service

Kafka (kafka-topics.sh --list)
  → Notification Service, User Service, Event Service

10. CORS — Gateway Katmaninda Merkezilestirme

CORS yalnizca API Gateway seviyesinde (spring.cloud.gateway.server.webmvc.globalcors) yapilandirilir. Mikroservisler CORS tanimlamaz — tarayici istekleri dogrudan onlara ulasmaz, her sey gateway uzerinden gider.

YAML girintisi kritik:

# ✅ DOGRU — spring.cloud.gateway.server.webmvc altinda
spring.cloud.gateway.server.webmvc.globalcors...

# ❌ YANLIS — spring.cloud.gateway altinda (reaktif variant anahtari), sessizce yok sayilir
spring.cloud.gateway.globalcors...

Yanlis girintide globalcors blogu sessizce yok sayilir — tarayicidan gelen OPTIONS preflight isteklerine Access-Control-Allow-Origin header'i donmez, Angular/frontend hata verir ama backend logda hicbir iz kalmaz.

  • CORS_ALLOWED_ORIGINS ortam degiskeni ile inject edilir → yeniden derleme gerekmeden production domain'e gecis
  • Authorization exposed-headers'da tanimli → Angular HttpClient JWT token'i yanit header'indan okuyabilir

Kurulum ve Calistirma

Gereksinimler

Gereksinim Surum
Java 21+
Maven 3.9+
Docker & Docker Compose Guncel
GitHub Hesabi GitHub Packages erisimi icin

Hizli Baslangic (Docker ile)

# 1. Repoyu klonlayin
git clone https://github.com/berkayerdemsoy/EventManagementSystem.git
cd EventManagementSystem

# 2. .env dosyasini yapilandirin

# 3. Tum servisleri baslatin (8 container)
docker compose up --build -d

Servis Adresleri

Servis Adres
API Gateway http://localhost:8090
Eureka Dashboard http://localhost:8761
Kafka UI (DLQ izleme) http://localhost:8085
Kafka (dis erisim) localhost:9092
PostgreSQL - User localhost:5432
PostgreSQL - Event localhost:5433

Gelecek Vizyonu

Faz 1 — Backend Mikroservisler ✓

  • Spring Boot 4 ile mikroservis mimarisi
  • Netflix Eureka + Spring Cloud Gateway
  • CORS — Gateway katmaninda merkezilestirme (spring.cloud.gateway.server.webmvc.globalcors)
  • Ozel JWT kimlik dogrulama (stateless)
  • Rol tabanli erisim kontrolu
  • Hashlenmis tokenlarla e-posta dogrulama
  • GitHub Packages ile paylasimli kutuphane

Faz 2 — Etkinlik ve Asenkron Bildirimler ✓

  • Event Service (CRUD, kategori, sehir, tarih aralik filtreleme)
  • Participation Service (etkinlige katilim)
  • Apache Kafka 3.7.0 (KRaft — ZooKeeper yok)
  • Asenkron bildirim pipeline (e-posta dogrulama, owner hosgeldin, katilim, hatirlatici)
  • Strategy Pattern ile handler dispatch
  • DLQ + 3 retry mekanizmasi
  • DLQ Consumer — otomatik re-queue ve sonsuz dongu korumasi (X-DLQ-Retry-Count header)
  • Kafka UI izleme paneli
  • Quartz JdbcJobStore (yatay olcekleme uyumlu cron)

Faz 3 — Frontend

  • Angular / React frontend uygulamasi
  • API Gateway uzerinden full-stack entegrasyon
  • Duyarli kullanici arayuzu

Faz 4 — AWS Cloud Deployment

  • AWS ECS / EKS + AWS MSK (yonetilen Kafka)
  • AWS RDS yonetilen veritabani
  • GitHub Actions ile CI/CD pipeline
  • Terraform / CloudFormation ile IaC

Faz 5 — Uretim Saglamiligi

  • DLQ Consumer — Slack / PagerDuty alert
  • Redis ile idempotency (duplicate mail engeli)
  • Circuit Breaker — Resilience4j for SMTP fast-fail
  • Micrometer + Prometheus metrikleri
  • Guvenlik zafiyet taramasi (OWASP, Trivy)
  • Performans testi (JMeter / Gatling)
  • Kafka Schema Registry (Avro)

Built with determination and passion for distributed systems.

Profile Views

About

Cloud-native Event Management System with microservices architecture. Built for production with Spring Boot 4, custom stateless JWT auth, shared libraries via GitHub Packages, and Docker orchestration. Targeting AWS deployment with full CI/CD pipeline.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors