Skip to content

askaroe/java-spring-final-

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Ticketing Platform Microservices (Spring Boot)

1. Overview

This project is a scalable, event-driven microservices architecture for a ticketing platform built with Java 17 / Spring Boot 3. It handles user authentication, dynamic routing, booking management, and event inventory updates using asynchronous communication.

Key Features

  • Dynamic API Gateway (Spring Cloud Gateway): Database-driven routing with role-based access control (RBAC).
  • Event-Driven Consistency: Uses the Transactional Outbox Pattern to ensure data consistency between PostgreSQL and Kafka.
  • Centralized Auth: Keycloak integration for Identity and Access Management (IAM).
  • Docker Compose: Fully containerized setup with health checks and internal networking.

2. Architecture

High-Level Architecture Diagram

                                    ┌─────────────────────┐
                                    │    Client/Browser   │
                                    └──────────┬──────────┘
                                               │
                                               │ HTTP + JWT
                                               ▼
                          ┌────────────────────────────────────┐
                          │      KEYCLOAK (Port 8088)          │
                          │   ┌────────────────────────────┐   │
                          │   │  • User Authentication     │   │
                          │   │  • JWT Token Generation    │   │
                          │   │  • Role Management         │   │
                          │   └────────────────────────────┘   │
                          └────────────────┬───────────────────┘
                                           │ JWT Token
                                           ▼
                          ┌────────────────────────────────────┐
                          │   API GATEWAY (Port 8083)          │
                          │   ┌────────────────────────────┐   │
                          │   │  • JWT Validation          │   │
                          │   │  • Dynamic Routing         │   │
                          │   │  • RBAC                    │   │
                          │   └────────────────────────────┘   │
                          └─┬──────────────┬──────────────┬───┘
                            │              │              │
                ┌───────────┘              │              └───────────┐
                │                          │                          │
                ▼                          ▼                          ▼
    ┌──────────────────────┐  ┌──────────────────────┐  ┌──────────────────────┐
    │  BOOKING SERVICE     │  │   EVENT SERVICE      │  │   USER SERVICE       │
    │    (Port 8081)       │  │    (Port 8082)       │  │    (Port 8084)       │
    │  ┌────────────────┐  │  │  ┌────────────────┐  │  │  ┌────────────────┐  │
    │  │ • Bookings     │  │  │  │ • Venues       │  │  │  │ • User Profile │  │
    │  │ • Payments     │  │  │  │ • Events       │  │  │  │ • Registration │  │
    │  │ • Outbox       │  │  │  │ • Tickets      │  │  │  │                │  │
    │  └────────────────┘  │  │  └────────────────┘  │  │  └────────────────┘  │
    └──────────┬───────────┘  └──────────┬───────────┘  └──────────┬───────────┘
               │                         │                         │
               ▼                         ▼                         ▼
    ┌──────────────────────┐  ┌──────────────────────┐  ┌──────────────────────┐
    │    booking_db        │  │     event_db         │  │      user_db         │
    │   (PostgreSQL)       │  │   (PostgreSQL)       │  │   (PostgreSQL)       │
    └──────────┬───────────┘  └──────────────────────┘  └──────────────────────┘
               │
               │ Outbox Scheduler (5s)
               ▼
    ┌─────────────────────────────────────────────────────────┐
    │              KAFKA BROKER (Port 9092)                   │
    │  ┌───────────────────────────────────────────────────┐  │
    │  │  Topics:                                          │  │
    │  │  • booking.events                                 │  │
    │  │  • event.updates                                  │  │
    │  └───────────────────────────────────────────────────┘  │
    └──────────────────────────┬──────────────────────────────┘
                               │ Consumes
                               ▼
                    ┌────────────────────────┐
                    │   EVENT SERVICE        │
                    │   Kafka Consumer       │
                    └────────────────────────┘

Service Breakdown

