code-structure について
code-structureは、アクションによるオーケストレーションと再利用可能なサービス操作を分離するためのアーキテクチャガイドです。既定の規約がないプロジェクトを対象とし、依存関係と責任の明確化、具体的な利点がある場合に限った共通処理の抽出を重視します。
主な機能
- 01オーケストレーションと共通操作の分離
- 02既存の責任者によるポリシーとトランザクションの管理
- 03組み合わせ可能な機能ブロックの設計
- 04明示的な入力と構造化された結果
- 05呼び出し元ごとの段階的な移行
- 06不要な抽象化の回避
活用シーン
- 01サービス規約のないプロジェクトの構成
- 02アクションファイルから重複処理を抽出
- 03永続化とトランザクションの責任範囲を明確化
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ michaelshimeles/ rstack --skill code-structureインストール後、Claude Code で/code-structureを入力して読み込まれたか確認します。
詳細
サービスレイヤーアーキテクチャ
概要
このコレクションでは、アクションがドメインルールを調整し、サービスが再利用可能な操作を実装する、2層の分離を推奨します。プロジェクトに確立された規約がない場合に使用してください。既存のドメインサービス、リポジトリ、トランザクション境界、フレームワークのパターンがタスクに適合する場合は、それらを維持してください。
これにより、コードの重複、一貫性のない動作、ある経路で修正したバグがほかの経路では修正されないといった問題を防げます。
使用するタイミング
- 複数の呼び出し元が同じ低レベル操作(サンドボックスの作成、メール送信、決済処理)を必要としている
- アクションファイル間で操作ロジックをコピー&ペーストしている
- あるワークフローでのバグ修正が、同じ処理を行うほかのワークフローに反映されない
- 既存のフローと仕組みを共有する新機能を追加する
具体的なメリットがないのに間接性を増やす抽出は避けてください。繰り返しは有用な判断材料ですが、呼び出し元が1つでも、テスト、プロバイダーの分離、トランザクション、複雑なロジックのために境界を設ける価値がある場合があります。
基本パターン
Orchestration Layer (Actions) Service Layer (Shared Mechanics)
├── owns business rules ├── owns reusable operations
├── owns state transitions ├── owns provider/SDK interactions
├── owns auth/ownership checks ├── owns command execution details
├── owns failure classification ├── owns health checks / readiness
├── owns retries / user-facing errors └── returns structured results
└── calls service functions
経験則:
- 「このプロダクトのフローが何を意味するか」→ アクションに残す
- 「この操作を確実に実行する方法」→ サービスレイヤーに移す
クイックリファレンス
| 設計原則 | すること | しないこと |
|---|---|---|
| API の形状 | 組み合わせ可能な機能ブロック | 「すべてを実行する」巨大なメソッド |
| 入出力 | 明示的な依存関係と意味のある結果 | 隠れたグローバル状態や文書化されていない副作用 |
| 移行 | 1つのブロックを抽出し、1つの呼び出し元を置き換えて検証してから、残りを移行する | すべてを一度にリファクタリングする |
| ドメインロジック | プロジェクトで確立された所有者にポリシーとトランザクションを置く | 同じポリシーを呼び出し元ごとに分散させる |
| 抽出のきっかけ | 繰り返される処理、または具体的な境界のメリット | 現時点でメリットのない推測上の抽象化 |
サービス関数の設計
モノリスではなく、機能ブロックとして設計します。
// Good: composable, each caller chooses what to use
createManagedSandbox(...)
prepareRepo(...)
detectPackageManager(...)
installDependencies(...)
runBuildCommand(...)
startSandboxRuntime(...)
各関数は次のようにします。
- 必要なデータをすべて明示的なパラメーターとして受け取る
- 構造化された出力(例:
{ ready, previewUrl, proxyPort })を返す - プロジェクトのアーキテクチャ上、サービスが永続化を担う場合は、データベースまたはリポジトリへのアクセスを注入する。状態の書き込みとトランザクション境界を明示する
- 型付きの結果または文書化された例外によって、失敗を明示する
これにより、呼び出し元はフローごとに厳密な動作か、柔軟な動作かを選択できます。
移行チェックリスト
共有ロジックを抽出する際は、次の手順に従います。
- ポリシー、永続化、トランザクションの既存の所有者を特定する
- 繰り返されている操作、または現時点で具体的なメリットがある境界を特定する
- それらの所有者と整合する、一貫性のある操作を抽出する
- 1つの呼び出し元を置き換える → 検証する → 残りの呼び出し元を置き換える
- 認証、状態遷移、エラー処理、トランザクションの動作を維持する
- 検証を実行する:型チェック、Lint、すべてのフローが引き続き動作することの確認
アンチパターン
| アンチパターン | 問題 |
|---|---|
| 神サービス | 1つの巨大な関数がすべての制御フローを隠してしまう |
| 漏れのあるサービス | 隠れた状態変更や不明確なトランザクションの所有権 |
| 一貫性のない API | 関数ごとに異なる引数形式やエラーの意味が使われている |
| 過剰な抽象化 | 再利用性、テスト容易性、境界を設けるメリットのない間接性 |
例:メールサービス(シンプル)
// emailService.ts — shared mechanics
export async function sendWelcomeEmail(params: { to: string; name: string }) {
const html = `<h1>Welcome ${params.name}</h1>`;
await emailProvider.send(params.to, "Welcome", html);
}
// userSignup.ts — orchestration (owns WHEN to send)
if (user.marketingOptIn) {
await sendWelcomeEmail({ to: user.email, name: user.name });
}
// adminInvite.ts — orchestration (different business rule, same mechanic)
await sendWelcomeEmail({ to: invitee.email, name: invitee.name });
メンタルモデル
Existing architecture? → Follow its boundaries
No convention? → Prefer actions for orchestration and services for operations
Extraction? → Name its concrete benefit before adding another layer
責任を明確にし、依存関係を明示し、副作用を見えるようにします。
コメント
Markdown 対応