‹ 首页

clean-architecture

@giuseppe-trisciuoglio · 收录于 1 周前

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.

适合你,如果正在用 Spring Boot 构建复杂业务系统,希望分离领域与框架

/ 下载安装
clean-architecture.skill双击,或拖进 Claude 桌面版 / Cowork,即完成安装↓ .skill↓ .zip
用别的 agent?下载 .zip 解压,把文件夹放进它的技能目录
Claude Code~/.claude/skills/(项目级 .claude/skills/)
Codex CLI~/.codex/skills/
Cursor自动读取上面两处目录
其他工具见其文档的「skills」目录;两个下载是同一份文件,只是名字不同
/ 通过 npx 安装 校验哈希
npx oh-my-skill add giuseppe-trisciuoglio/developer-kit/clean-architecture
/ 通过 bash 安装
curl -fsSL https://oh-my-skill.com/install.sh | bash -s -- giuseppe-trisciuoglio/developer-kit/clean-architecture
/ 已经装过?验证本机副本,不用重装
npx oh-my-skill verify giuseppe-trisciuoglio/developer-kit/clean-architecture
安装目标可用 --agent / --scope 或 --to 明确指定;省略时只会在唯一已存在的 agent 目录上自动选择,零命中或多命中会停止并提示。content_hash 缺失或不一致均拒装。
303GitHub stars
~2.8K最小装载
~6K含声明引用
~6K文本包总量
镜像托管

怎么用

技能原文 SKILL.md作者撰写 · MIT · 306f428

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
// 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
// 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) {
        if (!this.currency.equals(other.currency)) {
            throw new DomainException("Currency mismatch");
        }
        return new Money(this.amount.add(other.amount), this.currency);
    }
}
Example 3: Domain Layer - Repository Port
// domain/repository/OrderRepository.java (Port)
public interface OrderRepository {
    Order save(Order order);
    Optional<Order> findById(OrderId id);
}
Example 4: Application Layer - Use Case and Service
// application/port/in/CreateOrderUseCase.java
public interface CreateOrderUseCase {
    OrderResponse createOrder(CreateOrderRequest request);
}

// application/dto/CreateOrderRequest.java
public record CreateOrderRequest(
    @NotNull UUID customerId,
    @NotEmpty List<OrderItemRequest> items
) {}

// application/service/OrderService.java
@Service
@RequiredArgsConstructor
@Transactional
public class OrderService implements CreateOrderUseCase {
    private final OrderRepository orderRepository;
    private final PaymentGateway paymentGateway;
    private final DomainEventPublisher eventPublisher;

    @Override
    public OrderResponse createOrder(CreateOrderRequest request) {
        List<OrderItem> items = mapItems(request.items());
        Order order = Order.create(items);

        PaymentResult payment = paymentGateway.charge(order.getTotal());
        if (!payment.successful()) {
            throw new PaymentFailedException("Payment failed");
        }

        order.confirm();
        Order saved = orderRepository.save(order);
        publishEvents(order);

        return OrderMapper.toResponse(saved);
    }

    private void publishEvents(Order order) {
        order.getDomainEvents().forEach(eventPublisher::publish);
        order.clearDomainEvents();
    }
}
Example 5: Infrastructure Layer - JPA Entity and Adapter
// infrastructure/persistence/OrderJpaEntity.java
@Entity
@Table(name = "orders")
public class OrderJpaEntity {
    @Id
    private UUID id;
    @Enumerated(EnumType.STRING)
    private OrderStatus status;
    private BigDecimal totalAmount;
    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    private List<OrderItemJpaEntity> items;
}

// infrastructure/persistence/OrderRepositoryAdapter.java
@Component
@RequiredArgsConstructor
public class OrderRepositoryAdapter implements OrderRepository {
    private final OrderJpaRepository jpaRepository;
    private final OrderJpaMapper mapper;

    @Override
    public Order save(Order order) {
        OrderJpaEntity entity = mapper.toEntity(order);
        return mapper.toDomain(jpaRepository.save(entity));
    }

    @Override
    public Optional<Order> findById(OrderId id) {
        return jpaRepository.findById(id.value()).map(mapper::toDomain);
    }
}
Example 6: Adapter Layer - REST Controller
// adapter/rest/OrderController.java
@RestController
@RequestMapping("/api/orders")
@RequiredArgsConstructor
public class OrderController {
    private final CreateOrderUseCase createOrderUseCase;