Service Technology Port Responsibility
API Gateway Spring Cloud Gateway 8083 Entry point. Handles routing lookup, JWT validation, and RBAC.
Booking Service Spring Boot / Data JPA 8081 Manages orders. Implements the Outbox Producer.
Event Service Spring Boot / Data JPA 8082 Manages Venues/Events. Implements the Kafka Consumer.
Keycloak OIDC Provider 8088 Handles user login, registration, and token generation.
Kafka Message Broker 9092 Decouples the Booking service from the Event service.

3. Database Entity Relationship Diagrams (ERD)

API Gateway Database (gateway_db)

╔════════════════════════════════════════════╗
║            api_routes                      ║
╠════════════════════════════════════════════╣
║ • id                UUID (PK)              ║
║ • path_prefix       VARCHAR(255)           ║
║ • http_method       VARCHAR(20)            ║
║ • target_base_url   VARCHAR(255)           ║
║ • required_role     VARCHAR(50)            ║
║ • created_at        TIMESTAMP              ║
║ • updated_at        TIMESTAMP              ║
╚════════════════════════════════════════════╝

Event Service Database (event_db)

╔════════════════════════════════════════════╗
║              venues                        ║
╠════════════════════════════════════════════╣
║ • id            UUID (PK)                  ║
║ • name          VARCHAR(255)               ║
║ • address       VARCHAR(255)               ║
║ • city          VARCHAR(100)               ║
║ • capacity      INT                        ║
║ • created_at    TIMESTAMP                  ║
║ • updated_at    TIMESTAMP                  ║
╚════════════════════════════════════════════╝
                    │
                    │ 1:N
                    ▼
╔════════════════════════════════════════════╗
║              events                        ║
╠════════════════════════════════════════════╣
║ • id             UUID (PK)                 ║
║ • organizer_id   VARCHAR(255)              ║
║ • venue_id       UUID (FK → venues)        ║
║ • title          VARCHAR(255)              ║
║ • description    TEXT                      ║
║ • category       VARCHAR(64)               ║
║ • start_time     TIMESTAMP                 ║
║ • end_time       TIMESTAMP                 ║
║ • status         VARCHAR(32)               ║
║ • created_at     TIMESTAMP                 ║
║ • updated_at     TIMESTAMP                 ║
╚════════════════════════════════════════════╝
                    │
                    │ 1:N
                    ▼
╔════════════════════════════════════════════╗
║           ticket_types                     ║
╠════════════════════════════════════════════╣
║ • id                  UUID (PK)            ║
║ • event_id            UUID (FK → events)   ║
║ • name                VARCHAR(100)         ║
║ • price               NUMERIC(10,2)        ║
║ • currency            VARCHAR(16)          ║
║ • total_quantity      INT                  ║
║ • remaining_quantity  INT                  ║
║ • created_at          TIMESTAMP            ║
║ • updated_at          TIMESTAMP            ║
╚════════════════════════════════════════════╝

Booking Service Database (booking_db)

╔════════════════════════════════════════════╗
║             bookings                       ║
╠════════════════════════════════════════════╣
║ • id              UUID (PK)                ║
║ • user_id         UUID                     ║
║ • event_id        UUID                     ║
║ • status          VARCHAR(32)              ║
║ • total_amount    NUMERIC(10,2)            ║
║ • currency        VARCHAR(16)              ║
║ • payment_status  VARCHAR(32)              ║
║ • payment_ref     VARCHAR(255)             ║
║ • created_at      TIMESTAMP                ║
║ • updated_at      TIMESTAMP                ║
╚════════════════════════════════════════════╝
                    │
                    │ 1:N
                    ▼
╔════════════════════════════════════════════╗
║          booking_items                     ║
╠════════════════════════════════════════════╣
║ • id              UUID (PK)                ║
║ • booking_id      UUID (FK → bookings)     ║
║ • ticket_type_id  UUID                     ║
║ • quantity        INT                      ║
║ • unit_price      NUMERIC(10,2)            ║
║ • total_price     NUMERIC(10,2)            ║
╚════════════════════════════════════════════╝


