Agent skill
validate-architecture
Validate DDD/Clean Architecture compliance in code changes, checking layer violations, transaction patterns, and Row-Level Security
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/data/validate-architecture
SKILL.md
civicship-api アーキテクチャ検証
DDD (ドメイン駆動設計) と Clean Architecture の原則に基づいてコード変更を検証します。
$ARGUMENTS が指定された場合、その特定のパスまたはドメインを検証します。指定がない場合は、現在のブランチの未コミット変更を全て検証します。
重要なアーキテクチャルール
1. レイヤー違反
Resolverレイヤー (controller/resolver.ts)
- ✅ UseCaseのメソッドのみを呼び出す必要がある
- ✅ フィールドリゾルバではDataLoaderを使用する必要がある(N+1問題防止)
- ❌ ビジネスロジックを書いてはいけない
- ❌ Repositoryを直接呼び出してはいけない
UseCaseレイヤー (usecase.ts)
- ✅ Serviceを使ってビジネスフローをオーケストレーションする必要がある
- ✅
ctx.issuer.onlyBelongingCommunity(ctx, async (tx) => {...})でトランザクションを管理する必要がある - ✅ 自ドメインおよび他ドメインのServiceを呼び出せる
- ✅ 結果を返す前にPresenterを呼び出す必要がある
- ❌ 他ドメインのUseCaseを呼び出してはいけない(循環依存の原因)
Serviceレイヤー (service.ts)
- ✅ ビジネスロジックとバリデーションを実装する必要がある
- ✅ Repositoryを呼び出す必要がある
- ✅ 他ドメインのServiceを呼び出せる(読み取り操作のみ)
- ✅
txパラメータを受け取り、Repositoryに渡す必要がある(Service内でif (tx)分岐はしない) - ❌ GraphQL型(
GqlXxx)を返してはいけない - Prisma型のみを返す
Repositoryレイヤー (data/repository.ts)
- ✅ Prismaクエリのみを実行する必要がある
- ✅ Row-Level Security (RLS)のため
ctx.issuerを使用する必要がある - ✅
txパラメータをメソッドタイプに応じて処理する必要がある:- Mutationメソッド(create/update/delete): 必須の
tx: Prisma.TransactionClientを受け取り、直接使用(if (tx)分岐なし) - Queryメソッド トランザクション内外両方で呼ばれる可能性: オプショナルの
tx?: Prisma.TransactionClientを受け取り、if (tx)で分岐
- Mutationメソッド(create/update/delete): 必須の
- ❌ ビジネスロジックを含めてはいけない
Converter (data/converter.ts)
- ✅ 純粋関数である必要がある(GraphQL input → Prisma形式)
- ❌ トランザクションを使用してはいけない
- ❌ データベースクエリを実行してはいけない
Presenter (presenter.ts)
- ✅ 純粋関数である必要がある(Prisma → GraphQL型)
- ❌ ビジネスロジックを含めてはいけない
- ❌ データベースクエリを実行してはいけない
2. トランザクションパターンの検証
パターンA: Mutationメソッド(create/update/delete) - 必須トランザクション
// UseCaseがトランザクションを管理
async managerCreateOpportunity({ input, permission }, ctx) {
return ctx.issuer.onlyBelongingCommunity(ctx, async (tx) => {
const record = await this.service.createOpportunity(ctx, input, permission.communityId, tx);
return OpportunityPresenter.create(record);
});
}
// Serviceがtxを受け取り、Repositoryに渡す
async createOpportunity(ctx, input, communityId, tx) {
const data = this.converter.create(input, communityId);
return await this.repository.create(ctx, data, tx);
}
// Repositoryは必須のtxパラメータを受け取る(分岐なし)
async create(ctx, data, tx: Prisma.TransactionClient) {
return tx.opportunity.create({
data,
select: opportunitySelectDetail,
});
}
パターンB: Queryメソッド - オプショナルトランザクション(柔軟な使用)
// Repositoryはオプショナルのtxパラメータを受け取り、分岐処理
async findCommunityWallet(ctx, communityId, tx?: Prisma.TransactionClient) {
if (tx) {
return tx.wallet.findFirst({
where: { communityId, type: WalletType.COMMUNITY },
select: walletSelectDetail,
});
}
return ctx.issuer.public(ctx, (tx) => {
return tx.wallet.findFirst({
where: { communityId, type: WalletType.COMMUNITY },
select: walletSelectDetail,
});
});
}
違反としてフラグするもの:
- ❌ Serviceレイヤーでトランザクションを開始している(UseCaseのみで行う)
- ❌ ServiceからRepositoryへの
txパラメータ伝播が欠けている - ❌ Mutationメソッドがオプショナルの
tx?を使用している(必須のtxにすべき) - ❌ オプショナルの
tx?を使用しているQueryメソッドでif (tx)分岐がない
3. Row-Level Security (RLS) の検証
必須パターン:
ctx.issuer.public(ctx, tx => {...})- 公開クエリctx.issuer.internal(ctx, tx => {...})- 内部/管理者クエリctx.issuer.onlyBelongingCommunity(ctx, async tx => {...})- ユーザーの所属コミュニティのみ
違反としてフラグするもの:
- ❌
ctx.issuerラッパーなしの直接Prismaクエリ - ❌
communityId分離チェックの欠如 - ❌ RLSメソッドを使用していないRepository
4. GraphQL型の命名規則
正しい:
- 型:
GqlUser,GqlCommunity - 入力:
GqlCreateUserInput,GqlUpdateCommunityInput - ペイロード:
GqlUserPayload,GqlCommunityConnection - Prisma型:
PrismaUser,PrismaCommunity
違反としてフラグするもの:
- ❌
GqlXxx型を返すService - ❌ Service/Repositoryレイヤーで使用されているGraphQL型
- ❌ 誤った命名規則
5. DataLoaderによるN+1問題防止
フィールドリゾルバはDataLoaderを使用する必要がある:
Opportunity: {
community: (parent, _, ctx) => ctx.loaders.community.load(parent.communityId),
createdByUser: (parent, _, ctx) => ctx.loaders.user.load(parent.createdBy)
}
違反としてフラグするもの:
- ❌ データベースへの直接クエリを行うフィールドリゾルバ
- ❌
controller/dataloader.tsのDataLoader定義の欠如
検証プロセス
ステップ1: 検証対象ファイルの特定
$ARGUMENTS が指定された場合:
# 特定のドメインを検証
find src/application/domain/"$ARGUMENTS" -type f \( -name "*.ts" -o -name "*.graphql" \)
指定がない場合:
# 未コミット変更を検証
git status --porcelain | grep -E '^\s*[MA]' | cut -c 4-
ステップ2: ファイルの読み取りと分析
各ファイルに対してレイヤー固有のチェックを実行:
controller/resolver.ts の場合:
- Repositoryの直接呼び出しを検索(例:
repository.find,repository.create) - 全てのフィールドリゾルバが
ctx.loadersを使用していることを確認
usecase.ts の場合:
ctx.issuer.onlyBelongingCommunity(ctx, async (tx) => ...)パターンを確認- 他ドメインのUseCaseの呼び出しをチェック(例:
otherDomainUseCase.doSomething()) - 返却前にPresenterを呼び出していることを確認
service.ts の場合:
Gql型のインポートまたは返却を検索txパラメータが適切に宣言され、渡されていることを確認- 不適切なクロスドメインUseCaseの呼び出しをチェック
data/repository.ts の場合:
- 全てのクエリが
ctx.issuer.public/internal/onlyBelongingCommunityを使用していることを確認 - Mutationメソッド(create/update/delete)が必須の
tx: Prisma.TransactionClientを使用していることをチェック - オプショナルの
tx?を使用するQueryメソッドでif (tx)分岐を実装していることをチェック - ビジネスロジック(複雑な条件文、バリデーション)を検索
data/converter.ts の場合:
txパラメータがないことを確認- Prismaクライアントの使用がないことを確認
presenter.ts の場合:
- 純粋関数のみであることを確認
- Prismaクライアントの使用がないことを確認
schema/*.graphql の場合:
- 型命名が
Gql*規約に従っていることを確認 - 対応するResolverの実装があることをチェック
ステップ3: 検出結果の報告
構造化されたレポートを生成:
# アーキテクチャ検証レポート
## サマリー
- 分析ファイル数: X
- 検出違反数: Y
- 重大問題数: Z
## 違反
### レイヤー違反
- [ ] **ファイル**: `src/application/domain/opportunity/controller/resolver.ts:45`
- **問題**: ResolverがRepositoryを直接呼び出している
- **修正**: UseCaseメソッドを呼び出すように変更
### トランザクションパターンの問題
- [ ] **ファイル**: `src/application/domain/user/service.ts:120`
- **問題**: Repositoryへの `tx` パラメータ伝播が欠けている
- **修正**: `tx` パラメータを追加し、Repositoryメソッドに渡す
### Row-Level Securityの問題
- [ ] **ファイル**: `src/application/domain/community/data/repository.ts:78`
- **問題**: `ctx.issuer` なしの直接Prismaクエリ
- **修正**: クエリを `ctx.issuer.public(ctx, tx => ...)` でラップ
### GraphQL型の違反
- [ ] **ファイル**: `src/application/domain/wallet/service.ts:56`
- **問題**: Serviceが `GqlWallet` 型を返している
- **修正**: Prisma型を返し、Presenterで変換
### DataLoaderの欠如
- [ ] **ファイル**: `src/application/domain/opportunity/controller/resolver.ts:89`
- **問題**: フィールドリゾルバが直接データベースクエリを実行
- **修正**: `ctx.loaders.community.load(parent.communityId)` を使用
## 推奨事項
1. 完全なアーキテクチャガイドラインについては @CLAUDE.md を確認
2. リグレッションがないことを確認するため `pnpm test` を実行
3. Service内のビジネスロジックに対するユニットテストの追加を検討
追加チェック
依存性注入の検証
- 全てのService/Repositoryが
src/application/provider.tsに登録されていることを確認 @injectable()および@inject("ServiceName")デコレータをチェック
GraphQLスキーマの同期
- スキーマ変更後、
pnpm gql:generateが実行されたことを確認 - TypeScriptコンパイルエラーをチェック
セキュリティチェック
- パスワードやシークレットがログに出力されていないこと
- Serviceレイヤーでの入力バリデーション
- パラメータ化クエリ(Prismaが処理)
終了基準
以下の場合にレポートが完成:
- ✅ スコープ内の全ファイルが分析された
- ✅ 各違反にファイルパス、行番号、修正案が含まれる
- ✅ サマリー統計が正確である
- ✅ 推奨事項が実行可能である
参考資料
完全なアーキテクチャドキュメントと実装パターンについては @CLAUDE.md を参照してください。
Didn't find tool you were looking for?