← スキル一覧に戻る

clean-architecture
by xinqilin
⭐ 0🍴 0📅 2026年1月22日
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
| Check | Correct | Violation |
|---|---|---|
| Domain has no Spring annotations | public class Order | @Entity public class Order |
| Controller has no business logic | Delegates to UseCase | Contains validation/calculation |
| UseCase depends on ports only | OrderRepository (interface) | JpaOrderRepository (impl) |
| DTOs don't leak to domain | Maps to Command/Entity | Passes DTO to UseCase |
| Entities have behavior | order.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
レビュー
💬
レビュー機能は近日公開予定です