╔════════════════════════════════════════════╗
║          outbox_events                     ║
║      (Transactional Outbox)                ║
╠════════════════════════════════════════════╣
║ • id              UUID (PK)                ║
║ • aggregate_type  VARCHAR(64)              ║
║ • aggregate_id    UUID                     ║
║ • event_type      VARCHAR(64)              ║
║ • payload         JSONB                    ║
║ • status          VARCHAR(32)              ║
║ • created_at      TIMESTAMP                ║
║ • processed_at    TIMESTAMP                ║
╚════════════════════════════════════════════╝

4. Kafka Event Flow Diagram

┌───────────────────────────────────────────────────────────────────┐
│                      BOOKING SERVICE                              │
│                                                                   │
│  User Request → Create Booking                                   │
│         │                                                         │
│         ▼                                                         │
│  ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓         │
│  ┃     @Transactional saveBooking()                    ┃         │
│  ┃  ┌──────────────────────────────────────────────┐  ┃         │
│  ┃  │ 1. INSERT INTO bookings                      │  ┃         │
│  ┃  │ 2. INSERT INTO booking_items                 │  ┃         │
│  ┃  │ 3. INSERT INTO outbox_events                 │  ┃         │
│  ┃  │    - status: 'PENDING'                       │  ┃         │
│  ┃  │    - event_type: 'BOOKING_CREATED'           │  ┃         │
│  ┃  │    - payload: {booking details}              │  ┃         │
│  ┃  └──────────────────────────────────────────────┘  ┃         │
│  ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛         │
│         │                                                         │
│         │ COMMIT (All or Nothing)                                │
│         ▼                                                         │
│  ┌────────────────────────────────────────────────────────────┐  │
│  │  OutboxScheduler (@Scheduled fixedDelay = 5000ms)          │  │
│  │  ┌──────────────────────────────────────────────────────┐  │  │
│  │  │ 1. SELECT * FROM outbox_events                       │  │  │
│  │  │    WHERE status = 'PENDING'                          │  │  │
│  │  │                                                      │  │  │
│  │  │ 2. FOR EACH event:                                   │  │  │
│  │  │    → Send to Kafka                                   │  │  │
│  │  │    → UPDATE status = 'PUBLISHED'                     │  │  │
│  │  │    → SET processed_at = NOW()                        │  │  │
│  │  └──────────────────────────────────────────────────────┘  │  │
│  └────────────────────────────────────────────────────────────┘  │
│         │                                                         │
└─────────┼─────────────────────────────────────────────────────────┘
          │
          │ Kafka Message
          │ ┌───────────────────────────────────────┐
          └►│ Topic: booking.events                 │
            │ Key: booking-uuid                     │
            │ Value: {                              │
            │   "eventType": "BOOKING_CREATED",     │
            │   "bookingId": "...",                 │
            │   "eventId": "...",                   │
            │   "items": [...]                      │
            │ }                                     │
            └───────────────┬───────────────────────┘
                            │
                            ▼
          ┌─────────────────────────────────────────────────────┐
          │         KAFKA BROKER (Port 9092)                    │
          │  ┌───────────────────────────────────────────────┐  │
          │  │  📋 booking.events                            │  │
          │  │     - BOOKING_CREATED                         │  │
          │  │     - BOOKING_CANCELLED                       │  │
          │  │     - BOOKING_CONFIRMED                       │  │
          │  └───────────────────────────────────────────────┘  │
          └──────────────────────┬──────────────────────────────┘
                                 │
                                 │ Consumer Group: event-service-group
                                 ▼
