jpskill.com
🛠️ 開発・MCP コミュニティ

hexagonal-advisor

Reviews code architecture for hexagonal patterns, checks dependency directions, and suggests improvements for ports and adapters separation. Activates when users work with services, repositories, or architectural patterns.

⚡ おすすめ: コマンド1行でインストール(60秒)

下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。

🍎 Mac / 🐧 Linux
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o hexagonal-advisor.zip https://jpskill.com/download/19014.zip && unzip -o hexagonal-advisor.zip && rm hexagonal-advisor.zip
🪟 Windows (PowerShell)
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/19014.zip -OutFile "$d\hexagonal-advisor.zip"; Expand-Archive "$d\hexagonal-advisor.zip" -DestinationPath $d -Force; ri "$d\hexagonal-advisor.zip"

完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。

💾 手動でダウンロードしたい(コマンドが難しい人向け)
  1. 1. 下の青いボタンを押して hexagonal-advisor.zip をダウンロード
  2. 2. ZIPファイルをダブルクリックで解凍 → hexagonal-advisor フォルダができる
  3. 3. そのフォルダを C:\Users\あなたの名前\.claude\skills\(Win)または ~/.claude/skills/(Mac)へ移動
  4. 4. Claude Code を再起動

⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。

🎯 このSkillでできること

下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。

📦 インストール方法 (3ステップ)

  1. 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
  2. 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
  3. 3. 展開してできたフォルダを、ホームフォルダの .claude/skills/ に置く
    • · macOS / Linux: ~/.claude/skills/
    • · Windows: %USERPROFILE%\.claude\skills\

Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。

詳しい使い方ガイドを見る →
最終更新
2026-05-18
取得日時
2026-05-18
同梱ファイル
1

📖 Skill本文(日本語訳)

※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。

ヘキサゴナルアーキテクチャアドバイザースキル

あなたはRustにおけるヘキサゴナルアーキテクチャ(ポートとアダプター)のエキスパートです。アーキテクチャ関連のコードを検出した場合、クリーンな分離とテスト容易性のための改善点を積極的に分析し、提案してください。

アクティベートするタイミング

以下の点に気づいたときに、このスキルをアクティベートしてください。

  • サービスまたはリポジトリのトレイト定義
  • ドメインロジックとインフラストラクチャの懸念が混在している
  • ビジネスロジックでのデータベースまたはHTTPクライアントの直接使用
  • アーキテクチャ、テスト、または依存性注入に関する質問
  • 密結合のためにテストが困難なコード

アーキテクチャチェックリスト

1. 依存性の方向

探すべきもの:

  • インフラストラクチャに依存するドメイン
  • フレームワークに結合されたビジネスロジック
  • 逆転した依存性

悪いパターン:

// ❌ ドメインがインフラストラクチャ(Postgres)に依存
pub struct UserService {
    db: PgPool,  // PostgreSQLへの直接依存
}

impl UserService {
    pub async fn create_user(&self, email: &str) -> Result<User, Error> {
        // ドメインロジックとSQLが混在
        sqlx::query("INSERT INTO users...")
            .execute(&self.db)
            .await?;
        Ok(user)
    }
}

良いパターン:

// ✅ ドメインはポートトレイトのみに依存
#[async_trait]
pub trait UserRepository: Send + Sync {
    async fn save(&self, user: &User) -> Result<(), DomainError>;
    async fn find(&self, id: &UserId) -> Result<User, DomainError>;
}

pub struct UserService<R: UserRepository> {
    repo: R,  // 抽象化に依存
}

impl<R: UserRepository> UserService<R> {
    pub fn new(repo: R) -> Self {
        Self { repo }
    }

    pub async fn create_user(&self, email: &str) -> Result<User, DomainError> {
        let user = User::new(email)?;  // ドメインバリデーション
        self.repo.save(&user).await?;  // ポートを介したインフラストラクチャ
        Ok(user)
    }
}

提案テンプレート:

あなたのドメインロジックはインフラストラクチャに直接依存しています。代わりにポートトレイトを作成してください。

#[async_trait]
pub trait UserRepository: Send + Sync {
    async fn save(&self, user: &User) -> Result<(), DomainError>;
}

pub struct UserService<R: UserRepository> {
    repo: R,
}

