Skip to main content

Architecture Overview

The Blackjack API is built using a layered architecture that combines Hexagonal Architecture (Ports and Adapters), Domain-Driven Design (DDD), and Reactive Programming principles with Spring WebFlux.

Architectural Layers

The application is organized into three primary layers:

1. Domain Layer

The core of the application containing business logic and rules. Package: cat.itacademy.s05.t01.blackjack_api.domain

Domain Models

Core entities and value objects:
  • Game (Aggregate Root)
  • Player (Aggregate Root)
  • Card, Hand, Deck
  • Value Objects: GameId, PlayerId, PlayerName

Ports

Interfaces defining dependencies:
  • GameRepositoryPort
  • PlayerRepositoryPort

2. Application Layer

Orchestrates use cases and coordinates between domain and infrastructure. Package: cat.itacademy.s05.t01.blackjack_api.application

Use Cases

Business operations:
  • CreateNewGameUseCase
  • PlayMoveUseCase
  • GetGameStateUseCase
  • ChangePlayerNameUseCase
  • ViewRankingUseCase
  • DeleteGameUseCase

DTOs & Mappers

Data transfer objects and mapping logic:
  • Command objects (input)
  • Result objects (output)
  • GameStateMapper

3. Infrastructure Layer

Provides concrete implementations for external concerns. Package: cat.itacademy.s05.t01.blackjack_api.infrastructure

Web Controllers

REST API endpoints:
  • GameController
  • PlayerController
  • RankingController

Persistence Adapters

Database implementations:
  • MongoGameRepositoryAdapter
  • MySqlPlayerRepositoryAdapter

Component Interaction Flow

Here’s how components interact for a typical request:

Example: Creating a New Game

  1. HTTP Request arrives at GameController.create()
  2. Controller invokes CreateNewGameUseCase.execute()
  3. Use case calls PlayerRepositoryPort.findOrCreateByName()
  4. MySqlPlayerRepositoryAdapter queries the MySQL database reactively
  5. Use case creates domain Game object with business logic
  6. Use case calls GameRepositoryPort.save()
  7. MongoGameRepositoryAdapter persists to MongoDB
  8. Result flows back through layers as Mono<CreateGameResult>

Package Structure

src/main/java/cat/itacademy/s05/t01/blackjack_api/
├── domain/
│   ├── model/              # Entities, Value Objects, Aggregates
│   │   ├── Game.java       # Aggregate Root
│   │   ├── Player.java     # Aggregate Root
│   │   ├── Card.java       # Value Object (Record)
│   │   ├── Hand.java       # Entity
│   │   ├── GameId.java     # Value Object
│   │   └── PlayerName.java # Value Object
│   ├── port/               # Port interfaces
│   │   ├── GameRepositoryPort.java
│   │   └── PlayerRepositoryPort.java
│   └── exception/          # Domain exceptions
├── application/
│   ├── usecase/            # Application services
│   │   ├── CreateNewGameUseCase.java
│   │   └── PlayMoveUseCase.java
│   ├── dto/                # Commands and Results
│   └── mapper/             # Domain to DTO mapping
└── infrastructure/
    ├── web/
    │   └── controller/     # REST controllers
    ├── persistence/
    │   ├── mongo/          # MongoDB adapter
    │   │   ├── MongoGameRepositoryAdapter.java
    │   │   └── MongoGameDocument.java
    │   └── mysql/          # MySQL adapter
    │       ├── MySqlPlayerRepositoryAdapter.java
    │       └── PlayerRow.java
    └── config/             # Spring configuration

Key Design Principles

Dependencies point inward. The domain layer has no dependencies on outer layers. Infrastructure depends on domain, not vice versa.
Each layer has a distinct responsibility:
  • Domain: Business logic and rules
  • Application: Use case orchestration
  • Infrastructure: Technical implementation details
Domain models are immutable. Operations return new instances rather than modifying state.Example from domain/model/Game.java:55-63:
public Game hit() {
    ensureInProgress();
    var draw = deck.draw();
    Hand nextPlayer = playerHand.add(draw.card());
    Game next = new Game(id, playerId, draw.nextDeck(), nextPlayer, dealerHand, status);
    
    if(nextPlayer.isBust()) return next.withStatus(GameStatus.PLAYER_BUST).finalizeOutcome();
    return next;
}
All repository operations return Mono<T> or Flux<T> for non-blocking, asynchronous processing.

Technology Stack

LayerTechnologies
WebSpring WebFlux, Spring MVC annotations
ApplicationPlain Java, Reactor Core
DomainPure Java (no framework dependencies)
PersistenceSpring Data MongoDB (Reactive), Spring Data R2DBC (MySQL)
API DocumentationSpringDoc OpenAPI

Benefits of This Architecture

Testability

Domain logic can be tested in isolation without frameworks or databases.

Flexibility

Easy to swap implementations (e.g., switch from MongoDB to another NoSQL database).

Maintainability

Clear boundaries make code easier to understand and modify.

Scalability

Reactive programming enables efficient resource usage and high concurrency.

Next Steps

Hexagonal Architecture

Learn about ports and adapters pattern

Domain-Driven Design

Explore DDD concepts and implementations

Reactive Programming

Understand reactive patterns with Spring WebFlux

API Reference

View API endpoints and usage

Build docs developers (and LLMs) love