┌───────────────────────────────────────────────────────────────────┐
│                      EVENT SERVICE                                │
│                                                                   │
│  ┌────────────────────────────────────────────────────────────┐  │
│  │  @KafkaListener(topics = "booking.events")                 │  │
│  │                                                            │  │
│  │  handleBookingEvent(BookingEvent event) {                 │  │
│  │                                                            │  │
│  │    switch(event.getEventType()) {                         │  │
│  │                                                            │  │
│  │      case BOOKING_CREATED:                                │  │
│  │        // Decrease ticket inventory                       │  │
│  │        UPDATE ticket_types                                │  │
│  │        SET remaining_quantity -= qty                      │  │
│  │        WHERE id = ticket_type_id                          │  │
│  │                                                            │  │
│  │      case BOOKING_CANCELLED:                              │  │
│  │        // Restore ticket inventory                        │  │
│  │        UPDATE ticket_types                                │  │
│  │        SET remaining_quantity += qty                      │  │
│  │        WHERE id = ticket_type_id                          │  │
│  │    }                                                      │  │
│  │  }                                                        │  │
│  └────────────────────────────────────────────────────────────┘  │
└───────────────────────────────────────────────────────────────────┘

✓ Guaranteed Delivery    ✓ Eventual Consistency    ✓ Decoupling

Key Benefits:

  • Guaranteed Delivery: Outbox pattern ensures events are never lost
  • Eventual Consistency: Services stay in sync asynchronously
  • Decoupling: Services don't need to know about each other
  • Scalability: Can add more consumers without changing producers

5. Keycloak Integration Flow

╔═══════════════════════════════════════════════════════════════════════╗
║  STEP 1: User Login & Token Generation                               ║
╚═══════════════════════════════════════════════════════════════════════╝

  ┌─────────────┐                              ┌──────────────────┐
  │   Client    │                              │    Keycloak      │
  │   (User)    │                              │   Port 8088      │
  └──────┬──────┘                              └────────┬─────────┘
         │                                              │
         │  POST /realms/ticketing-realm/protocol/...  │
         │  ┌──────────────────────────────────────┐   │
         │  │ grant_type: password                 │   │
         │  │ username: testuser                   │   │
         │  │ password: password                   │   │
         │  │ client_id: ticketing-client          │   │
         │  └──────────────────────────────────────┘   │
         ├────────────────────────────────────────────►│
         │                                              │
         │                                              │ ✓ Validate
         │                                              │   Credentials
         │                                              │
         │  ◄ JWT Token Response                       │
         │◄────────────────────────────────────────────┤
         │  ┌──────────────────────────────────────┐   │
         │  │ access_token: eyJhbGci...            │   │
         │  │ token_type: Bearer                   │   │
         │  │ expires_in: 300                      │   │
         │  └──────────────────────────────────────┘   │
         │                                              │

╔═══════════════════════════════════════════════════════════════════════╗
║  STEP 2: API Request with JWT Validation                             ║
╚═══════════════════════════════════════════════════════════════════════╝

  ┌─────────────┐       ┌────────────────┐       ┌──────────────────┐
  │   Client    │       │  API Gateway   │       │    Keycloak      │
  └──────┬──────┘       │   Port 8083    │       │   Port 8088      │
         │              └────────┬───────┘       └────────┬─────────┘
         │                       │                        │
         │  POST /api/v1/bookings                         │
         │  Authorization: Bearer eyJhbGci...             │
         ├──────────────────────►│                        │
         │                       │                        │
         │                       │ ① Validate JWT         │
         │                       │   • Check signature    │
         │                       │   • Check expiration   │
         │                       │   • Extract roles      │
         │                       ├───────────────────────►│
         │                       │                        │
         │                       │ ◄ JWKS (Public Keys)   │
         │                       │◄───────────────────────┤
         │                       │                        │
         │                       │ ② Lookup Route         │
         │                       │   FROM api_routes      │
         │                       │   WHERE path = '/api/v1/bookings'
         │                       │                        │
         │                       │ ③ Check RBAC           │
         │                       │   required: USER       │
         │                       │   user has: [USER]     │
         │                       │   ✓ Authorized         │
         │                       │                        │