    @PostMapping
    public ResponseEntity<OrderResponse> createOrder(
            @Valid @RequestBody CreateOrderRequest request) {
        OrderResponse response = createOrderUseCase.createOrder(request);
        URI location = ServletUriComponentsBuilder
            .fromCurrentRequest()
            .path("/{id}")
            .buildAndExpand(response.id())
            .toUri();
        return ResponseEntity.created(location).body(response);
    }
}
Example 7: Domain Tests (No Spring Context)
class OrderTest {
    @Test
    void shouldCreateOrderWithValidItems() {
        List<OrderItem> items = List.of(
            new OrderItem(new ProductId(UUID.randomUUID()), 2, new Money("10.00", EUR))
        );

        Order order = Order.create(items);

        assertThat(order.getStatus()).isEqualTo(OrderStatus.PENDING);
        assertThat(order.getDomainEvents()).hasSize(1);
    }
}
Example 8: Application Tests (Unit with Mocks)
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
    @Mock OrderRepository orderRepository;
    @Mock PaymentGateway paymentGateway;
    @Mock DomainEventPublisher eventPublisher;

    @InjectMocks OrderService orderService;

    @Test
    void shouldCreateAndConfirmOrder() {
        when(paymentGateway.charge(any())).thenReturn(new PaymentResult(true, "tx-123"));
        when(orderRepository.save(any())).thenAnswer(i -> i.getArgument(0));

        OrderResponse response = orderService.createOrder(createRequest());

        assertThat(response.status()).isEqualTo(OrderStatus.CONFIRMED);
        verify(eventPublisher).publish(any(OrderCreatedEvent.class));
    }
}
Best Practices
  • Domain purity: Keep the domain layer free of Spring annotations and framework imports — zero dependencies on outer layers
  • Feature-based packages: Organize by business capability (order/, customer/) rather than technical role, with each feature containing all four layers
  • Immutable value objects: Use Java records for value objects with built-in validation in compactors — immutable by design
  • Rich domain models: Place business logic in entities and aggregates, not in application services — services orchestrate, entities encapsulate
  • Always map: Separate JPA entities from domain models using MapStruct or manual mappers; never expose JPA entities outside infrastructure
  • Domain events for decoupling: Use DomainEventPublisher to decouple cross-aggregate side effects instead of direct service calls
  • Transaction boundaries in application layer: Place @Transactional only on application services, never on domain classes
  • Factory methods for invariants: Use Entity.create(...) static methods to enforce invariants at construction time
  • Enforce with ArchUnit: Add ArchUnit tests in the test suite to verify no Spring or infrastructure imports reach the domain layer
  • Strongly-typed IDs: Use record OrderId(UUID value) instead of raw UUID to prevent ID confusion across aggregates
Constraints and Warnings
Critical Constraints
  • Domain Layer Purity: Never add Spring annotations (@Entity, @Autowired, @Component) to domain classes
  • Dependency Direction: Dependencies must only point inward (domain <- application <- infrastructure/adapter)
  • Framework Isolation: All framework-specific code must stay in infrastructure and adapter layers
Common Pitfalls to Avoid
  • Anemic Domain Model: Entities with only getters/setters, logic in services - place business logic in entities
  • Framework Leakage: @Entity, @Autowired in domain layer - keep domain framework-free
  • Lazy Loading Issues: Exposing JPA entities through domain model - use mappers to convert
  • Circular Dependencies: Between domain aggregates - use IDs instead of direct references
  • Missing Domain Events: Direct service calls instead of events for cross-aggregate communication
  • Repository Misplacement: Defining repository interfaces in infrastructure - they belong in domain
  • DTO Bypass: Exposing domain entities directly in API - always use DTOs for external contracts
Performance Considerations
  • Separate JPA entities from domain models to avoid lazy loading issues
  • Use read-only transactions for query operations
  • Consider CQRS for complex read/write scenarios
References
  • references/java-clean-architecture.md - Java-specific patterns (records, sealed classes, strongly-typed IDs)
  • references/spring-boot-implementation.md - Spring Boot integration (DI patterns, JPA mapping, transaction management)
按 MIT 许可原样转载,未经改动 · 在 GitHub 查看 →

评论

登录即可评论;带「已验证安装」的,是发布者名下有本店的安装或持有记录。