architecture-review

varubogu's avatarfrom varubogu

Flequit のクリーンアーキテクチャ、レイヤードアーキテクチャへの準拠をチェックします。レイヤー間の依存関係、crate 間のアクセス制御、Store と Service の分離、アンチパターンの検出などのアーキテクチャレビューに使用します。

⭐0stars🔀0forks📁View on GitHub🕐Updated Dec 28, 2025

When & Why to Use This Skill

This Claude skill automates architectural compliance checks for the Flequit project, ensuring strict adherence to Clean Architecture and Layered Architecture principles. It proactively identifies structural issues such as improper crate dependencies in Rust, tight coupling between Stores and UI Services in frontend code, and common anti-patterns like circular references or layer bypassing to maintain a highly maintainable and scalable codebase.

Use Cases

  • Automated Pull Request Reviews: Automatically verify that new code changes respect the defined crate hierarchy (Main -> Core -> Storage) and prevent unauthorized cross-layer access.
  • Frontend State Management Audit: Ensure frontend components and Stores remain decoupled by validating that side effects and backend communications are handled exclusively within the UI Service layer.
  • Technical Debt & Anti-Pattern Detection: Scan the codebase to detect and flag circular dependencies, 'God files' exceeding 200 lines, and instances where the UI layer directly invokes backend commands.
  • Architectural Refactoring Guidance: Provide real-time feedback during refactoring to ensure that Facades, Services, and Repositories are correctly implemented according to the project's structural rules.
namearchitecture-review
descriptionFlequit のクリーンアーキテクチャ、レイヤードアーキテクチャへの準拠をチェックします。レイヤー間の依存関係、crate 間のアクセス制御、Store と Service の分離、アンチパターンの検出などのアーキテクチャレビューに使用します。
modelsonnet

Architecture Review Skill

Flequit プロジェクトのアーキテクチャ準拠をチェックするスキルです。

アーキテクチャ概要

バックエンド: クリーンアーキテクチャ(Crate分離版)

Main Crate (flequit)
├── Application Layer (commands, controllers, events)
    ↓
flequit-core Crate
├── Domain Layer (facade → services)
    ↓
flequit-storage Crate
├── Data Access Layer (repositories)

フロントエンド: レイヤードアーキテクチャ

UI Layer (Components)
    ↓
UI Service Layer (Orchestration, Side Effects)
    ↓
Store Layer (Reactive State)
    ↓
Backend Communication (Tauri Commands)

バックエンド: Crate 間アクセス制御

ルール

  1. Main Crate (flequit): flequit-core のみ参照可能
  2. flequit-core: flequit-storage のみ参照可能
  3. flequit-storage: 外部 crate 参照不可(完全独立)

Crate 内アクセス制御

Main Crate (flequit)

// ✅ OK: Facade を参照
use flequit_core::facades::task;

#[tauri::command]
pub async fn get_tasks(project_id: String) -> Result<Vec<Task>, String> {
    task::get_tasks(&repositories, &project_id).await
}

// ❌ NG: Service を直接参照
use flequit_core::services::task_service; // Facade 経由で呼ぶべき

// ❌ NG: Repository を直接参照
use flequit_storage::repositories::task_repository; // Facade 経由で呼ぶべき

flequit-core Crate

// ✅ OK: Facade から Service を参照
// facades/task.rs
use crate::services::task_service;

pub async fn create_task(...) -> Result<Task, String> {
    task_service::create(&repositories, ...).await
}

// ✅ OK: Service から Repository を参照
// services/task_service.rs
use flequit_storage::repositories::task_repository;

pub async fn get_tasks(repositories: &R, project_id: &str) -> Result<Vec<Task>> {
    repositories.task().get_all(project_id).await
}

// ❌ NG: Service から Facade を参照
use crate::facades::task; // 循環参照

// ❌ NG: Facade から他の Facade を参照
use crate::facades::project; // Facade は他の Facade を参照しない

flequit-storage Crate

// ✅ OK: Repository 内での参照
use crate::models::task::Task;
use crate::errors::RepositoryError;

// ❌ NG: 外部 crate の参照
use flequit_core::services::...; // Storage は独立している

フロントエンド: レイヤー分離チェック

Store と UI Service の分離

Store: 純粋な状態管理のみ

// ✅ OK: Reactive state のみ
export class TaskStore {
  private tasks = $state<Task[]>([]);

  get allTasks() {
    return this.tasks;
  }

  get completedTasks() {
    return $derived(this.tasks.filter(t => t.status === 'completed'));
  }

  // ✅ OK: 状態の更新メソッド
  setTasks(tasks: Task[]) {
    this.tasks = tasks;
  }

  addTask(task: Task) {
    this.tasks.push(task);
  }
}

// ❌ NG: Store が直接 invoke を呼ぶ
export class TaskStore {
  async loadTasks() {
    const tasks = await invoke('get_tasks'); // Service でやるべき
    this.tasks = tasks;
  }
}

// ❌ NG: Store が Service を import
import { taskService } from '$lib/services/task-service'; // UI Service でやるべき

UI Service: オーケストレーションと副作用

// ✅ OK: Backend 通信と Store 更新の連携
export class TaskUIService {
  constructor(private store: TaskStore) {}

