hexagonal-architecture-layers-java について
Javaサービスの設計やリファクタリングに向けた、ヘキサゴナルアーキテクチャのスキルです。ポートとアダプターを使い、ドメイン、アプリケーション、インフラの分離と依存方向を整理します。
主な機能
- 01フレームワークやI/Oに依存しないドメイン
- 02アプリケーション層のユースケースとポート
- 03ポートを実装するインフラアダプター
- 04依存方向とモジュール境界の明確化
活用シーン
- 01レイヤー化したJavaサービスの設計
- 02ドメインを分離するSpringコードのリファクタリング
- 03RESTやメッセージングのアダプター対応
- 04永続化処理とドメインモデルの分離
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ gentleman-programming/ gentleman-skills --skill hexagonal-architecture-layers-javaインストール後、Claude Code で/hexagonal-architecture-layers-javaを入力して読み込まれたか確認します。
詳細
使用するタイミング
以下の場合に、このスキルを読み込みます。
- クリーンでテスト可能なレイヤーを備えた新しい Java サービスを設計する
- ドメインをフレームワークから分離するために Spring コードをリファクタリングする
- 複数のアダプター(REST + メッセージング、JPA + Mongo)に対応する
- 依存関係の方向と明確なモジュール境界を徹底する
重要なパターン
パターン 1:ドメインを純粋に保つ
ドメインには、フレームワークのアノテーション、永続化に関する処理、I/O を含めません。
パターン 2:アプリケーションがオーケストレーションする
アプリケーションはユースケースとポートを定義し、ドメインロジックを呼び出して、I/O をポートに委譲します。
パターン 3:インフラストラクチャが適応する
インフラストラクチャはポートを実装し、アダプター(コントローラー、リポジトリー、クライアント)を接続します。
コード例
例 1:ドメインモデル + 出力ポート
package com.acme.order.domain;
public record OrderId(String value) { }
public final class Order {
private final OrderId id;
private final Money total;
public Order(OrderId id, Money total) {
this.id = id;
this.total = total;
}
public OrderId id() { return id; }
public Money total() { return total; }
}
package com.acme.order.application.port;
import com.acme.order.domain.Order;
import com.acme.order.domain.OrderId;
public interface OrderRepositoryPort {
OrderId nextId();
void save(Order order);
}
例 2:アプリケーションのユースケース + 入力ポート
package com.acme.order.application.usecase;
import com.acme.order.application.port.OrderRepositoryPort;
import com.acme.order.domain.Order;
import com.acme.order.domain.OrderId;
import com.acme.order.domain.Money;
public interface PlaceOrderUseCase {
OrderId place(Money total);
}
public final class PlaceOrderService implements PlaceOrderUseCase {
private final OrderRepositoryPort repository;
public PlaceOrderService(OrderRepositoryPort repository) {
this.repository = repository;
}
@Override
public OrderId place(Money total) {
OrderId id = repository.nextId();
Order order = new Order(id, total);
repository.save(order);
return id;
}
}
例 3:インフラストラクチャのアダプター + 接続
package com.acme.order.infrastructure.persistence;
import com.acme.order.application.port.OrderRepositoryPort;
import com.acme.order.domain.Order;
import com.acme.order.domain.OrderId;
import org.springframework.stereotype.Repository;
@Repository
public final class OrderJpaAdapter implements OrderRepositoryPort {
private final SpringOrderRepository repository;
private final OrderMapper mapper;
public OrderJpaAdapter(SpringOrderRepository repository, OrderMapper mapper) {
this.repository = repository;
this.mapper = mapper;
}
@Override
public OrderId nextId() {
return new OrderId(java.util.UUID.randomUUID().toString());
}
@Override
public void save(Order order) {
repository.save(mapper.toEntity(order));
}
}
アンチパターン
禁止:ドメインにフレームワークのアノテーションを配置する
// BAD: domain tied to JPA
@jakarta.persistence.Entity
public class Order {
@jakarta.persistence.Id
private String id;
}
禁止:ドメインからインフラストラクチャを直接呼び出す
// BAD: domain depends on Spring repository
public class Order {
private final SpringOrderRepository repository;
}
クイックリファレンス
| タスク | パターン |
|---|---|
| ドメインデータを永続化する | アプリケーションに出力ポートを定義し、インフラストラクチャで実装する |
| ユースケースを公開する | アプリケーションに入力ポートとサービスを定義する |
| ドメインを純粋に保つ | アノテーション、I/O、フレームワークのインポートを含めない |
リソース
コメント
Markdown 対応