Skip to main content

Overview

The Authorization Service is built using Hexagonal Architecture (also known as Ports and Adapters), a pattern that promotes loose coupling and high testability by organizing code around the business domain.

Architecture Layers

The system is organized into three main layers:
com.autorization.autorization/
├── auth/
│   ├── domain/           # Business logic (hexagon core)
│   ├── application/      # Use cases and orchestration
│   └── adapter/          # External interfaces
│       ├── in/          # Inbound adapters (controllers)
│       └── out/         # Outbound adapters (persistence)
├── audit/               # Same structure
└── shared/              # Cross-cutting concerns

1. Domain Layer (Core)

The domain layer contains the business logic and is completely independent of frameworks and external systems.

Key Components:

Domain Models - Rich domain objects with business behavior:
  • UserDomain (auth/domain/model/user/UserDomain.java:14)
  • RoleDomain (auth/domain/model/role/RoleDomain.java:14)
  • PermissionDomain
  • ActivityLogDomain (audit/domain/model/ActivityLogDomain.java)
Value Objects - Immutable objects that validate invariants:
  • UserId, UserEmail, UserPassword
  • RoleId, RoleName, RoleDescription
  • Located in vo packages under each domain model
Domain Exceptions - Business rule violations:
  • RoleAlreadyAssignedException
  • PermissionNotAssignedException
  • NullValueException
Ports - Interfaces defining contracts:
// Inbound ports (use cases)
public interface UserUseCasePort {
    UserResponse create(CreateUserRequest request);
    void assignRole(UUID userId, UUID roleId);
    // ...
}

// Outbound ports (dependencies)
public interface UserRepositoryPort {
    UserDomain save(UserDomain user);
    Optional<UserDomain> findById(UserId id);
    // ...
}

2. Application Layer

The application layer implements use cases by orchestrating domain objects and coordinating with external systems through ports. Services - Implement inbound ports:
  • AuthService (auth/application/services/AuthService.java:18) - Handles login and authentication
  • AuditService (audit/application/services/AuditService.java:18) - Manages audit logging
  • UserService - CRUD operations for users
DTOs - Data transfer objects for communication:
  • AuthResponse, UserResponse
  • Located in application/dto/out

3. Adapter Layer

Adapters connect the application core to external systems.

Inbound Adapters (Driving Side)

REST Controllers - Handle HTTP requests:
@RestController
@RequestMapping("/api/auth")
public class AuthController {
    private final AuthService authService;
    
    @PostMapping("/login")
    public ResponseEntity<AuthResponse> login(@Valid @RequestBody LoginRequest request) {
        AuthResponse res = authService.login(request);
        return ResponseEntity.status(HttpStatus.OK).body(res);
    }
}
Located in adapter/in/web/controller/rest/:
  • AuthController (auth/adapter/in/web/controller/rest/AuthController.java:23)
  • UserController
  • RoleController
  • PermissionController
AOP Aspects - Cross-cutting concerns:
  • AuditLogAspect (audit/adapter/in/aop/AuditLogAspect.java:27) - Intercepts methods for audit logging

Outbound Adapters (Driven Side)

Repository Adapters - Implement persistence ports:
@Component
public class UserRepositoryAdapter implements UserRepositoryPort {
    private final UserRepository userRepository;
    private final RoleRepository roleRepository;
    
    @Override
    public UserDomain save(UserDomain domain) {
        // Convert domain to entity
        User entityToSave = UserJPAMapper.fromDomain(domain);
        
        // Persist using JPA repository
        var saved = userRepository.save(entityToSave);
        
        // Convert back to domain
        return UserJPAMapper.toDomain(saved);
    }
}
Located in adapter/out/jpa/:
  • UserRepositoryAdapter (auth/adapter/out/jpa/UserRepositoryAdapter.java:27)
  • RoleRepositoryAdapter
  • PermissionRepositoryAdapter
  • AuditRepositoryAdapter (audit/adapter/out/jpa/AuditRepositoryAdapter.java)
JPA Entities - Database mappings:
  • User, Role, Permission, Module
  • ActivityLog
  • Located in adapter/out/jpa/entity/
Mappers - Convert between domain and persistence models:
  • UserJPAMapper - Converts between UserDomain and User entity
  • RoleJPAMapper, PermissionJPAMapper
  • AuditMapper

Benefits of This Architecture

1. Technology Independence

The domain layer has zero dependencies on Spring, JPA, or any framework. This means:
  • Easy to test domain logic in isolation
  • Can swap persistence or web frameworks without touching business logic

2. Clear Separation of Concerns

Each layer has a specific responsibility:
  • Domain: Business rules and invariants
  • Application: Use case orchestration
  • Adapters: Infrastructure and I/O

3. Testability

Ports can be mocked for testing:
// Test application layer without database
UserRepositoryPort mockRepo = mock(UserRepositoryPort.class);
UserService service = new UserService(mockRepo, encoder);

4. Flexibility

Multiple adapters can implement the same port:
  • JPA adapter for production
  • In-memory adapter for testing
  • REST client adapter for microservices

Data Flow Example

Let’s trace a user login request through the layers:
  1. Inbound Adapter: AuthController receives HTTP POST to /api/auth/login
  2. Application Service: AuthService.login() orchestrates:
    • Calls UserRepositoryPort.findByEmail() (outbound port)
    • Validates password using PasswordEncoder
    • Calls JwtUtil.generateToken() to create JWT
  3. Outbound Adapter: UserRepositoryAdapter queries database:
    • Uses JPA UserRepository.findByEmail()
    • Maps User entity to UserDomain
  4. Response: AuthService returns AuthResponse with token
  5. Controller: Returns HTTP 200 with JSON response

Package Structure by Feature

The architecture is organized by feature (auth, audit) rather than by layer:
auth/
├── domain/
│   ├── model/
│   │   ├── user/
│   │   │   ├── UserDomain.java
│   │   │   └── vo/ (UserId, UserEmail, etc.)
│   │   ├── role/
│   │   └── permission/
│   ├── port/
│   │   ├── in/  (UserUseCasePort, etc.)
│   │   └── out/ (UserRepositoryPort, etc.)
│   └── exception/
├── application/
│   ├── services/
│   └── dto/
└── adapter/
    ├── in/web/
    └── out/jpa/
This vertical slicing makes it easier to:
  • Understand all code related to a feature
  • Extract features into separate microservices
  • Work on features independently

Dependency Rules

Dependencies must point inward:
Adapter → Application → Domain
  • Domain has no dependencies (pure Java)
  • Application depends only on Domain
  • Adapters depend on Application and Domain
This is enforced through:
  • Package-private visibility
  • Dependency injection
  • Interface segregation (ports)

Next Steps

Authentication

Learn how JWT authentication works

RBAC System

Understand role-based access control

Audit Logging

See how AOP-based auditing is implemented

API Reference

Explore the REST API endpoints

Build docs developers (and LLMs) love