This REST API manages billiard table reservations with JWT authentication, player registration, and reservation comments. The architecture follows REST principles with clear separation of concerns across layers.
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Controllers │───▶│ Services │───▶│ DAOs │
│ (REST Layer) │ │ (Business Logic)│ │ (Data Access) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Filters │ │ DTOs │ │ Models │
│ (Cross-cutting) │ │ (Data Transfer) │ │ (Entities) │
└─────────────────┘ └─────────────────┘ └─────────────────┘- Authentication: Verifies user identity
- Authorization: Controls access rights on resources
- Stateless sessions: No server-side session storage, everything is in the token
- Algorithm: HS512 (HMAC with SHA-512)
- Library:
jjwt-api 0.12.6 - Custom claims:
sub: User identifierown: List of reservation indexes owned by userply: List of reservation indexes where user participates
{
"iss": "billard-book-api",
"sub": "userId",
"name": "userName",
"own": [1, 3, 5],
"ply": [1, 2, 3, 4],
"exp": 1754692839,
"iat": 1754689239
}- Responsibility: Reservation CRUD and actions
- Main endpoints:
POST /reservations: CreateGET /reservations/{id}: ReadPUT /reservations/{id}: UpdateDELETE /reservations/{id}: DeletePOST /reservations/{id}/register: Register to reservationDELETE /reservations/{id}/unregister: Unregister from reservationPOST /reservations/{id}/comment: Add comment
- Responsibility: Resource and collection access
- Main endpoints:
GET /reservations: Reservation listingGET /reservations/{id}/players: Reservation players
- Responsibility: User management and authentication
- Main endpoints:
POST /users/login: Sign inPOST /users/logout: Sign out- Full user CRUD
@Service
public class ReservationOperationService {
// Business logic for reservation operations
// JWT claim management
// Business rule validation
}Responsibilities:
- Validate business rules
- Update JWT claims after each operation
- Manage relationships between users and reservations
@Service
public class ReservationResourceService {
// Resource access logic
// DTO transformations
// Data filtering
}public abstract class AbstractListDao<T> implements Dao<T> {
protected final List<T> collection = new ArrayList<>();
// Generic in-memory list implementation
}Principles:
- In-memory storage:
List<T>as persistence layer - Soft deletion: Deleted elements become
null - Index-based identity: List index is used as identifier
- Type safety: Generic abstractions for reuse
public class ReservationDao extends AbstractListDao<Reservation> {
@Override
public Reservation findOne(Serializable id)
throws DeletedReservationException {
Reservation r = super.findOne(id);
if(r == null) {
throw new DeletedReservationException(id.toString());
}
return r;
}
}Specific behavior:
- Handles deleted reservations explicitly
- Owner-based lookup (
findByOwner) - Player-based lookup (
findByPlayer)
public class Reservation {
private final String id; // Stable UUID (8 chars)
private String tableId; // Billiard table id
private final String ownerId; // Reservation owner
private LocalDateTime startTime; // Start time
private LocalDateTime endTime; // End time
private List<String> players; // Players (max 4)
private List<Comment> comments; // Reservation comments
}Business rules:
- Maximum 4 players per reservation
- Stable ID: UUID generated at creation and never changed
- Automatic owner participation: creator is automatically included as a player
public class Comment {
private final String authorId; // Comment author
private final String content; // Content
}public class User {
private String id; // Unique identifier
private String name; // Display name
private String password; // Password (hashed)
}DTOs are the API contract boundary and are used to:
- Hide internal entity structure
- Control exposed payloads
- Support safe API evolution
public class ReservationRequestDto {
private String tableId;
private LocalDateTime startTime;
private LocalDateTime endTime;
}public class ReservationResponseDto {
private String tableId;
private String ownerId;
private LocalDateTime startTime;
private LocalDateTime endTime;
private List<LinkDto> players; // Liens vers les joueurs
private List<Comment> comments;
}public class PlayersResponseDto {
private List<LinkDto> players;
}@Order(1)
public class AuthenticationFilter extends HttpFilterResponsibility: JWT verification
- Extract token from
Authorization: Bearer - Validate signature and expiration
- Store user info in request attributes
@Order(2)
public class AuthorizationFilter extends HttpFilterResponsibility: resource access control
- Check reservation-level permissions
- Validate
ownandplyclaims - Prevent unauthorized access
@Order(3)
public class DateCacheFilter extends HttpFilterResponsibility: conditional HTTP cache management
Last-ModifiedandIf-Modified-Sinceheaders304 Not Modifiedresponses for bandwidth optimization- Cache invalidation on write operations
Logic:
// GET: cache validation
if (ifModifiedSince >= lastModified.getTime()) {
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
return;
}
// POST/PUT/DELETE: cache invalidation
lastModifiedMap.put(url, new Date());@Order(4)
public class ETagFilter extends HttpFilterResponsibility: ETag management for consistency
- Generate content-based ETags
- Validate through
If-None-Match - Reduce conflicting updates
Problem solved: reservation IDs changed after updates Solution: immutable UUID generated at creation time
public Reservation(String tableId, String creatorId, ...) {
this.id = UUID.randomUUID().toString().substring(0, 8);
// ID never changes after creation
}| Code | Meaning | Usage |
|---|---|---|
| 200 | OK | Resource found and returned |
| 201 | Created | Resource created successfully |
| 302 | Found | Resource temporarily moved |
| 304 | Not Modified | Resource unchanged (cache hit) |
| 400 | Bad Request | Invalid request (for example, missing parameters) |
| 401 | Unauthorized | Authentication required |
| 403 | Forbidden | Access denied (for example, insufficient rights) |
| 404 | Not Found | Resource does not exist |
| 410 | Gone | Resource existed but was deleted |
| 409 | Conflict | Resource conflict (for example, duplicate) |
| 422 | Unprocessable Entity | Validation failed |
404 vs 410 logic:
// 404: ID never existed
throw new NameNotFoundException("Reservation not found");
// 410: ID existed but reservation was deleted (null entry in list)
if (hasDeletedElements && validUuidFormat) {
throw new DeletedReservationException("Reservation deleted");
}Smart cache behavior: updating a subresource invalidates its parent resource cache
// POST /reservations/{id}/comment invalidates /reservations/{id} cache
if (url.matches("/reservations/[^/]+/.*")) {
String parentUrl = url.replaceFirst("(/reservations/[^/]+)/.*", "$1");
lastModifiedMap.put(parentUrl, now);
}- Spring Boot 3.3.5: Core framework
- Java 21: Programming language
- Maven: Dependency management
- Jackson: JSON/XML serialization
- JWT (jjwt 0.12.6): Authentication
- Jakarta Servlet API: HTTP filters
- DAO Pattern: Data access
- DTO Pattern: Data transfer objects
- Filter Chain: Request processing
- Service Layer: Business logic
- Dependency Injection: Inversion of control
The full OpenAPI specification is available here:
OpenAPI Specification (Billard-Book-api.yaml)
try {
// Business operation
} catch (NameNotFoundException e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
} catch (DeletedReservationException e) {
return ResponseEntity.status(HttpStatus.GONE).build();
} catch (InvalidNameException e) {
return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY).build();
}@JsonDeserialize(using = LocalDateTimeDeserializer.class)
private LocalDateTime startTime;
// Handles "null" string values in input
public class LocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime>@Component
public class ConnectionManager {
// Central JWT token management
// User extraction and validation
// Claim refresh logic
}- Separation of concerns: each layer has a precise role
- Extensibility: easy to add endpoints and features
- Testability: modular architecture supports isolated testing
- Performance: intelligent HTTP caching strategy
- Security: robust authentication and authorization model
- Standards: aligned with REST and HTTP conventions
- Tests: 89/89 (100% pass rate)
- Stability: non-deterministic behaviors removed
- Performance: HTTP cache reduces bandwidth usage
- Maintainability: structured and documented codebase
This technical documentation reflects the current optimized API state. The architecture is ready for production-oriented hardening and future evolution.