Skip to content

Latest commit

Β 

History

History
445 lines (406 loc) Β· 24.4 KB

File metadata and controls

445 lines (406 loc) Β· 24.4 KB

WorkOrder Management Architecture

Data Model (Entity Relationships)

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                         CUSTOMER ENTITY                              β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ PK: id (Long)                                                        β”‚
β”‚ Fields:                                                              β”‚
β”‚   - firstName: String                                               β”‚
β”‚   - lastName: String                                                β”‚
β”‚   - email: String                                                   β”‚
β”‚   - phone: String                                                   β”‚
β”‚                                                                      β”‚
β”‚ Relationships:                                                       β”‚
β”‚   - workOrders: List<WorkOrder> (OneToMany, cascade=ALL)           β”‚
β”‚     └─ Initialized with: new ArrayList<>()                          β”‚
β”‚     └─ Safe add: customer.addWorkOrder(workOrder)                  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              ↓ 1:N
                         (cascade: ALL)
                              ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                       WORKORDER ENTITY                                β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ PK: id (Long)                                                        β”‚
β”‚ FK: customer_id (references Customer.id)                            β”‚
β”‚ Fields:                                                              β”‚
β”‚   - status: String (e.g., "RECEIVED", "IN_PROGRESS")               β”‚
β”‚   - createdAt: LocalDateTime                                        β”‚
β”‚                                                                      β”‚
β”‚ Relationships:                                                       β”‚
β”‚   - customer: Customer (ManyToOne, @JsonIgnore)                    β”‚
β”‚   - skiItems: List<SkiItem> (OneToMany, cascade=ALL)               β”‚
β”‚     └─ Initialized with: new ArrayList<>()                          β”‚
β”‚     └─ Safe add: workOrder.addSkiItem(skiItem)                     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              ↓ 1:N
                         (cascade: ALL)
                              ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                        SKIITEM ENTITY                                 β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ PK: id (Long)                                                        β”‚
β”‚ FK: work_order_id (references WorkOrder.id)                         β”‚
β”‚ Fields:                                                              β”‚
β”‚   - skiMake: String (e.g., "Rossignol")                            β”‚
β”‚   - skiModel: String (e.g., "Experience 80")                       β”‚
β”‚   - serviceType: String (e.g., "WAXING")                           β”‚
β”‚                                                                      β”‚
β”‚ Relationships:                                                       β”‚
β”‚   - workOrder: WorkOrder (ManyToOne, @JsonBackReference)           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

API Request/Response Flow

Scenario: Create a Work Order for a New Customer

CLIENT REQUEST
    ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ POST /workorders                                            β”‚
β”‚ {                                                           β”‚
β”‚   "customerFirstName": "John",                             β”‚
β”‚   "customerLastName": "Doe",                               β”‚
β”‚   "email": "john@example.com",                             β”‚
β”‚   "phone": "5551234567",                                   β”‚
β”‚   "skis": [                                                β”‚
β”‚     {                                                      β”‚
β”‚       "skiMake": "Rossignol",                             β”‚
β”‚       "skiModel": "Experience 80",                         β”‚
β”‚       "serviceType": "WAXING"                             β”‚
β”‚     }                                                      β”‚
β”‚   ]                                                        β”‚
β”‚ }                                                          β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ WorkOrderController.createWorkOrder()                            β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ 1. @Valid annotation validates CreateWorkOrderRequest           β”‚
β”‚    - email format check                                         β”‚
β”‚    - phone format check (10 digits)                            β”‚
β”‚    - at least 1 ski item required                              β”‚
β”‚                                                                 β”‚
β”‚ 2. Call CustomerService.findOrCreateCustomer()               β”‚
β”‚    β”œβ”€ Query: findByEmailOrPhone(email, phone)                β”‚
β”‚    β”œβ”€ Result: Customer found or created                     β”‚
β”‚    └─ Update firstName, lastName                            β”‚
β”‚    └─ Save to database                                      β”‚
β”‚                                                                 β”‚
β”‚ 3. Create WorkOrder entity                                    β”‚
β”‚    β”œβ”€ status = "RECEIVED"                                   β”‚
β”‚    β”œβ”€ createdAt = LocalDateTime.now()                      β”‚
β”‚    └─ customer = null (to be set)                          β”‚
β”‚                                                                 β”‚
β”‚ 4. Link Customer ↔ WorkOrder                                 β”‚
β”‚    └─ customer.addWorkOrder(workOrder)                     β”‚
β”‚       β”œβ”€ Null check                                         β”‚
β”‚       β”œβ”€ Duplicate check                                    β”‚
β”‚       └─ Bidirectional link                                 β”‚
β”‚                                                                 β”‚
β”‚ 5. For each SkiItem in request:                             β”‚
β”‚    β”œβ”€ Create SkiItem entity                                β”‚
β”‚    β”œβ”€ Set skiMake, skiModel, serviceType                  β”‚
β”‚    └─ workOrder.addSkiItem(skiItem)                       β”‚
β”‚       └─ Bidirectional link                                β”‚
β”‚                                                                 β”‚
β”‚ 6. Cascade Save                                             β”‚
β”‚    └─ customerRepository.save(customer)                    β”‚
β”‚       β”œβ”€ Save Customer                                     β”‚
β”‚       β”œβ”€ Cascade: Save all WorkOrders                      β”‚
β”‚       └─ Cascade: Save all SkiItems                        β”‚
β”‚                                                                 β”‚
β”‚ 7. Map to DTO and return                                    β”‚
β”‚    └─ WorkOrderResponse.fromEntity(workOrder)              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ HTTP 201 CREATED                                                β”‚
β”‚ {                                                               β”‚
β”‚   "id": 1,                                                      β”‚
β”‚   "status": "RECEIVED",                                         β”‚
β”‚   "createdAt": "2026-01-31T10:30:00",                          β”‚
β”‚   "customerId": 1,                                              β”‚
β”‚   "customerName": "John Doe",                                   β”‚
β”‚   "customerEmail": "john@example.com",                          β”‚
β”‚   "customerPhone": "5551234567",                                β”‚
β”‚   "skiItems": [                                                 β”‚
β”‚     {                                                           β”‚
β”‚       "id": 1,                                                  β”‚
β”‚       "skiMake": "Rossignol",                                   β”‚
β”‚       "skiModel": "Experience 80",                              β”‚
β”‚       "serviceType": "WAXING"                                   β”‚
β”‚     }                                                           β”‚
β”‚   ]                                                             β”‚
β”‚ }                                                               β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    ↓
CLIENT RESPONSE

Database Schema (SQL)

-- Customers Table
CREATE TABLE customers (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    first_name VARCHAR(255),
    last_name VARCHAR(255),
    email VARCHAR(255),
    phone VARCHAR(255),
    UNIQUE KEY uk_email_phone (email, phone)
);

-- Work Orders Table
CREATE TABLE work_orders (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    customer_id BIGINT NOT NULL,
    status VARCHAR(50),
    created_at TIMESTAMP,
    promised_by TIMESTAMP,
    FOREIGN KEY (customer_id) REFERENCES customers(id) ON DELETE CASCADE
);

-- Ski Items Table
CREATE TABLE ski_items (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    work_order_id BIGINT NOT NULL,
    ski_make VARCHAR(255),
    ski_model VARCHAR(255),
    service_type VARCHAR(255),
    FOREIGN KEY (work_order_id) REFERENCES work_orders(id) ON DELETE CASCADE
);

Layer Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                       REST API LAYER                            β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ β€’ WorkOrderController (REST endpoints)                         β”‚
β”‚ β€’ CustomerController (REST endpoints)                          β”‚
β”‚                                                                 β”‚
β”‚ Responsibilities:                                              β”‚
β”‚   - Receive HTTP requests                                      β”‚
β”‚   - Validate input                                             β”‚
β”‚   - Call services                                              β”‚
β”‚   - Map entities to DTOs                                       β”‚
β”‚   - Return HTTP responses                                      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                      SERVICE LAYER                              β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ β€’ CustomerService (business logic)                             β”‚
β”‚ β€’ WorkOrderService (if needed)                                 β”‚
β”‚                                                                 β”‚
β”‚ Responsibilities:                                              β”‚
β”‚   - Business logic                                             β”‚
β”‚   - findOrCreateCustomer logic                                 β”‚
β”‚   - Complex workflows                                          β”‚
β”‚   - Return entities to controllers                             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                   REPOSITORY LAYER (DATA ACCESS)               β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ β€’ CustomerRepository (Spring Data JPA)                         β”‚
β”‚ β€’ WorkOrderRepository (Spring Data JPA)                        β”‚
β”‚ β€’ SkiItemRepository (if needed)                                β”‚
β”‚                                                                 β”‚
β”‚ Responsibilities:                                              β”‚
β”‚   - Database queries                                           β”‚
β”‚   - Cascade operations                                         β”‚
β”‚   - Return entities                                            β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                      ENTITY LAYER                               β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ β€’ Customer (JPA Entity)                                        β”‚
β”‚ β€’ WorkOrder (JPA Entity)                                       β”‚
β”‚ β€’ SkiItem (JPA Entity)                                         β”‚
β”‚                                                                 β”‚
β”‚ Responsibilities:                                              β”‚
β”‚   - Define database structure                                  β”‚
β”‚   - Relationship mapping                                       β”‚
β”‚   - Cascade configuration                                      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    DATABASE LAYER                               β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ β€’ MySQL/PostgreSQL/H2                                          β”‚
β”‚   - Customers table                                            β”‚
β”‚   - Work Orders table                                          β”‚
β”‚   - Ski Items table                                            β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

DTO Mapping Pattern

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Entities Layer  β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ WorkOrder        β”‚
β”‚ β”œβ”€ id: 1         β”‚
β”‚ β”œβ”€ customer:     β”‚
β”‚ β”‚  β”œβ”€ id: 1      β”‚
β”‚ β”‚  β”œβ”€ firstName  β”‚
β”‚ β”‚  β”œβ”€ lastName   β”‚
β”‚ β”‚  β”œβ”€ email      β”‚
β”‚ β”‚  β”œβ”€ phone      β”‚
β”‚ β”‚  └─ workOrders β”‚
β”‚ β”‚     └─ [...]   β”‚
β”‚ β”œβ”€ status        β”‚
β”‚ β”œβ”€ createdAt     β”‚
β”‚ └─ skiItems      β”‚
β”‚    β”œβ”€ id: 1      β”‚
β”‚    β”œβ”€ skiMake    β”‚
β”‚    β”œβ”€ skiModel   β”‚
β”‚    β”œβ”€ serviceTypeβ”‚
β”‚    └─ workOrder  β”‚
β”‚       └─ [...]   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         ↓
   [Mapper/DTO]
         ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  DTOs Layer (JSON Safe)  β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ WorkOrderResponse        β”‚
β”‚ β”œβ”€ id: 1                 β”‚
β”‚ β”œβ”€ customerId: 1         β”‚
β”‚ β”œβ”€ customerName: "John"  β”‚
β”‚ β”œβ”€ customerEmail         β”‚
β”‚ β”œβ”€ customerPhone         β”‚
β”‚ β”œβ”€ status                β”‚
β”‚ β”œβ”€ createdAt             β”‚
β”‚ └─ skiItems              β”‚
β”‚    └─ SkiItemResponse[]  β”‚
β”‚       β”œβ”€ id: 1           β”‚
β”‚       β”œβ”€ skiMake         β”‚
β”‚       β”œβ”€ skiModel        β”‚
β”‚       └─ serviceType     β”‚
β”‚                          β”‚
β”‚ NO CIRCULAR REFERENCES! βœ…
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Cascade Operations Example

DELETE Customer (id=1)
    ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ CASCADE DELETE: CascadeType.ALL          β”‚
β”‚ orphanRemoval = true                    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    ↓
DELETE all WorkOrders where customer_id=1
    β”œβ”€ WorkOrder 1 (DELETED)
    β”‚  └─ CASCADE: Delete all SkiItems where work_order_id=1
    β”‚     β”œβ”€ SkiItem 1 (DELETED)
    β”‚     β”œβ”€ SkiItem 2 (DELETED)
    β”‚     └─ SkiItem 3 (DELETED)
    β”‚
    β”œβ”€ WorkOrder 2 (DELETED)
    β”‚  └─ CASCADE: Delete all SkiItems where work_order_id=2
    β”‚     β”œβ”€ SkiItem 4 (DELETED)
    β”‚     └─ SkiItem 5 (DELETED)
    β”‚
    └─ WorkOrder 3 (DELETED)
       └─ CASCADE: Delete all SkiItems where work_order_id=3
          └─ SkiItem 6 (DELETED)

Result: Customer + 3 WorkOrders + 6 SkiItems (9 total) DELETED
All in single transaction βœ…

Error Handling Flow

POST /workorders (invalid request)
    ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Validation Layer (@Valid)          β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ βœ— Invalid email format?            β”‚
β”‚   β†’ HTTP 400 Bad Request           β”‚
β”‚                                    β”‚
β”‚ βœ— Phone not 10 digits?            β”‚
β”‚   β†’ HTTP 400 Bad Request           β”‚
β”‚                                    β”‚
β”‚ βœ— No ski items provided?           β”‚
β”‚   β†’ HTTP 400 Bad Request           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Business Logic Layer               β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ βœ— WorkOrder is null?               β”‚
β”‚   β†’ IllegalArgumentException       β”‚
β”‚   β†’ HTTP 500 Internal Error        β”‚
β”‚                                    β”‚
β”‚ βœ— Customer not found in DB?        β”‚
β”‚   β†’ Create new customer            β”‚
β”‚   β†’ Continue normally              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    ↓
Success β†’ HTTP 201 CREATED + WorkOrderResponse

Concurrency & Safety

Two simultaneous requests for same email/phone
    ↓
Request 1                          Request 2
β”œβ”€ Query: findByEmailOrPhone      β”œβ”€ Query: findByEmailOrPhone
β”‚  (result: not found)             β”‚  (result: not found)
β”‚                                  β”‚
β”œβ”€ Create Customer                 β”œβ”€ Create Customer
β”‚  (firstName="John")              β”‚  (firstName="Jon")
β”‚                                  β”‚
β”œβ”€ Save (DB unique constraint)     β”œβ”€ Save (DB unique constraint)
β”‚  βœ“ Success                       β”‚  βœ— UNIQUE violation
β”‚                                  β”‚
└─ Return WorkOrder               └─ Error or retry

Solutions:

  1. Add database UNIQUE constraint (already implemented)
  2. Use pessimistic locking if needed
  3. Handle SQLIntegrityConstraintViolationException
  4. Retry logic in service layer

Performance Considerations

LazyLoading vs. EAGER Loading

Current Configuration:
WorkOrder.skiItems = FetchType.EAGER

When you load a WorkOrder:
β”œβ”€ Load WorkOrder entity
β”œβ”€ Immediately load all SkiItems (no N+1 problem)
└─ Ready for REST response

Trade-off:
βœ… Good: No lazy loading issues
βœ… Good: REST APIs usually need complete data
❌ Bad: Loads ski items even if not needed

Query Optimization

Current: GET /customers/1/workorders
β”œβ”€ Load Customer
β”œβ”€ Load all WorkOrders for Customer
β”œβ”€ Load all SkiItems for each WorkOrder
└─ Acceptable for small to medium data

If you have 1000+ customers with 100+ workorders each:
β”œβ”€ Consider: Pagination
β”œβ”€ Consider: Lazy loading for workOrders
β”œβ”€ Consider: Custom queries
└─ Create: DTO projections

Summary

βœ… Entities: Properly configured with cascades and bidirectional relationships βœ… Services: Business logic for customer management βœ… Controllers: REST endpoints returning clean DTOs βœ… DTOs: Prevent JSON recursion and control API contracts βœ… Database: Proper foreign keys and constraints βœ… Validation: Input validation at request level βœ… Error Handling: Safe cascade operations βœ… Security: No infinite recursion in JSON responses