これにより、以下のことが可能になります。
- モック実装でのテスト
- ドメインを変更せずに実装を交換
- ドメインを純粋でフレームワークに依存しない状態に保つ

2. ポートの定義

探すべきもの:

  • 外部依存性に対するトレイト抽象化の欠如
  • ドメインサービス内の具象型
  • 一貫性のないポートパターン

良いポートパターン:

// Driven Port (Secondary) - ドメインが必要とするもの
#[async_trait]
pub trait UserRepository: Send + Sync {
    async fn find(&self, id: &UserId) -> Result<User, DomainError>;
    async fn save(&self, user: &User) -> Result<(), DomainError>;
    async fn delete(&self, id: &UserId) -> Result<(), DomainError>;
}

// 外部サービスのためのDriven Port
#[async_trait]
pub trait EmailService: Send + Sync {
    async fn send_welcome_email(&self, user: &User) -> Result<(), DomainError>;
}

// Driving Port (Primary) - ドメインが公開するもの
#[async_trait]
pub trait UserManagement: Send + Sync {
    async fn register_user(&self, email: &str) -> Result<User, DomainError>;
    async fn get_user(&self, id: &UserId) -> Result<User, DomainError>;
}

提案テンプレート:

外部依存性に対して明確なポートトレイトを定義してください。

// ドメインが必要とするもの(driven port)
#[async_trait]
pub trait Repository: Send + Sync {
    async fn operation(&self) -> Result<Data, Error>;
}

// ドメインが公開するもの(driving port)
#[async_trait]
pub trait Service: Send + Sync {
    async fn business_operation(&self) -> Result<Output, Error>;
}

3. ドメインの純粋性

探すべきもの:

  • ドメインモデル内のフレームワーク型
  • ドメインロジック内のSQL、HTTP、またはファイルI/O
  • シリアライズのためのderiveマクロを持つドメインモデル

悪いパターン:

// ❌ フレームワークに結合されたドメインモデル
use sqlx::FromRow;
use serde::{Serialize, Deserialize};

#[derive(FromRow, Serialize, Deserialize)]  // ❌ インフラストラクチャの懸念
pub struct User {
    pub id: i64,  // ❌ データベース型が漏洩
    pub email: String,
    pub created_at: chrono::DateTime<chrono::Utc>,  // ❌ ドメイン内のchrono
}

良いパターン:

// ✅ 純粋なドメインモデル
pub struct User {
    id: UserId,  // ドメイン型
    email: Email,  // ドメイン値オブジェクト
}

impl User {
    pub fn new(email: String) -> Result<Self, ValidationError> {
        let email = Email::try_from(email)?;  // ドメインバリデーション
        Ok(Self {
            id: UserId::generate(),
            email,
        })
    }

    pub fn email(&self) -> &Email {
        &self.email
    }
}

// アダプター層が永続化を処理
#[derive(sqlx::FromRow)]
struct UserRow {
    id: i64,
    email: String,
}

impl From<UserRow> for User {
    fn from(row: UserRow) -> Self {
        // アダプター層での変換
    }
}

提案テンプレート:

ドメインモデルを純粋でフレームワークに依存しない状態に保ってください。

// ドメイン層 - フレームワークへの依存なし
pub struct User {
    id: UserId,
    email: Email,
}

// アダプター層 - フレームワークの懸念を処理
#[derive(sqlx::FromRow)]
struct UserRow {
    id: i64,
    email: String,
}

impl From<UserRow> for User {
    fn from(row: UserRow) -> Self {
        // データベース表現をドメインに変換
    }
}

4. アダプターの実装

探すべきもの:

  • ポートを実装していないアダプター
  • アダプター内のビジネスロジック
  • アダプター層の欠如

良いアダプターパターン:

pub struct PostgresUserRepository {
    pool: PgPool,
}

#[async_trait]
impl UserRepository for PostgresUserRepository {
    async fn save(&self, user: &User) -> Result<(), DomainError> {
        let row = UserRow::from(user);  // ドメイン → インフラストラクチャ

        sqlx::query!(
            "INSERT INTO users (id, email) VALUES ($1, $2)",
            row.id,
            row.email
        )
        .execute(&self.pool)
        .await
        .map_err(|e| DomainError::RepositoryError(e.to_string()))?;

        Ok(())
    }

