clean-architecture は何をしますか?
Java 21+ Spring Boot 3.5+ アプリケーション向けに、クリーンアーキテクチャ、ヘキサゴナルアーキテクチャ(ポート&アダプター)、およびドメイン駆動設計の実装パターンを提供します。階層化アーキテクチャの構築、フレームワークからのドメインロジックの分離、ポートとアダプターの実装、エンティティ/値オブジェクト/集約の作成、またはモノリシックコードベースのテスト容易性と保守性向上のためのリファクタリングに使用します。
概要
このスキルは、Java 21+ および Spring Boot 3.5+ を使用する開発者を対象としており、堅牢でテスタブル、かつ保守可能なアプリケーションを構築するためのアーキテクチャパターンを提供します。これには以下のパターンが含まれます:
- クリーンアーキテクチャ: 関心の分離と依存関係のルールに焦点を当て、レイヤーの依存関係が内側に向かうようにします。
- ヘキサゴナルアーキテクチャ(ポート&アダプター): ドメインロジックの内外との通信に明確なポート(インターフェース)とアダプター(実装)を定義します。
- ドメイン駆動設計 (DDD): コアドメインに重点を置き、エンティティ、値オブジェクト、集約、ドメインイベントなどの戦術的パターンを使用します。
実装戦略
1. レイヤー構造
Maven/Gradle モジュールまたは Java パッケージを使用して、明確なアーキテクチャバウンダリを定義します:
com.example.myapp
├── domain # エンティティ、値オブジェクト、集約、ドメインイベント、リポジトリインターフェース
├── application # アプリケーションサービス、ユースケースインターフェース、DTO
├── infrastructure # 永続化(JPA)、メッセージング(Kafka/RabbitMQ)、外部 API クライアント
└── interfaces # REST コントローラー、GraphQL リゾルバー、gRPC サービス
2. 依存関係のルール
- ドメイン: 外部依存関係なし。純粋な Java ロジックのみ。
- アプリケーション:
domainに依存、フレームワーク固有のアノテーションは使用しない。 - インフラストラクチャ:
domainとapplicationに依存。 - インターフェース:
applicationとdomainに依存。
3. ポートとアダプター
ポート(インターフェース) はドメインレイヤーで定義します:
public interface OrderRepository {
Optional<Order> findById(OrderId id);
void save(Order order);
}
アダプター(実装) はインフラストラクチャレイヤーで定義します:
@Repository
public class JpaOrderRepository implements OrderRepository {
private final JpaOrderEntityRepository jpaRepository;
private final OrderMapper mapper;
public JpaOrderRepository(JpaOrderEntityRepository jpaRepository, OrderMapper mapper) {
this.jpaRepository = jpaRepository;
this.mapper = mapper;
}
@Override
public Optional<Order> findById(OrderId id) {
return jpaRepository.findById(id.value()).map(mapper::toDomain);
}
@Override
public void save(Order order) {
jpaRepository.save(mapper.toEntity(order));
}
}
4. アプリケーションサービス
アプリケーションサービスはユースケースを調整し、トランザクションとセキュリティを管理します:
@Service
@Transactional
public class CreateOrderService {
private final OrderRepository orderRepository;
private final PaymentService paymentService;
private final EventPublisher eventPublisher;
public CreateOrderService(OrderRepository orderRepository, PaymentService paymentService, EventPublisher eventPublisher) {
this.orderRepository = orderRepository;
this.paymentService = paymentService;
this.eventPublisher = eventPublisher;
}
public CreateOrderResponse execute(CreateOrderCommand command) {
Order order = Order.create(command.customerId(), command.items());
PaymentResult payment = paymentService.charge(order.total());
order.confirmPayment(payment.transactionId());
orderRepository.save(order);
eventPublisher.publish(new OrderCreatedEvent(order.id()));
return new CreateOrderResponse(order.id().value(), order.status());
}
}
5. ドメインオブジェクト
集約ルート: 一貫性とカプセル化を保証します:
public class Order {
private final OrderId id;
private OrderStatus status;
private final List<OrderItem> items;
private Money total;
private Order(OrderId id, List<OrderItem> items) {
this.id = id;
this.status = OrderStatus.PENDING;
this.items = Collections.unmodifiableList(items);
this.total = items.stream().map(OrderItem::subtotal).reduce(Money.ZERO, Money::add);
}
public static Order create(CustomerId customerId, List<OrderItem> items) {
return new Order(OrderId.generate(), items);
}
public void confirmPayment(TransactionId transactionId) {
if (status != OrderStatus.PENDING) {
throw new IllegalStateException("Order must be in PENDING status to confirm payment");
}
this.status = OrderStatus.CONFIRMED;
}
// ゲッター...
}
値オブジェクト: 不変で自己検証可能:
public record OrderId(UUID value) {
public OrderId {
Objects.requireNonNull(value, "OrderId value must not be null");
}
public static OrderId generate() {
return new OrderId(UUID.randomUUID());
}
public static OrderId fromString(String value) {
return new OrderId(UUID.fromString(value));
}
}
テスト戦略
- 単体テスト: Mock を使用してドメインオブジェクトとアプリケーションサービスをテストします。
- 統合テスト: 実際のデータベース(Testcontainers を使用)とインフラストラクチャアダプターをテストします。
- アーキテクチャテスト: ArchUnit を使用してアーキテクチャルールを強制します(例: "ドメインレイヤーが Spring に依存してはならない")。
アーキテクチャテストの例 (ArchUnit)
@AnalyzeClasses(packages = "com.example.myapp")
class ArchitectureTest {
@ArchTest
static final ArchRule domain_should_not_depend_on_infrastructure =
classes()
.that().resideInAPackage("..domain..")
.should().onlyDependOnClassesThat().resideInAnyPackage("..domain..", "java..");
@ArchTest
static final ArchRule application_should_not_depend_on_spring =
classes()
.that().resideInAPackage("..application..")
.should().onlyDependOnClassesThat().resideInAnyPackage("..application..", "..domain..", "java..");
}
制限事項
- Java 21+ および Spring Boot 3.5+ 専用に設計されています。
- インフラストラクチャ層で Jakarta EE を使用する必要があります(Spring Boot 3.x 以前の javax ではありません)。
- DDD の戦術的パターンに重点を置いており、戦略的設計(境界づけられたコンテキスト、コンテキストマッピングなど)は対象外です。
- CI/CD パイプライン、インフラストラクチャのプロビジョニング、またはクラウドへのデプロイの自動化は含まれません。
- 高度なパターン(例: CQRS、イベントソーシング、Saga)は対象範囲外です。
- Kotlin または他の JVM 言語は現在サポートされていません。
サポートされているプラットフォーム
- Codex CLI (Recommended)
- Cline
- VS Code