スキル一覧に戻る
xinqilin

clean-architecture

by xinqilin

0🍴 0📅 2026年1月22日
GitHubで見るManusで実行

SKILL.md


name: clean-architecture description: Clean Architecture design guide for Spring Boot. Use when reviewing code architecture, designing solutions, discussing layer separation, dependency rules, or project structure. Applies Uncle Bob's Clean Architecture principles.

Clean Architecture for Spring Boot

IMPORTANT: All output must be in Traditional Chinese.

Core Principle: The Dependency Rule

Dependencies point inward only. Inner layers know nothing about outer layers.

┌─────────────────────────────────────────────┐
│              Presentation                    │  ← Controllers, DTOs
│   ┌─────────────────────────────────────┐   │
│   │          Infrastructure              │   │  ← Repositories Impl, External APIs
│   │   ┌─────────────────────────────┐   │   │
│   │   │        Application           │   │   │  ← Use Cases, Ports
│   │   │   ┌─────────────────────┐   │   │   │
│   │   │   │      Domain          │   │   │   │  ← Entities, Value Objects
│   │   │   └─────────────────────┘   │   │   │
│   │   └─────────────────────────────┘   │   │
│   └─────────────────────────────────────┘   │
└─────────────────────────────────────────────┘

Layer Responsibilities

1. Domain Layer (Core)

The heart of the application. No framework dependencies.

// Entity - business identity
public class Order {
    private OrderId id;
    private CustomerId customerId;
    private Money totalAmount;
    private OrderStatus status;

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

// Value Object - immutable, equality by value
public record Money(BigDecimal amount, Currency currency) {
    public Money {
        if (amount.compareTo(BigDecimal.ZERO) < 0) {
            throw new IllegalArgumentException("Amount cannot be negative");
        }
    }

    public Money add(Money other) {
        validateSameCurrency(other);
        return new Money(this.amount.add(other.amount), this.currency);
    }
}

2. Application Layer

Orchestrates use cases. Defines Ports (interfaces).

// Input Port - what the application can do
public interface CreateOrderUseCase {
    OrderId execute(CreateOrderCommand command);
}

// Output Port - what the application needs
public interface OrderRepository {
    void save(Order order);
    Optional<Order> findById(OrderId id);
}

// Use Case implementation
@Service
@Transactional
public class CreateOrderService implements CreateOrderUseCase {
    private final OrderRepository orderRepository;
    private final CustomerRepository customerRepository;

    @Override
    public OrderId execute(CreateOrderCommand command) {
        Customer customer = customerRepository.findById(command.customerId())
            .orElseThrow(() -> new CustomerNotFoundException(command.customerId()));

        Order order = Order.create(customer, command.items());
        orderRepository.save(order);

        return order.getId();
    }
}

3. Infrastructure Layer

Implements ports. Contains framework-specific code.

// Repository implementation (Adapter)
@Repository
public class JpaOrderRepository implements OrderRepository {
    private final OrderJpaRepository jpaRepository;
    private final OrderMapper mapper;

    @Override
    public void save(Order order) {
        OrderEntity entity = mapper.toEntity(order);
        jpaRepository.save(entity);
    }

    @Override
    public Optional<Order> findById(OrderId id) {
        return jpaRepository.findById(id.value())
            .map(mapper::toDomain);
    }
}

4. Presentation Layer

Handles HTTP requests. Maps between DTOs and domain.

@RestController
@RequestMapping("/api/orders")
public class OrderController {
    private final CreateOrderUseCase createOrderUseCase;

    @PostMapping
    public ResponseEntity<OrderResponse> createOrder(@Valid @RequestBody CreateOrderRequest request) {
        CreateOrderCommand command = request.toCommand();
        OrderId orderId = createOrderUseCase.execute(command);
        return ResponseEntity.created(URI.create("/api/orders/" + orderId.value()))
            .body(new OrderResponse(orderId.value()));
    }
}

Spring Boot Project Structure

src/main/java/com/example/order/
├── domain/
│   ├── model/
│   │   ├── Order.java
│   │   ├── OrderId.java
│   │   ├── OrderStatus.java
│   │   └── Money.java
│   ├── service/
│   │   └── OrderDomainService.java
│   └── exception/
│       └── IllegalOrderStateException.java
├── application/
│   ├── port/
│   │   ├── in/
│   │   │   └── CreateOrderUseCase.java
│   │   └── out/
│   │       └── OrderRepository.java
│   ├── service/
│   │   └── CreateOrderService.java
│   └── dto/
│       └── CreateOrderCommand.java
├── infrastructure/
│   ├── persistence/
│   │   ├── entity/
│   │   │   └── OrderEntity.java
│   │   ├── repository/
│   │   │   ├── OrderJpaRepository.java
│   │   │   └── JpaOrderRepository.java
│   │   └── mapper/
│   │       └── OrderMapper.java
│   └── config/
│       └── PersistenceConfig.java
└── presentation/
    ├── controller/
    │   └── OrderController.java
    ├── request/
    │   └── CreateOrderRequest.java
    └── response/
        └── OrderResponse.java

Code Review Checklist

CheckCorrectViolation
Domain has no Spring annotationspublic class Order@Entity public class Order
Controller has no business logicDelegates to UseCaseContains validation/calculation
UseCase depends on ports onlyOrderRepository (interface)JpaOrderRepository (impl)
DTOs don't leak to domainMaps to Command/EntityPasses DTO to UseCase
Entities have behaviororder.confirm()Anemic model with only getters

Common Anti-Patterns

1. Framework Coupling in Domain

// BAD - Domain depends on JPA
@Entity
public class Order {
    @Id @GeneratedValue
    private Long id;
}

// GOOD - Domain is pure
public class Order {
    private OrderId id;
}
// Separate JPA entity in infrastructure

2. Fat Controllers

// BAD - Business logic in controller
@PostMapping
public Order createOrder(@RequestBody Request req) {
    if (req.getItems().isEmpty()) throw new BadRequestException();
    Order order = new Order();
    order.setCustomerId(req.getCustomerId());
    order.setTotal(calculateTotal(req.getItems())); // Business logic!
    return orderRepository.save(order);
}

// GOOD - Delegate to use case
@PostMapping
public OrderResponse createOrder(@RequestBody CreateOrderRequest req) {
    OrderId id = createOrderUseCase.execute(req.toCommand());
    return new OrderResponse(id);
}

3. Anemic Domain Model

// BAD - No behavior, just data holder
public class Order {
    private OrderStatus status;
    public void setStatus(OrderStatus s) { this.status = s; }
}
// Service does all the work
orderService.confirmOrder(order);

// GOOD - Encapsulated behavior
public class Order {
    public void confirm() {
        validateCanConfirm();
        this.status = OrderStatus.CONFIRMED;
    }
}

When to Apply

  • New feature development requiring clear boundaries
  • Refactoring legacy code with tangled dependencies
  • Code review for architectural compliance
  • Designing microservice boundaries

Additional Resources

For detailed guidance:

  • references/layer-dependencies.md - Dependency rules and violation examples
  • references/spring-boot-implementation.md - Complete project templates
  • references/testing-strategy.md - Testing each layer

スコア

総合スコア

55/100

リポジトリの品質指標に基づく評価

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

+10
説明文

100文字以上の説明がある

0/10
人気

GitHub Stars 100以上

0/15
最近の活動

3ヶ月以内に更新がある

0/10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

0/5
タグ

1つ以上のタグが設定されている

0/5

レビュー

💬

レビュー機能は近日公開予定です