Claude
Skills
Sign in
Back

clean-architecture

Included with Lifetime
$97 forever

Provides implementation patterns for Clean Architecture, Hexagonal Architecture (Ports & Adapters), and Domain-Driven Design in Java 21+ Spring Boot 3.5+ applications. Use when structuring layered architectures, separating domain logic from frameworks, implementing ports and adapters, creating entities/value objects/aggregates, or refactoring monolithic codebases for testability and maintainability.

Design

What this skill does


# Clean Architecture, Hexagonal Architecture & DDD for Spring Boot

## Overview

This skill provides comprehensive guidance for implementing Clean Architecture, Hexagonal Architecture (Ports & Adapters), and Domain-Driven Design tactical patterns in Java 21+ Spring Boot 3.5+ applications. It ensures clear separation of concerns, framework-independent domain logic, and highly testable codebases through proper layering and dependency management.

## When to Use

- Architecting new Spring Boot applications with clear separation of concerns
- Refactoring tightly coupled code into testable, layered architectures
- Implementing domain logic independent of frameworks and infrastructure
- Designing ports and adapters for swappable implementations
- Applying Domain-Driven Design tactical patterns (entities, value objects, aggregates)
- Creating testable business logic without Spring context dependencies

## Instructions

### 1. Understand the Core Concepts

#### Clean Architecture Layers (Dependency Rule)

Dependencies flow inward. Inner layers know nothing about outer layers.

| Layer | Responsibility | Spring Boot Equivalent |
|-------|---------------|----------------------|
| **Domain** | Entities, value objects, domain events, repository interfaces | `domain/` - no Spring annotations |
| **Application** | Use cases, application services, DTOs, ports | `application/` - `@`Service, `@`Transactional |
| **Infrastructure** | Frameworks, database, external APIs | `infrastructure/` - `@`Repository, `@`Entity |
| **Adapter** | Controllers, presenters, external gateways | `adapter/` - `@`RestController |

#### Hexagonal Architecture (Ports & Adapters)

- **Domain Core**: Pure Java business logic, no framework dependencies
- **Ports**: Interfaces defining contracts (driven and driving)
- **Adapters**: Concrete implementations (JPA, REST, messaging)

#### Domain-Driven Design Tactical Patterns

- **Entities**: Objects with identity and lifecycle (e.g., `Order`, `Customer`)
- **Value Objects**: Immutable, defined by attributes (e.g., `Money`, `Email`)
- **Aggregates**: Consistency boundary with root entity
- **Domain Events**: Capture significant business occurrences
- **Repositories**: Persistence abstraction, implemented in infrastructure

### 2. Organize Package Structure

Follow this feature-based package organization:

```
com.example.order/
├── domain/
│   ├── model/              # Entities, value objects
│   ├── event/              # Domain events
│   ├── repository/         # Repository interfaces (ports)
│   └── exception/          # Domain exceptions
├── application/
│   ├── port/in/            # Driving ports (use case interfaces)
│   ├── port/out/           # Driven ports (external service interfaces)
│   ├── service/            # Application services
│   └── dto/                # Request/response DTOs
├── infrastructure/
│   ├── persistence/        # JPA entities, repository adapters
│   └── external/           # External service adapters
└── adapter/
    └── rest/               # REST controllers
```

### 3. Implement the Domain Layer (Framework-Free)

The domain layer must have zero dependencies on Spring or any framework.

- Use Java records for immutable value objects with built-in validation
- Place business logic in entities, not services (Rich Domain Model)
- Define repository interfaces (ports) in the domain layer
- Use strongly-typed IDs to prevent ID confusion
- Implement domain events for decoupling side effects
- Use factory methods for entity creation to enforce invariants

### 4. Implement the Application Layer

- Create use case interfaces (driving ports) in `application/port/in/`
- Create external service interfaces (driven ports) in `application/port/out/`
- Implement application services with `@Service` and `@Transactional`
- Use DTOs for request/response, separate from domain models
- Publish domain events after successful operations

