Service Layers & Component Architecture
Layered Architecture
StockEase follows a classic N-tier layered architecture with clear separation of concerns:
HTTP Layer] B[Security Layer
JWT, Spring Security
Cross-cutting Concern] C[Business Logic Services
Domain Layer] D[Data Access
Repositories, Entities
Persistence Layer] E["Database
PostgreSQL or H2 for tests
Data Layer"] A --> B B --> C C --> D D --> E style A fill:#e3f2fd style B fill:#fff3e0 style C fill:#e8f5e9 style D fill:#f3e5f5 style E fill:#fce4ec
Layer Descriptions
1. HTTP/Presentation Layer (Controllers)
Purpose: Handle HTTP requests/responses, input validation, and routing
Components: -
AuthController - Authentication endpoints -
ProductController - Product CRUD endpoints
- HealthController - System health check -
ExceptionHandler - Global error
handling
DTOs used by controllers: -
LoginRequest β { username: String,
password: String } (validated with @NotBlank)
- ApiResponse<T> β Generic response
envelope: { success: boolean, message: String, data: T }
- PaginatedResponse<T> β Wrapper for
Spring Page with pagination metadata (pageNumber,
pageSize, totalElements, totalPages)
Responsibilities:
Request β Validate Input β Call Service β Format Response β HTTP Status
Example Flow:
// POST /api/products
@PostMapping
public ResponseEntity<ProductDTO> createProduct(
@RequestBody CreateProductRequest req,
@RequestHeader("Authorization") String token) {
// 1. Controller validates request format
// 2. Controller calls ProductService
// 3. Service validates business logic
// 4. Controller formats response
// 5. Returns HTTP 201 Created
}2. Security Layer (Cross-cutting)
Purpose: Enforce authentication and authorization across all endpoints
Components: -
JwtProvider - Generate and validate JWT
tokens - SecurityConfig - Spring Security
configuration - AuthenticationFilter -
Intercept and validate requests -
PasswordEncoder - BCrypt hashing
Authentication Flow:
with username and password] --> B[2. Spring Security intercepts
HTTP Basic] B --> C[3. UserDetailsService
validates credentials] C --> D[4. JwtProvider generates token] D --> E[5. Token returned in response] E --> F[6. Client includes token in
Authorization Bearer header] F --> G[7. JwtProvider validates token
on subsequent requests] G --> H{Valid?} H -->|Yes| I[Request proceeds to Service Layer] H -->|No| J[401 Unauthorized returned] style A fill:#e3f2fd style I fill:#c8e6c9 style J fill:#ffcdd2
Protected Endpoints: - All
/api/products/* endpoints require JWT token
- /health is public (no auth required)
Controller / Endpoint summary
| Controller | Key Endpoints | Auth Requirement |
|---|---|---|
AuthController |
POST /api/auth/login |
Public (permitAll) |
HealthController |
GET /api/health |
Public |
ProductController |
GET /api/products |
JWT (ADMIN,USER) |
ProductController |
GET /api/products/paged |
JWT (ADMIN,USER) |
ProductController |
GET /api/products/{id} |
JWT (ADMIN,USER) |
ProductController |
POST /api/products |
JWT (ADMIN) |
ProductController |
PUT /api/products/{id}/quantity |
JWT (ADMIN,USER) |
ProductController |
PUT /api/products/{id}/price |
JWT (ADMIN,USER) |
ProductController |
PUT /api/products/{id}/name |
JWT (ADMIN,USER) |
ProductController |
GET /api/products/low-stock |
JWT (ADMIN,USER) |
ProductController |
GET /api/products/search |
JWT (ADMIN,USER) |
ProductController |
DELETE /api/products/{id} |
JWT (ADMIN) |
ProductController |
GET
/api/products/total-stock-value |
JWT (ADMIN,USER) |
3. Business Logic Layer (Services)
Purpose: Implement business rules, validation, and orchestration
Components: -
AuthService - User authentication and
authorization - ProductService - Product
business logic - HealthService - System
status - Custom validators and processors
AuthService Responsibilities:
not empty, unique] --> R2[Validate password
strength requirements] R2 --> R3[Hash password with BCrypt] R3 --> R4[Create User entity] R4 --> R5[Save to database] R5 --> R6[Return UserDTO] end subgraph authenticateUser["authenticateUser(username, password)"] A1[Find user by username] --> A2[Compare provided password
with hashed password] A2 --> A3[Generate JWT token
with role] A3 --> A4[Return token + user info] A2 -.->|Fail| A5[Throw exception if auth fails] end subgraph validateToken["validateToken(token)"] V1[Parse JWT signature] --> V2[Check expiration] V2 --> V3[Extract user claims] V3 --> V4[Return user info or
throw exception] end style registerUser fill:#e3f2fd style authenticateUser fill:#fff3e0 style validateToken fill:#e8f5e9
ProductService Responsibilities:
name, price, quantity] --> C2[Check authorization
ADMIN only] C2 --> C3[Check SKU uniqueness] C3 --> C4[Create Product entity] C4 --> C5[Set metadata
createdAt, createdBy] C5 --> C6[Save to database] C6 --> C7[Return ProductDTO] end subgraph getProducts["getProducts(page, size, sort, filter)"] G1[Build dynamic query
from filters] --> G2[Apply pagination
page, size] G2 --> G3[Apply sorting
name, price, date] G3 --> G4[Execute query] G4 --> G5[Map to DTOs] G5 --> G6[Return PageResponse] end subgraph updateProduct["updateProduct(id, request, userId, role)"] U1[Find product by ID] --> U2[Check authorization
ADMIN only] U2 --> U3[Validate request fields] U3 --> U4[Update product fields] U4 --> U5[Set updatedAt timestamp] U5 --> U6[Save to database] U6 --> U7[Return updated ProductDTO] end subgraph deleteProduct["deleteProduct(id, userId, role)"] D1[Find product by ID] --> D2[Check authorization
ADMIN only] D2 --> D3[Delete from database] D3 --> D4[Return success] end style createProduct fill:#e3f2fd style getProducts fill:#fff3e0 style updateProduct fill:#e8f5e9 style deleteProduct fill:#ffcdd2
Key Design Patterns: -
Dependency Injection: Spring injects
repositories into services -
Validation: Validate before database
calls - Error Handling: Throw
domain-specific exceptions - Transaction
Management: @Transactional for
data consistency
4. Data Access Layer (Repositories & Entities)
Purpose: Abstract database operations and provide query interface
Components: - User
entity - JPA entity mapping to users table -
Product entity - JPA entity mapping to
products table - AuthRepository - Spring
Data JPA repository for User -
ProductRepository - Spring Data JPA
repository for Product
Entity Example:
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
@Column(unique = true, nullable = false)
private String username;
@Column(nullable = false)
private String password; // BCrypt hashed
@Enumerated(EnumType.STRING)
private Role role; // ADMIN, USER
@CreationTimestamp
private LocalDateTime createdAt;
}Repository Interface:
public interface ProductRepository extends JpaRepository<Product, UUID> {
// Spring Data generates SQL automatically
Optional<Product> findBySku(String sku);
List<Product> findByCategory(String category);
Page<Product> findAll(Pageable pageable);
// Custom query if needed
@Query("SELECT p FROM Product p WHERE p.price > ?1 AND p.quantity > 0")
List<Product> findAffordableInStock(BigDecimal maxPrice);
}Database Operations:
Java Service β Spring Data JPA β JDBC Driver β PostgreSQL/H2
5. Database Layer (Persistence)
Purpose: Store and retrieve data reliably
Production Database: - PostgreSQL 17.5 (Neon serverless) - ACID compliance - Full-text search capabilities - Connection pooling (HikariCP)
Test Database: - H2 (in-memory) - Fast test execution - Data isolation per test - No external dependencies
Schema Management:
Flyway Migrations β Database Versioning
V1__init_schema.sql β Create tables
V2__add_indexes.sql β Add performance optimizations
V3__seed_data.sql β Populate test data
Data Flow Diagram
Create Product Flow
1. Frontend (Vue.js)
βββ User fills form (name, price, quantity)
βββ Sends POST /api/products with JWT token
βββ Content-Type: application/json
2. HTTP Layer (ProductController)
βββ Receives request
βββ Validates JSON structure
βββ Calls productService.createProduct(request, token)
βββ Catches exceptions
3. Security Layer
βββ Validates JWT token
βββ Extracts user ID and role
βββ Checks if user has ADMIN role
βββ Returns 403 Forbidden if unauthorized
4. Business Logic Layer (ProductService)
βββ Validates business rules:
β βββ Name is not empty
β βββ Price > 0
β βββ SKU is unique
β βββ Quantity >= 0
βββ Creates Product entity
βββ Sets metadata (createdAt, createdBy)
βββ Calls productRepository.save(product)
βββ Throws ValidationException if validation fails
5. Data Access Layer (Spring Data JPA)
βββ Converts Product entity to SQL INSERT
βββ Executes: INSERT INTO products (name, price, sku, ...) VALUES (...)
βββ Returns saved Product with generated ID
6. Database Layer (PostgreSQL)
βββ Validates constraints (unique SKU, NOT NULL fields)
βββ Executes INSERT
βββ Triggers any cascade operations
βββ Returns INSERTING succeeds or error
7. Back through layers with response:
βββ JPA returns Product entity
βββ Service returns ProductDTO
βββ Controller formats HTTP response
βββ Returns HTTP 201 Created with Location header
βββ Frontend receives response and updates UI
Response JSON:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Widget",
"price": 29.99,
"sku": "WIDGET-001",
"quantity": 100,
"createdAt": "2025-10-31T10:30:00Z"
}
Get Products with Filtering Flow
1. Frontend
βββ Sends: GET /api/products?page=0&size=20&sort=name,asc&category=electronics
2. ProductController
βββ Extracts query parameters
βββ Calls productService.getProducts(page, size, sort, category)
3. ProductService
βββ Builds dynamic WHERE clause: category = 'electronics'
βββ Creates PageRequest(0, 20, Sort.by("name").ascending())
βββ Calls productRepository.findAll(spec, pageRequest)
4. Spring Data JPA
βββ Translates to SQL:
β SELECT * FROM products
β WHERE category = 'electronics'
β ORDER BY name ASC
β LIMIT 20 OFFSET 0
β
βββ Executes query
5. Database
βββ Scans products table (uses index on category)
βββ Filters results
βββ Orders by name
βββ Returns limited result set
6. Back through layers:
βββ JPA returns Page<Product>
βββ Service converts to Page<ProductDTO>
βββ Controller formats JSON response
βββ Returns HTTP 200 OK with pagination metadata
Response JSON:
{
"content": [
{ "id": "...", "name": "...", "category": "electronics" },
...
],
"page": 0,
"size": 20,
"totalElements": 150,
"totalPages": 8,
"last": false
}
Component Dependencies
AuthController
βββ depends on: AuthService
βββ depends on: JwtProvider
βββ depends on: PasswordEncoder
ProductController
βββ depends on: ProductService
βββ depends on: SecurityContext (JWT validation)
βββ depends on: ExceptionHandler
AuthService
βββ depends on: AuthRepository
βββ depends on: PasswordEncoder
βββ depends on: JwtProvider
βββ depends on: UserRepository
ProductService
βββ depends on: ProductRepository
βββ depends on: Custom validators
βββ depends on: Mappers (Entity β DTO)
AuthRepository
βββ accesses: User entity (users table)
ProductRepository
βββ accesses: Product entity (products table)
Security Configuration
βββ depends on: JwtProvider
βββ depends on: UserDetailsService
βββ depends on: PasswordEncoder
Transaction Boundaries
Read Operations (No transaction needed): - GET /api/products (read-only) - GET /api/products/{id} (read-only) - GET /health (read-only)
Write Operations (@Transactional required):
@Transactional
public void createProduct(CreateProductRequest req) {
// BEGIN TRANSACTION
// Multiple operations treated as atomic
Product product = new Product(req);
productRepository.save(product); // Can fail
auditLog.log("Product created", product.getId()); // Can fail
// COMMIT TRANSACTION (if all succeed)
// ROLLBACK TRANSACTION (if any operation fails)
}Error Handling Strategy
Layer β Exception β Handler β Response
Application Exception
βββ ValidationException (400 Bad Request)
βββ AuthenticationException (401 Unauthorized)
βββ AuthorizationException (403 Forbidden)
βββ EntityNotFoundException (404 Not Found)
βββ InternalServerException (500 Internal Server Error)
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ValidationException.class)
public ResponseEntity<ErrorResponse> handleValidation(ValidationException e) {
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body(new ErrorResponse(e.getMessage(), "VALIDATION_ERROR"));
}
}
Performance Considerations
Database Indexing
-- Indexed for fast lookups
CREATE INDEX idx_products_sku ON products(sku);
CREATE INDEX idx_products_category ON products(category);
CREATE INDEX idx_users_username ON users(username);Query Optimization
- Use pagination for list endpoints (avoid loading 10k+ records)
- Use lazy loading for relationships
- Cache frequently accessed data
- Use database-level filtering before returning to application
Connection Pooling
- HikariCP manages database connections
- Maximum pool size: 10 connections
- Reduces connection overhead
Related Documentation
Main Architecture Topics
- Architecture Overview - Business context, technology stack, design decisions
- Backend Architecture - Spring Boot components and code organization
- Security Architecture - Authentication and authorization in each layer
- Deployment Architecture - How layers are deployed to production
Architecture Decisions (ADRs)
- Database Choice - Repository layer database selection
- Validation Strategy - Validation in Service layer
Design Patterns & Practices
- Repository Pattern - Repository layer implementation details
- Security Patterns - Security filters in Controller layer
Document Version: 1.0
Last Updated: October 31, 2025
Status: Production