  async loadTasks(projectId: string): Promise<boolean> {
    try {
      const tasks = await invoke<Task[]>('get_tasks', { projectId });
      this.store.setTasks(tasks);
      return true;
    } catch (error) {
      console.error('Failed to load tasks:', error);
      return false;
    }
  }

  async createTask(request: CreateTaskRequest): Promise<boolean> {
    try {
      const task = await invoke<Task>('create_task', request);
      this.store.addTask(task);
      return true;
    } catch (error) {
      console.error('Failed to create task:', error);
      return false;
    }
  }
}

// ❌ NG: UI Service が状態を直接持つ
export class TaskUIService {
  private tasks = $state<Task[]>([]); // Store でやるべき
}

コンポーネント: UI のみ

// ✅ OK: UI Service 経由で操作
<script lang="ts">
  import { taskUIService } from '$lib/services/ui/task-ui-service';
  import { taskStore } from '$lib/stores/task.svelte';

  async function handleCreate() {
    const success = await taskUIService.createTask(formData);
    if (success) {
      // UI 更新
    }
  }
</script>

{#each taskStore.allTasks as task}
  <TaskItem {task} />
{/each}

// ❌ NG: コンポーネントが直接 invoke を呼ぶ
<script lang="ts">
  import { invoke } from '@tauri-apps/api/tauri';

  async function handleCreate() {
    await invoke('create_task', formData); // UI Service でやるべき
  }
</script>

アンチパターン検出

1. 循環参照

// ❌ NG: Service が Facade を参照
// services/task_service.rs
use crate::facades::task; // 循環参照!

// ✅ OK: Facade が Service を参照
// facades/task.rs
use crate::services::task_service;

2. レイヤー飛び越し

// ❌ NG: Command が直接 Repository を参照
use flequit_storage::repositories::task_repository;

#[tauri::command]
pub async fn get_tasks() -> Result<Vec<Task>, String> {
    task_repository::get_all().await // Facade 経由で呼ぶべき
}

// ✅ OK: Command が Facade を参照
use flequit_core::facades::task;

#[tauri::command]
pub async fn get_tasks() -> Result<Vec<Task>, String> {
    task::get_all(&repositories).await
}

3. Store に副作用

// ❌ NG: Store が副作用を持つ
export class TaskStore {
  async saveToBackend() {
    await invoke('save_tasks', { tasks: this.tasks }); // UI Service でやるべき
  }

  async syncWithServer() {
    const serverTasks = await fetch('/api/tasks'); // UI Service でやるべき
    this.tasks = await serverTasks.json();
  }
}

// ✅ OK: Store は純粋な状態管理のみ
export class TaskStore {
  private tasks = $state<Task[]>([]);

  setTasks(tasks: Task[]) {
    this.tasks = tasks;
  }
}

4. プロキシだけの Service

// ❌ NG: 単なるプロキシ(価値なし)
export class TaskService {
  async getTasks(projectId: string) {
    return await invoke('get_tasks', { projectId }); // これだけなら不要
  }
}

// ✅ OK: オーケストレーション + 状態管理
export class TaskUIService {
  constructor(
    private taskStore: TaskStore,
    private projectStore: ProjectStore
  ) {}

  async loadTasksForCurrentProject(): Promise<boolean> {
    const project = this.projectStore.current;
    if (!project) return false;

    try {
      const tasks = await invoke<Task[]>('get_tasks', {
        projectId: project.id
      });
      this.taskStore.setTasks(tasks);
      return true;
    } catch (error) {
      console.error('Failed to load tasks:', error);
      return false;
    }
  }
}

ファイルサイズチェック

ルール

  • 200行以上: 必須分割対象(テストコード除外)
  • 100行以上: 分割を検討
  • 例外: 設定ファイル、データ定義

分割方法

// ❌ NG: 200行を超える巨大ファイル
// task-service.ts (300行)

// ✅ OK: 機能ごとに分割
// task-ui-service.ts (150行)
// task-filter-service.ts (80行)
// task-sort-service.ts (70行)

チェックリスト

バックエンド

  • Command は Facade のみ参照している
  • Facade は Service のみ参照している
  • Service は Repository を参照している
  • Repository は外部 crate を参照していない
  • 循環参照が存在しない
  • トランザクションは Facade レイヤーで管理している

フロントエンド

  • Store は純粋な状態管理のみを行っている
  • UI Service がオーケストレーションを担当している
  • コンポーネントは UI Service 経由で操作している
  • Store が直接 invoke を呼んでいない
  • Store が Service を import していない
  • 単なるプロキシ Service が存在しない

共通

  • ファイルサイズが200行以下(テスト除く)
  • 単一責任原則に従っている
  • 命名規則に従っている(camelCase/snake_case)

関連ドキュメント

詳細は以下のドキュメントを参照:

  • docs/en/develop/design/architecture.md - アーキテクチャ全体
  • docs/en/develop/design/backend-tauri/rust-guidelines.md - Rust 設計ガイドライン
  • docs/en/develop/design/frontend/layers.md - フロントエンドレイヤー
  • docs/en/develop/design/frontend/store-and-service-architecture.md - Store と Service 分離
  • docs/en/develop/design/frontend/anti-patterns.md - アンチパターン
  • docs/en/develop/rules/file-structure.md - ファイル構造ルール