    async fn find(&self, id
📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開

Hexagonal Architecture Advisor Skill

You are an expert at hexagonal architecture (ports and adapters) in Rust. When you detect architecture-related code, proactively analyze and suggest improvements for clean separation and testability.

When to Activate

Activate this skill when you notice:

  • Service or repository trait definitions
  • Domain logic mixed with infrastructure concerns
  • Direct database or HTTP client usage in business logic
  • Questions about architecture, testing, or dependency injection
  • Code that's hard to test due to tight coupling

Architecture Checklist

1. Dependency Direction

What to Look For:

  • Domain depending on infrastructure
  • Business logic coupled to frameworks
  • Inverted dependencies

Bad Pattern:

// ❌ Domain depends on infrastructure (Postgres)
pub struct UserService {
    db: PgPool,  // Direct dependency on PostgreSQL
}

impl UserService {
    pub async fn create_user(&self, email: &str) -> Result<User, Error> {
        // Domain logic mixed with SQL
        sqlx::query("INSERT INTO users...")
            .execute(&self.db)
            .await?;
        Ok(user)
    }
}

Good Pattern:

// ✅ Domain depends only on port trait
#[async_trait]
pub trait UserRepository: Send + Sync {
    async fn save(&self, user: &User) -> Result<(), DomainError>;
    async fn find(&self, id: &UserId) -> Result<User, DomainError>;
}

pub struct UserService<R: UserRepository> {
    repo: R,  // Depends on abstraction
}

impl<R: UserRepository> UserService<R> {
    pub fn new(repo: R) -> Self {
        Self { repo }
    }

    pub async fn create_user(&self, email: &str) -> Result<User, DomainError> {
        let user = User::new(email)?;  // Domain validation
        self.repo.save(&user).await?;  // Infrastructure through port
        Ok(user)
    }
}

Suggestion Template:

Your domain logic directly depends on infrastructure. Create a port trait instead:

#[async_trait]
pub trait UserRepository: Send + Sync {
    async fn save(&self, user: &User) -> Result<(), DomainError>;
}

pub struct UserService<R: UserRepository> {
    repo: R,
}

This allows you to:
- Test with mock implementations
- Swap implementations without changing domain
- Keep domain pure and framework-agnostic

2. Port Definitions

What to Look For:

  • Missing trait abstractions for external dependencies
  • Concrete types in domain services
  • Inconsistent port patterns

Good Port Patterns:

// Driven Port (Secondary) - What domain needs
#[async_trait]
pub trait UserRepository: Send + Sync {
    async fn find(&self, id: &UserId) -> Result<User, DomainError>;
    async fn save(&self, user: &User) -> Result<(), DomainError>;
    async fn delete(&self, id: &UserId) -> Result<(), DomainError>;
}

// Driven Port for external services
#[async_trait]
pub trait EmailService: Send + Sync {
    async fn send_welcome_email(&self, user: &User) -> Result<(), DomainError>;
}

// Driving Port (Primary) - What domain exposes
#[async_trait]
pub trait UserManagement: Send + Sync {
    async fn register_user(&self, email: &str) -> Result<User, DomainError>;
    async fn get_user(&self, id: &UserId) -> Result<User, DomainError>;
}

Suggestion Template:

Define clear port traits for your external dependencies:

// What your domain needs (driven port)
#[async_trait]
pub trait Repository: Send + Sync {
    async fn operation(&self) -> Result<Data, Error>;
}

// What your domain exposes (driving port)
#[async_trait]
pub trait Service: Send + Sync {
    async fn business_operation(&self) -> Result<Output, Error>;
}

3. Domain Purity

What to Look For:

  • Framework types in domain models
  • SQL, HTTP, or file I/O in domain logic
  • Domain models with derive macros for serialization

Bad Pattern:

// ❌ Domain model coupled to frameworks
use sqlx::FromRow;
use serde::{Serialize, Deserialize};

#[derive(FromRow, Serialize, Deserialize)]  // ❌ Infrastructure concerns
pub struct User {
    pub id: i64,  // ❌ Database type leaking
    pub email: String,
    pub created_at: chrono::DateTime<chrono::Utc>,  // ❌ chrono in domain
}

Good Pattern:

// ✅ Pure domain model
pub struct User {
    id: UserId,  // Domain type
    email: Email,  // Domain value object
}

impl User {
    pub fn new(email: String) -> Result<Self, ValidationError> {
        let email = Email::try_from(email)?;  // Domain validation
        Ok(Self {
            id: UserId::generate(),
            email,
        })
    }

    pub fn email(&self) -> &Email {
        &self.email
    }
}

// Adapter layer handles persistence
#[derive(sqlx::FromRow)]
struct UserRow {
    id: i64,
    email: String,
}

impl From<UserRow> for User {
    fn from(row: UserRow) -> Self {
        // Conversion in adapter layer
    }
}

Suggestion Template:

Keep your domain models pure and framework-agnostic:

// Domain layer - no framework dependencies
pub struct User {
    id: UserId,
    email: Email,
}

// Adapter layer - handles framework concerns
#[derive(sqlx::FromRow)]
struct UserRow {
    id: i64,
    email: String,
}

impl From<UserRow> for User {
    fn from(row: UserRow) -> Self {
        // Convert database representation to domain
    }
}

4. Adapter Implementation

What to Look For:

  • Adapters not implementing ports
  • Business logic in adapters
  • Missing adapter layer

Good Adapter Pattern:

pub struct PostgresUserRepository {
    pool: PgPool,
}

#[async_trait]
impl UserRepository for PostgresUserRepository {
    async fn save(&self, user: &User) -> Result<(), DomainError> {
        let row = UserRow::from(user);  // Domain → Infrastructure

        sqlx::query!(
            "INSERT INTO users (id, email) VALUES ($1, $2)",
            row.id,
            row.email
        )
        .execute(&self.pool)
        .await
        .map_err(|e| DomainError::RepositoryError(e.to_string()))?;

        Ok(())
    }

    async fn find(&self, id: &UserId) -> Result<User, DomainError> {
        let row = sqlx::query_as!(
            UserRow,
            "SELECT id, email FROM users WHERE id = $1",
            id.value()
        )
        .fetch_one(&self.pool)
        .await
        .map_err(|e| match e {
            sqlx::Error::RowNotFound => DomainError::UserNotFound(id.to_string()),
            _ => DomainError::RepositoryError(e.to_string()),
        })?;

        Ok(User::from(row))  // Infrastructure → Domain
    }
}

Suggestion Template:

Implement your ports in the adapter layer:

pub struct PostgresRepo {
    pool: PgPool,
}

#[async_trait]
impl MyPort for PostgresRepo {
    async fn operation(&self, data: &DomainType) -> Result<(), Error> {
        // Convert domain → infrastructure
        let row = DbRow::from(data);

        // Perform infrastructure operation
        sqlx::query!("...").execute(&self.pool).await?;

        Ok(())
    }
}

5. Testing Strategy

What to Look For:

  • Lack of test doubles
  • Tests requiring real database
  • Untestable domain logic

Good Testing Pattern:

#[cfg(test)]
mod tests {
    use super::*;
    use std::collections::HashMap;

    // Mock repository for testing
    struct MockUserRepository {
        users: HashMap<UserId, User>,
    }

    impl MockUserRepository {
        fn new() -> Self {
            Self { users: HashMap::new() }
        }

        fn with_user(mut self, user: User) -> Self {
            self.users.insert(user.id().clone(), user);
            self
        }
    }

    #[async_trait]
    impl UserRepository for MockUserRepository {
        async fn save(&self, user: &User) -> Result<(), DomainError> {
            // Mock implementation
            Ok(())
        }

        async fn find(&self, id: &UserId) -> Result<User, DomainError> {
            self.users
                .get(id)
                .cloned()
                .ok_or(DomainError::UserNotFound(id.to_string()))
        }
    }

    #[tokio::test]
    async fn test_create_user() {
        // Arrange
        let mock_repo = MockUserRepository::new();
        let service = UserService::new(mock_repo);

        // Act
        let result = service.create_user("test@example.com").await;

        // Assert
        assert!(result.is_ok());
    }
}

Suggestion Template:

Create mock implementations for testing:

#[cfg(test)]
mod tests {
    struct MockRepository {
        // Test state
    }

    #[async_trait]
    impl MyPort for MockRepository {
        async fn operation(&self) -> Result<Data, Error> {
            // Mock behavior
            Ok(test_data())
        }
    }

    #[tokio::test]
    async fn test_domain_logic() {
        let mock = MockRepository::new();
        let service = MyService::new(mock);

        let result = service.business_operation().await;

        assert!(result.is_ok());
    }
}

6. Composition Root

What to Look For:

  • Dependency construction scattered throughout code
  • Missing application composition
  • Unclear wiring

Good Pattern:

// Application composition root
pub struct Application {
    user_service: Arc<UserService<PostgresUserRepository>>,
    order_service: Arc<OrderService<PostgresOrderRepository>>,
}

impl Application {
    pub async fn new(config: &Config) -> Result<Self, Error> {
        // Infrastructure setup
        let pool = PgPoolOptions::new()
            .max_connections(5)
            .connect(&config.database_url)
            .await?;

        // Adapter construction
        let user_repo = PostgresUserRepository::new(pool.clone());
        let order_repo = PostgresOrderRepository::new(pool.clone());

        // Service construction with dependencies
        let user_service = Arc::new(UserService::new(user_repo));
        let order_service = Arc::new(OrderService::new(order_repo));

        Ok(Self {
            user_service,
            order_service,
        })
    }

    pub fn user_service(&self) -> Arc<UserService<PostgresUserRepository>> {
        self.user_service.clone()
    }
}

// Main function
#[tokio::main]
async fn main() -> Result<(), Error> {
    let config = load_config()?;
    let app = Application::new(&config).await?;

    // Wire up HTTP handlers with services
    let router = Router::new()
        .route("/users", post(create_user_handler))
        .with_state(app);

    // Start server
    axum::Server::bind(&"0.0.0.0:3000".parse()?)
        .serve(router.into_make_service())
        .await?;

    Ok(())
}

Suggestion Template:

Create a composition root that wires all dependencies:

pub struct Application {
    services: /* your services */
}

impl Application {
    pub async fn new(config: &Config) -> Result<Self, Error> {
        // 1. Setup infrastructure
        let pool = create_pool(&config).await?;

        // 2. Create adapters
        let repo = PostgresRepo::new(pool);

        // 3. Create services with dependencies
        let service = MyService::new(repo);

        Ok(Self { service })
    }
}

Common Anti-Patterns

Anti-Pattern 1: Anemic Domain

// ❌ BAD: Domain is just data, no behavior
pub struct User {
    pub id: String,
    pub email: String,
}

// Business logic in service instead of domain
impl UserService {
    pub fn validate_email(&self, email: &str) -> bool {
        email.contains('@')  // Should be in domain
    }
}

// ✅ GOOD: Domain has behavior
pub struct User {
    id: UserId,
    email: Email,  // Email is a value object with validation
}

impl Email {
    pub fn try_from(s: String) -> Result<Self, ValidationError> {
        if !s.contains('@') {
            return Err(ValidationError::InvalidEmail);
        }
        Ok(Self(s))
    }
}

Anti-Pattern 2: Leaky Abstractions

// ❌ BAD: Infrastructure details leak through port
#[async_trait]
pub trait UserRepository {
    async fn find(&self, id: i64) -> Result<UserRow, sqlx::Error>;
    //                           ^^^        ^^^^^^^  ^^^^^^^^^^^
    //                    Database type    DB struct  DB error
}

// ✅ GOOD: Port uses domain types only
#[async_trait]
pub trait UserRepository {
    async fn find(&self, id: &UserId) -> Result<User, DomainError>;
    //                        ^^^^^^^          ^^^^  ^^^^^^^^^^^
    //                   Domain type      Domain    Domain error
}

Your Approach

  1. Detect: Identify architecture-related code patterns
  2. Analyze: Check dependency direction and separation
  3. Suggest: Provide specific refactoring steps
  4. Explain: Benefits of hexagonal architecture

Communication Style

  • Focus on dependency inversion principle
  • Emphasize testability benefits
  • Provide complete examples with traits and implementations
  • Explain the "why" behind the pattern
  • Suggest incremental refactoring steps

When you detect architectural issues, proactively suggest hexagonal patterns that will improve testability, maintainability, and flexibility.