╔═══════════════════════════════════════════════════════════════════════╗
║  STEP 3: Forward to Backend Service                                   ║
╚═══════════════════════════════════════════════════════════════════════╝

         │                       │              ┌──────────────────────┐
         │                       │              │  Booking Service     │
         │                       │              │    Port 8081         │
         │                       │              └──────────┬───────────┘
         │                       │                         │
         │                       │ ④ Forward Request       │
         │                       │  POST http://booking-service:8081/...
         │                       │  + JWT in header        │
         │                       ├────────────────────────►│
         │                       │                         │
         │                       │                         │ ⚙ Process
         │                       │                         │   Business
         │                       │                         │   Logic
         │                       │                         │
         │                       │ ◄ 201 Created           │
         │                       │◄────────────────────────┤
         │                       │   { "bookingId": "..." }│
         │                       │                         │
         │  ◄ Response           │                         │
         │◄──────────────────────┤                         │
         │   { "bookingId": "..." }                        │
         │                       │                         │

Security Features:

  • JWT Validation: API Gateway verifies token signature using Keycloak's public keys
  • Role-Based Access Control: Each route can specify required roles (USER, ADMIN, etc.)
  • Token Expiration: Tokens expire after 5 minutes (configurable)
  • Centralized Auth: All services trust Keycloak as the single source of truth

6. Keycloak Configuration (Critical)

Because this system runs in Docker, we deal with "Split Horizon" DNS.

  1. Browser (You): Accesses Keycloak at http://localhost:8088. The JWT Token iss (issuer) claim will be http://localhost:8088....
  2. API Gateway (Docker): Needs to verify the token signature. It cannot talk to localhost (that would be itself). It must talk to the container named keycloak.

This is handled in the API Gateway's docker-compose environment variables:

# 1. The token claims it comes from here (Validation check)
SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI: http://localhost:8088/realms/ticketing-realm

# 2. But we fetch the public keys from here (Physical network location)
SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI: http://keycloak:8088/realms/ticketing-realm/protocol/openid-connect/certs

7. Folder Structure (Spring Boot Standard)

/project-root
├── docker-compose.yml
├── postgres-init.sql
├── /api-gateway
│   ├── src/main/java/com/example/gateway
│   │   ├── config       # SecurityConfig.java (OAuth2)
│   │   ├── model        # ApiRoute.java
│   │   ├── repository   # RouteRepository.java
│   │   └── service      # DynamicRouteLocator.java
│   └── Dockerfile
├── /booking-service
│   ├── src/main/java/com/example/booking
│   │   ├── controller
│   │   ├── service      # BookingService (writes to DB + Outbox)
│   │   ├── kafka        # OutboxScheduler.java (Polls DB -> Kafka)
│   │   └── entity       # Booking.java, OutboxEvent.java
│   └── Dockerfile
├── /event-service
│   ├── src/main/java/com/example/event
│   │   ├── kafka        # KafkaConsumer.java (@KafkaListener)
│   │   └── service      # InventoryService.java
│   └── Dockerfile

8. API Usage Example

1. Create a Route (SQL in gateway_db) Connect to the Postgres container to run this:

INSERT INTO api_routes (id, path_prefix, http_method, target_base_url, required_role)
VALUES (gen_random_uuid(), '/api/v1/bookings', 'POST', 'http://booking-service:8081', 'USER');

2. Get Token

TOKEN=$(curl -s -d "client_id=ticketing-client" -d "username=testuser" -d "password=password" -d "grant_type=password" \
  http://localhost:8088/realms/ticketing-realm/protocol/openid-connect/token | jq -r .access_token)

3. Call API

curl -X POST http://localhost:8083/api/v1/bookings \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "event_id": "123",
    "items": [{"ticket_type_id": "abc", "quantity": 1}]
  }'

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors