⬅️ Back to Layers Overview

Service Interaction Diagram

Operation Flow

The following diagram illustrates how a typical business operation flows through the service layer:

graph TB subgraph "Business Operation" Input["Input DTO"] Valid["Validate"] Transform["Transform DTO β†’ Entity"] Persist["Persist to Database"] Audit["Create Audit Entry"] Response["Transform Entity β†’ DTO"] end Input --> Valid Valid -->|Success| Transform Valid -->|Failure| Error1["Throw Exception"] Transform --> Persist Persist -->|Success| Audit Persist -->|Failure| Error2["Throw Exception"] Audit --> Response Response --> Output["Output DTO"] style Input fill:#bbdefb style Valid fill:#90caf9 style Transform fill:#64b5f6 style Persist fill:#42a5f5 style Audit fill:#2196f3 style Response fill:#1976d2 style Output fill:#1565c0 style Error1 fill:#ef9a9a style Error2 fill:#ef9a9a

Step-by-Step Breakdown

1. Input DTO

Request arrives from controller with data transfer object:

CreateSupplierDTO dto = new CreateSupplierDTO();
dto.setName("TechCorp");
dto.setContactName("John Doe");

2. Validate

Specialized validators check business rules before any persistence:

validator.validateRequiredFields(dto);
validator.validateUniquenessOnCreate(dto.getName());
// Throws exception if validation fails

3. Transform DTO β†’ Entity

Mapper converts API contract to domain model:

Supplier entity = mapper.toEntity(dto);
// Result: Supplier with name="TechCorp", contactName="John Doe"

4. Persist to Database

Repository saves entity and returns with generated ID:

Supplier saved = repository.save(entity);
// Result: Supplier with id="uuid-123", all fields populated

5. Create Audit Entry

Optional: Create related audit records (like stock history):

stockHistoryService.logInitialStock(saved);
// Creates StockHistory entry linked to item

6. Transform Entity β†’ DTO

Mapper converts persisted entity back to API response:

SupplierDTO result = mapper.toDTO(saved);
// Result: SupplierDTO with all fields and audit timestamps

7. Return to Client

DTO returned with HTTP 200 OK:

HTTP/1.1 201 Created
Content-Type: application/json

{
  "id": "uuid-123",
  "name": "TechCorp",
  "contactName": "John Doe",
  "createdBy": "admin",
  "createdAt": "2025-01-15T10:30:00"
}

Example: Create Supplier

Complete example of a supplier creation operation:

// 1. Controller receives request
@PostMapping
public ResponseEntity<SupplierDTO> create(
        @RequestBody CreateSupplierDTO dto) {
    SupplierDTO result = service.create(dto);
    return ResponseEntity.status(HttpStatus.CREATED).body(result);
}

// 2. Service orchestrates the flow
@Service
@RequiredArgsConstructor
@Transactional
public class SupplierServiceImpl implements SupplierService {
    
    private final SupplierRepository repository;
    private final SupplierValidator validator;
    private final SupplierMapper mapper;
    
    public SupplierDTO create(CreateSupplierDTO dto) {
        // Step 1: Validate input
        validator.validateRequiredFields(dto);
        validator.validateUniquenessOnCreate(dto.getName());
        
        // Step 2: Transform DTO β†’ Entity
        Supplier entity = mapper.toEntity(dto);
        
        // Step 3: Set audit fields
        entity.setCreatedBy(getCurrentUsername());
        entity.setCreatedAt(LocalDateTime.now());
        
        // Step 4: Persist to database
        Supplier saved = repository.save(entity);
        
        // Step 5: Transform Entity β†’ DTO
        return mapper.toDTO(saved);
    }
    
    private String getCurrentUsername() {
        return SecurityContextHolder.getContext()
            .getAuthentication()
            .getName();
    }
}

// 3. Exception handler catches failures
@RestControllerAdvice
public class GlobalExceptionHandler {
    
    @ExceptionHandler(IllegalStateException.class)
    public ResponseEntity<ErrorResponse> handleDuplicate(
            IllegalStateException ex) {
        return ResponseEntity.status(HttpStatus.CONFLICT)
            .body(new ErrorResponse("CONFLICT", ex.getMessage()));
    }
}

Example: Update Supplier

Update follows similar pattern with slight variations:

@Service
@RequiredArgsConstructor
@Transactional
public class SupplierServiceImpl implements SupplierService {
    
    public SupplierDTO update(String id, UpdateSupplierDTO dto) {
        // Step 1: Retrieve existing entity
        Supplier entity = repository.findById(id)
            .orElseThrow(() -> new NoSuchElementException(
                "Supplier not found: " + id));
        
        // Step 2: Validate changes
        validator.validateUpdateFields(dto);
        
        // Step 3: Apply changes
        entity.setName(dto.getName());
        entity.setContactName(dto.getContactName());
        
        // Step 4: Update audit fields
        entity.setUpdatedBy(getCurrentUsername());
        entity.setUpdatedAt(LocalDateTime.now());
        
        // Step 5: Persist (implicit via @Transactional)
        // entity is already managed, changes auto-persisted
        
        // Step 6: Return DTO
        return mapper.toDTO(entity);
    }
}

Example: Delete Supplier

Deletion includes constraint validation:

@Service
@RequiredArgsConstructor
@Transactional
public class SupplierServiceImpl implements SupplierService {
    
    public void delete(String id) {
        // Step 1: Check if entity exists
        Supplier entity = repository.findById(id)
            .orElseThrow(() -> new NoSuchElementException(
                "Supplier not found: " + id));
        
        // Step 2: Validate deletion is allowed
        validator.validateDeletionAllowed(id);
        // Throws exception if items exist
        
        // Step 3: Delete from database
        repository.delete(entity);
        
        // No response DTO needed for DELETE
    }
}

Layered Interaction

Complete view of service within architecture:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚     Controller Layer                 β”‚
β”‚  (HTTP Requests/Responses)           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
             β”‚
             β”‚ Calls Service
             ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚     SERVICE LAYER                    β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  β”‚ 1. Input DTO                    β”‚ β”‚
β”‚  β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚
β”‚  β”‚ 2. Validation                   β”‚ β”‚
β”‚  β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚
β”‚  β”‚ 3. DTO β†’ Entity Transform       β”‚ β”‚
β”‚  β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚
β”‚  β”‚ 4. Business Logic               β”‚ β”‚
β”‚  β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚
β”‚  β”‚ 5. Audit Logging                β”‚ β”‚
β”‚  β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚
β”‚  β”‚ 6. Entity β†’ DTO Transform       β”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
             β”‚
             β”‚ Calls Repository
             ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚     Repository Layer                 β”‚
β”‚  (JPA/Database Operations)           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

⬅️ Back to Layers Overview