This project implements a restaurant reservation system using two microservices built with Spring Boot, following SOLID principles. The project uses Lombok extensively to reduce boilerplate code.
- Table Availability Microservice (Port 8080): Manages table data and availability status using hardcoded in-memory data
- Reservation Microservice (Port 8081): Handles customer reservations, waitlist, and preferences using PostgreSQL database
- PostgreSQL Database: Docker containerized database for Reservation Service only
- Java 17 or higher
- Maven 3.6+ (or use an IDE like IntelliJ IDEA/Eclipse)
- Docker and Docker Compose
- PostgreSQL 15 (via Docker) - only required for Reservation Service
restaurant/
├── table-service/ # Table Availability Microservice
│ ├── src/main/java/com/restaurant/table/
│ ├── pom.xml
│ └── lombok.config
├── reservation-service/ # Reservation Microservice
│ ├── src/main/java/com/restaurant/reservation/
│ ├── pom.xml
│ └── lombok.config
├── docker-compose.yml # PostgreSQL container setup
├── lombok.config # Global Lombok configuration
└── README.md
- Spring Boot 3.2.0: Framework for building microservices
- Spring WebFlux: For reactive HTTP client (WebClient)
- Spring Data JPA: For database operations (Reservation Service)
- PostgreSQL 15: Database for Reservation Service
- Lombok: Reduces boilerplate code (getters, setters, constructors, builders, logging)
- SpringDoc OpenAPI (Swagger): API documentation
- Docker Compose: Container orchestration
Start the PostgreSQL container using Docker Compose:
docker-compose up -dThis will create a PostgreSQL container running on port 5432 with:
- Username:
postgres - Password:
postgrespassword - Database:
reservation_db(auto-created)
Note: Table Service uses hardcoded data and does not require PostgreSQL.
Verify the container is running:
docker psBuild Table Service:
cd table-service
mvn clean package -DskipTests
mvn spring-boot:runBuild Reservation Service:
cd reservation-service
mvn clean package -DskipTests
mvn spring-boot:run-
IntelliJ IDEA / Eclipse:
- Open the project root folder
- Let IDE import Maven projects
- Run
TableApplication.java(table-service) - Run
ReservationApplication.java(reservation-service)
-
VS Code:
- Install Java Extension Pack
- Open project folder
- Run Spring Boot applications from the IDE
# Table Service
cd table-service
java -jar target/table-service-1.0.0.jar
# Reservation Service (in another terminal)
cd reservation-service
java -jar target/reservation-service-1.0.0.jar- Table Service: http://localhost:8080
- Reservation Service: http://localhost:8081
Once both services are running, access Swagger UI:
- Table Service: http://localhost:8080/swagger-ui.html
- Reservation Service: http://localhost:8081/swagger-ui.html
API Documentation (JSON):
- Table Service: http://localhost:8080/v3/api-docs
- Reservation Service: http://localhost:8081/v3/api-docs
Get table details including availability status.
Example Request:
curl http://localhost:8080/tables/T001Example Response:
{
"tableId": "T001",
"capacity": 4,
"location": "Window",
"available": true
}Error Response (404):
{
"error": "Table not found"
}Update table availability status.
Example Request:
curl -X PUT http://localhost:8080/tables/T001 \
-H "Content-Type: application/json" \
-d '{"available": false}'Example Response:
{
"tableId": "T001",
"capacity": 4,
"location": "Window",
"available": false
}Process a reservation request or cancellation.
Example Request - Create Reservation:
curl -X POST http://localhost:8081/reservations \
-H "Content-Type: application/json" \
-d '{
"tableId": "T001",
"customerId": "C001",
"reservationType": "reserve",
"preferences": ["Window", "Quiet"]
}'Example Response (Table Available):
{
"reservationId": "R001",
"status": "success",
"tableId": "T001",
"waitlistMessage": null
}Example Response (Table Not Available - Waitlist):
{
"reservationId": null,
"status": "waitlisted",
"tableId": "T001",
"waitlistMessage": "You are #3 in the queue."
}Example Request - Cancel Reservation:
curl -X POST http://localhost:8081/reservations \
-H "Content-Type: application/json" \
-d '{
"tableId": "T001",
"customerId": "C001",
"reservationType": "cancel",
"preferences": []
}'Example Response (Cancellation with Waitlist Fulfillment):
{
"reservationId": "R002",
"status": "success",
"tableId": "T001",
"waitlistMessage": null
}Example Response (Simple Cancellation):
{
"reservationId": "R001",
"status": "success",
"tableId": "T001",
"waitlistMessage": null
}404 - Table Not Found:
{
"error": "Table not found"
}400 - Invalid Reservation Type:
{
"error": "Invalid reservation type"
}500 - Reservation Processing Failed:
{
"error": "Reservation processing failed"
}The Table Service uses the following hardcoded table data (initialized in memory):
| Table ID | Capacity | Location | Available |
|---|---|---|---|
| T001 | 4 | Window | true |
| T002 | 2 | Corner | false |
| T003 | 6 | Center | true |
| T004 | 8 | Balcony | false |
The Reservation Service connects to PostgreSQL with the following configuration:
- Host:
localhost - Port:
5432 - Database:
reservation_db - Username:
postgres - Password:
postgrespassword - Driver:
org.postgresql.Driver - Connection URL:
jdbc:postgresql://localhost:5432/reservation_db
These settings can be found in reservation-service/src/main/resources/application.properties.
reservations table:
id(BIGINT, Primary Key)reservation_id(VARCHAR, Unique) - Format: R001, R002, etc.table_id(VARCHAR)customer_id(VARCHAR)status(VARCHAR) - ACTIVE, CANCELLED, FULFILLEDpreferences(JSON) - Array of preference stringscreated_at(TIMESTAMP)
waitlist table:
id(BIGINT, Primary Key)customer_id(VARCHAR)table_id(VARCHAR)preferences(JSON) - Array of preference stringsposition(INT) - Position in waitlist queuecreated_at(TIMESTAMP)
Note: Table Service does not use a database. It uses in-memory hardcoded data.
-
Create Reservation (
reservationType: "reserve"):- Customer sends POST request to
/reservationswithreservationType: "reserve" - Reservation Service checks table availability via Table Service
- If available: Creates reservation, updates table availability, returns
status: "success" - If not available: Adds customer to waitlist, returns
status: "waitlisted"with position message
- Customer sends POST request to
-
Cancel Reservation (
reservationType: "cancel"):- Customer sends POST request to
/reservationswithreservationType: "cancel" - Reservation Service cancels the reservation
- If waitlist exists: Finds best match based on preferences and fulfills reservation
- If no waitlist: Updates table availability to available
- Customer sends POST request to
- Customers are added to waitlist when tables are unavailable
- Waitlist maintains position order (1, 2, 3, etc.)
- When a table becomes available, the system:
- Matches customer preferences using strategy pattern
- Considers position (earlier = higher priority)
- Automatically fulfills the best match
- Updates positions of remaining waitlist entries
The system uses a strategy pattern for preference matching:
- Customers can specify preferences (e.g., "Window", "Quiet", "Corner")
- When a table becomes available, the system finds the best match
- Matching algorithm considers:
- Number of matching preferences
- Waitlist position (first-come-first-served bias)
This project uses Lombok extensively to reduce boilerplate code:
- @Data: Generates getters, setters, toString, equals, hashCode (Entities & DTOs)
- @Builder: Enables fluent builder pattern for object creation
- @NoArgsConstructor & @AllArgsConstructor: Constructor generation
- @RequiredArgsConstructor: Generates constructor for final fields (dependency injection)
- @Slf4j: Provides
logfield for logging (no need for Logger declaration)
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class TableDTO {
private String tableId;
private Integer capacity;
private String location;
private Boolean available;
}
// Usage
TableDTO table = TableDTO.builder()
.tableId("T001")
.capacity(4)
.location("Window")
.available(true)
.build();-
Single Responsibility: Each service/class has a single, well-defined responsibility
TableService: Manages table dataReservationService: Manages reservationsWaitlistService: Manages waitlist operationsPreferenceMatcher: Handles preference matching logic
-
Open/Closed: Preference matching uses strategy pattern (open for extension, closed for modification)
PreferenceMatcherinterface allows different matching algorithms
-
Liskov Substitution: Interfaces for service clients ensure substitutability
TableServiceClientinterface withTableServiceClientImplimplementation
-
Interface Segregation: Separate interfaces for different responsibilities
- Separate interfaces for different service operations
-
Dependency Inversion: Services depend on abstractions (interfaces) rather than concrete implementations
- Services depend on
TableServiceClientinterface, not implementation
- Services depend on
Both services include comprehensive error handling:
-
TableNotFoundException (404): When table is not found
- Response:
{"error": "Table not found"}
- Response:
-
TableServiceException (503): When Table Service is unavailable
- Response:
{"error": "Reservation processing failed"}
- Response:
-
IllegalArgumentException (400): Invalid reservation type or request
- Response:
{"error": "Invalid reservation type"}
- Response:
-
Validation Errors (400): Request validation errors
- Response:
{"error": "Validation message"}
- Response:
-
Generic Exceptions (500): Internal server errors
- Response:
{"error": "Reservation processing failed"}
- Response:
Test Table Service:
# Get table details
Invoke-RestMethod -Uri "http://localhost:8080/tables/T001" -Method GET | ConvertTo-Json
# Update table availability
$body = @{ available = $false } | ConvertTo-Json
Invoke-RestMethod -Uri "http://localhost:8080/tables/T001" -Method PUT -Body $body -ContentType "application/json" | ConvertTo-JsonTest Reservation Service:
# Create reservation
$body = @{
tableId = "T001"
customerId = "C001"
reservationType = "reserve"
preferences = @("Window", "Quiet")
} | ConvertTo-Json
Invoke-RestMethod -Uri "http://localhost:8081/reservations" -Method POST -Body $body -ContentType "application/json" | ConvertTo-Json
# Cancel reservation
$body = @{
tableId = "T001"
customerId = "C001"
reservationType = "cancel"
preferences = @()
} | ConvertTo-Json
Invoke-RestMethod -Uri "http://localhost:8081/reservations" -Method POST -Body $body -ContentType "application/json" | ConvertTo-JsonUse the provided PowerShell script to check service status:
.\check-services.ps1- Stop the Spring Boot applications (Ctrl+C in terminal or stop in IDE)
- Stop PostgreSQL container:
docker-compose downTo remove volumes (deletes data):
docker-compose down -vIf Maven is not installed or not in PATH:
- Option 1: Install Maven and add to PATH
- Option 2: Use an IDE (IntelliJ IDEA, Eclipse) which includes Maven support
- Option 3: Use Maven Wrapper (can be added to project)
If ports 8080 or 8081 are already in use:
- Find process using the port:
Get-NetTCPConnection -LocalPort 8080,8081
- Update
server.portinapplication.propertiesfiles - Update
table.service.urlin Reservation Service if Table Service port changes
- Verify PostgreSQL container is running:
docker ps - Check PostgreSQL logs:
docker logs restaurant-postgres - Verify credentials in
application.propertiesmatch docker-compose.yml - Ensure PostgreSQL is healthy:
docker ps --filter "name=restaurant-postgres" - Test PostgreSQL connection:
docker exec -it restaurant-postgres psql -U postgres -d reservation_db
- Ensure Table Service is running before Reservation Service
- Verify
table.service.urlin Reservation Service properties (default:http://localhost:8080) - Check network connectivity between services
- Test Table Service directly:
curl http://localhost:8080/tables/T001
Table Service does not require PostgreSQL. If it fails to start:
- Check for port conflicts
- Verify Java 17+ is installed:
java -version - Check application logs for errors
- Ensure PostgreSQL container is running:
docker ps - Verify database connection in
application.properties - Check if database exists: PostgreSQL will auto-create if configured correctly
- Test PostgreSQL connection:
docker exec -it restaurant-postgres psql -U postgres -c "\l"
- Uses hardcoded in-memory data (no database required)
- Data persists only during application runtime
- Tables are initialized on service startup
- Uses PostgreSQL database for persistence
- Requires PostgreSQL container to be running
- Tables are auto-created by JPA/Hibernate
- Add authentication and authorization (JWT)
- Implement distributed tracing (Zipkin, Jaeger)
- Add caching layer (Redis)
- Implement event-driven architecture with message queues (RabbitMQ, Kafka)
- Add comprehensive unit and integration tests
- Implement circuit breaker pattern for service resilience (Resilience4j)
- Add Maven Wrapper for easier setup
- Implement rate limiting
- Add monitoring and metrics (Prometheus, Grafana)
This project is for educational/demonstration purposes.