### 5. Implement the Infrastructure Layer (Adapters)

- Create JPA entities in `infrastructure/persistence/`
- Implement repository adapters that map between domain and JPA entities
- Use MapStruct or manual mappers for domain-JPA conversion
- Configure conditional beans for swappable implementations
- Keep infrastructure concerns isolated from domain logic

### 6. Implement the Adapter Layer (REST)

- Create REST controllers in `adapter/rest/`
- Inject use case interfaces, not implementations
- Use Bean Validation on DTOs
- Return proper HTTP status codes and responses
- Handle exceptions with global exception handlers

### 7. Apply Best Practices

1. **Dependency Rule**: Domain has zero dependencies on Spring or other frameworks
2. **Immutable Value Objects**: Use Java records for value objects with built-in validation
3. **Rich Domain Models**: Place business logic in entities, not services
4. **Repository Pattern**: Domain defines interface, infrastructure implements
5. **Domain Events**: Decouple side effects from primary operations
6. **Constructor Injection**: Mandatory dependencies via final fields
7. **DTO Mapping**: Separate domain models from API contracts
8. **Transaction Boundaries**: Place `@`Transactional in application services
9. **Factory Methods**: Use `Entity.create()` for invariant enforcement during construction
10. **Separate JPA Entities**: Keep domain entities separate from JPA entities with mappers

### 8. Validate Architecture Compliance

After implementing each layer, verify the dependency rules are respected:

- **Domain Layer Check**: Run `grep -r "@Service\|@Component\|@Autowired" domain/` to ensure zero Spring imports
- **ArchUnit Test**: Add dependency tests to verify no infrastructure imports in domain layer:
  ```java
  noClasses().that().resideInPackage("..domain..")
      .should().accessClassesThat().resideInAnyPackage("..spring..", "..infrastructure..");
  ```
- **Entity Exposure Check**: Verify JPA entities are never returned from domain services
- **Transaction Check**: Confirm `@`Transactional only on application layer services, never on domain

### 9. Write Tests

- **Domain Tests**: Pure unit tests without Spring context, fast execution
- **Application Tests**: Unit tests with mocked ports using Mockito
- **Infrastructure Tests**: Integration tests with `@`DataJpaTest and Testcontainers
- **Adapter Tests**: Controller tests with `@`WebMvcTest

## Examples

### Example 1: Domain Layer - Entity with Domain Events

```java
// domain/model/Order.java
public class Order {
    private final OrderId id;
    private final List<OrderItem> items;
    private Money total;
    private OrderStatus status;
    private final List<DomainEvent> domainEvents = new ArrayList<>();

    private Order(OrderId id, List<OrderItem> items) {
        this.id = id;
        this.items = new ArrayList<>(items);
        this.status = OrderStatus.PENDING;
        calculateTotal();
    }

    public static Order create(List<OrderItem> items) {
        validateItems(items);
        Order order = new Order(OrderId.generate(), items);
        order.domainEvents.add(new OrderCreatedEvent(order.id, order.total));
        return order;
    }

    public void confirm() {
        if (status != OrderStatus.PENDING) {
            throw new DomainException("Only pending orders can be confirmed");
        }
        this.status = OrderStatus.CONFIRMED;
    }

    public List<DomainEvent> getDomainEvents() {
        return List.copyOf(domainEvents);
    }

    public void clearDomainEvents() {
        domainEvents.clear();
    }
}
```

### Example 2: Domain Layer - Value Object with Validation

```java
// domain/model/Money.java (Value Object)
public record Money(BigDecimal amount, Currency currency) {
    public Money {
        if (amount.compareTo(BigDecimal.ZERO) < 0) {
            throw new DomainException("Amount cannot be negative");
        }
    }

    public static Money zero() {
        return new Money(BigDecimal.ZERO, Currency.getInstance("EUR"));
    }

    public Money add(Money other) {
  

Related in Design