システムレベルAPI実装ガイド
このドキュメントの目的
システムレベル管理APIを、ゼロから実装できるようになることが目標です。
所要時間
⏱️ 約45分(実装 + テスト)
前提知識
全体像
システムレベルAPIとは
テナント単位で管理するAPI。システム管理者が使用。
GET /v1/management/tenants/{tenantId}/clients
POST /v1/management/tenants/{tenantId}/clients
特徴:
- ✅ テナント単位のリソース管理
- ✅ システム管理者権限が必要(
client:read,client:write等) - ✅ 全操作の監査ログ記録(重要)
- ✅ Dry Run対応(変更前の検証)
対比: 組織レベルAPI = 組織単位で管理(/organizations/{orgId}/tenants/{tenantId}/...)
アーキテクチャ全体像
HTTPリクエスト
↓
Controller (XxxManagementV1Api)
- HTTP処理のみ
↓
EntryService (XxxManagementEntryService)
- Handler呼び出し
- Audit Log記録 ← 全操作必須
- レスポンス返却
↓
Handler (XxxManagementHandler)
- Tenant取得
- 権限チェック
- Service委譲
- 例外処理
↓
Service (XxxCreationService, XxxUpdateService等)
- Validation
- ビジネスロジック
- ContextBuilder更新
- Repository呼び出し
↓
Repository
- DB永続化
監査ログ(Audit Log)
Control Plane APIの最重要機能の1つ
なぜ必要か
- ✅ セキュリティ: 誰が、いつ、何をしたか追跡
- ✅ コンプライアンス: 監査要件への対応
- ✅ トラブルシューティング: 設定変更履歴の追跡
- ✅ 不正検知: 異常な操作パターンの検出
記録される情報
AuditableContextが提供:
- 操作者情報: userId, externalUserId, ipAddress, userAgent
- 対象リソース: targetResource, targetResourceAction
- 変更内容: before(変更前), after(変更後)
- 結果: outcomeResult(success/failure), outcomeReason
- メタ情報: dryRun, tenantId, clientId
実装パターン
EntryServiceで必ず実行:
// 1. Handler呼び出し
XxxManagementResult result = handler.handle(...);
// 2. Audit Log記録(成功・失敗問わず必須)
AuditLog auditLog = AuditLogCreator.create(result.context());
auditLogPublisher.publish(auditLog);
// 3. レスポンス返却
return result.toResponse(dryRun);
重要:
- ContextBuilderがHandlerで早期作成されるため、エラー時も記録可能
- before/after の変更履歴を自動記録
実装の全体フロー
Handler-Serviceパターンによる3層実装:
1. API契約定義(Control Plane層)
├─ インターフェース定義(XxxManagementApi)
├─ Request/Response DTO(Map<String, Object>ベース)
├─ Handler実装(Tenant取得、権限チェック、Service委譲)
├─ Service実装(Validation、ビジネスロジック、永続化)
├─ Context Creator(リクエスト→ドメインモデル変換)
└─ 権限定義(defaultメソッド)
2. EntryService実装(UseCase層)
├─ Handlerの初期化(Serviceマップ登録)
├─ Handlerに委譲
├─ Audit Log記録
└─ レスポンス返却
3. Controller実装(Controller層)
└─ HTTPエンドポイント(EntryService呼び出し)
4. E2Eテスト作成
└─ API動作確認
重要: EntryServiceは複雑な処理を持たず、Handlerに委譲するだけ
各層の責務と主要クラス
EntryService(UseCase層)
責務: トランザクション境界、Audit Log記録、レスポンス変換
実装: 3ステップのみ
- Handler呼び 出し
- Audit Log記録
- レスポンス返却
クラス例: ClientManagementEntryService, UserManagementEntryService
Handler(Control Plane層)
責務: Tenant取得、権限チェック、Serviceオーケストレーション、例外処理
実装:
- Service選択(メソッド名から適切なServiceを選択)
- Context Builder作成
- Tenant取得
- 権限チェック(
ApiPermissionVerifier) - Serviceに委譲
- 例外ハンドリング
クラス例: ClientManagementHandler, UserManagementHandler
Service(Control Plane層)
責務: 入力検証、ビジネスロジック、Context更新、永続化
実装:
- Validation(Validator使用)
- ビジネスロジック実行(ドメインモデル作成)
- Context Builder更新(Before/After状態)
- Dry Run判定
- Repository呼び出し(永続化)
- レスポンス作成
クラス例: ClientCreationService, ClientUpdateService, ClientDeletionService
実装手順
新しいシステムレベルAPIを実装する手順を説明します。
Step 1: API契約定義(Control Plane層)
作成するファイル:
libs/idp-server-control-plane/src/main/java/org/idp/server/control_plane/management/{domain}/
├── {Domain}ManagementApi.java # インターフェース
├── {Domain}ManagementContext.java # 統一Context
├── {Domain}ManagementContextBuilder.java # ContextBuilder
├── io/
│ ├── {Domain}ManagementRequest.java # Request DTO(Map<String, Object>ベース)
│ ├── {Domain}ManagementResponse.java # Response DTO
│ └── {Domain}ManagementStatus.java # Status列挙型
├── handler/
│ ├── {Domain}ManagementHandler.java # Handler
│ ├── {Domain}CreationService.java # create用Service
│ ├── {Domain}UpdateService.java # update用Service
│ ├── {Domain}DeletionService.java # delete用Service
│ ├── {Domain}FindService.java # get用Service
│ └── {Domain}FindListService.java # findList用Service
└── validator/
└── {Domain}RegistrationRequestValidator.java # Validator
実装の参考:
Step 2: EntryService実装(UseCase層)
作成するファイル:
libs/idp-server-use-cases/src/main/java/org/idp/server/usecases/control_plane/system_manager/
└── {Domain}ManagementEntryService.java
実装パターン:
@Transaction
public class XxxManagementEntryService implements XxxManagementApi {
private final XxxManagementHandler handler;
private final AuditLogPublisher auditLogPublisher;
// コンストラクタ: Handlerを初期化(Serviceマップ登録)
public XxxManagementEntryService(...) {
Map<String, XxxManagementService<?>> services = new HashMap<>();
services.put("create", new XxxCreationService(...));
services.put("findList", new XxxFindListService(...));
// ...
this.handler = new XxxManagementHandler(services, this, tenantQueryRepository);
this.auditLogPublisher = auditLogPublisher;
}
// 各メソッド: 3ステップパターン
@Override
public XxxManagementResponse create(...) {
// 1. Handlerに委譲
XxxManagementResult result = handler.handle("create", ...);
// 2. Audit Log記録
AuditLog auditLog = AuditLogCreator.create(result.context());
auditLogPublisher.publish(auditLog);
// 3. レスポンス返却
return result.toResponse(dryRun);
}
}
実装の参考:
Step 3: IdpServerApplication登録
ファイル: libs/idp-server-use-cases/src/main/java/org/idp/server/usecases/IdpServerApplication.java
実装パターン:
// フィールド追加
XxxManagementApi xxxManagementApi;
// コンストラクタ内で初期化
this.xxxManagementApi =
TenantAwareEntryServiceProxy.createProxy(
new XxxManagementEntryService(...),
XxxManagementApi.class,
databaseTypeProvider);
// Getter追加
public XxxManagementApi xxxManagementApi() {
return xxxManagementApi;
}
Proxy選択:
- System-level:
TenantAwareEntryServiceProxy - Organization-level:
ManagementTypeEntryServiceProxy
Step 4: Controller実装(Controller層)
作成するファイル:
libs/idp-server-springboot-adapter/src/main/java/org/idp/server/adapters/springboot/management/{domain}/
└── {Domain}ManagementV1Api.java
実装パターン:
@RestController
@RequestMapping("/v1/management/tenants/{tenant-id}/{resources}")
public class XxxManagementV1Api {
private final XxxManagementApi xxxManagementApi;
@PostMapping
public ResponseEntity<?> post(
@AuthenticationPrincipal OperatorPrincipal operatorPrincipal,
@PathVariable("tenant-id") String tenantId,
@RequestBody Map<String, Object> requestBody) {
XxxManagementRequest request = new XxxManagementRequest(requestBody);
XxxManagementResponse response =
xxxManagementApi.create(
new TenantIdentifier(tenantId),
operatorPrincipal.operator(),
operatorPrincipal.oAuthToken(),
request,
requestAttributes,
dryRun);
return ResponseEntity.status(response.statusCode()).body(response.contents());
}
}
実装の参考:
Step 5: E2Eテスト作成
作成するファイル:
e2e/src/tests/management/{domain}/
└── {domain}-management.test.js
実装の参考:
e2e/src/tests/management/client/配下のテスト
チェックリスト
システムレベルAPI実装前に以下を確認:
API契約定義(Control Plane層)
- インターフェース定義(
{Domain}ManagementApi) -
defaultメソッドで権限定義(実装不要) - Request DTO作成(
Map<String, Object>ベース、型安全なヘルパーメソッド) - Response DTO作成
- Context Creator作成(リクエスト → ドメインモデル変換)
- Context作成(
toResponse()メソッド実装)
EntryService実装(UseCase層)
-
@Transactionアノテーション付与 - 読み取り専用なら
@Transaction(readOnly = true) - Context Creator使用
- 権限チェック実装
- Audit Log記録(
AuditLogCreator.create()) - Dry Run対応(書き込み操作のみ)
IdpServerApplication登録
- フィールド追加
-
TenantAwareEntryServiceProxy使用(第一引数がTenantIdentifier) - Getterメソッド追加
Controller実装(Controller層)
- HTTPエンドポイント定義
- 型変換のみ(ロジック禁止)
-
@PathVariable,@RequestParam適切使用
E2Eテスト
- 正常系テスト(CREATE/READ/UPDATE/DELETE)
- Dry Runテスト
- 権限エラーテスト(403)
よくあるエラー
エラー1: defaultメソッドを実装してしまう
// ❌ 間違い: defaultメソッドをオーバーライド
@Override
public AdminPermissions getRequiredPermissions(String method) {
// 不要な実装
}
// ✅ 正しい: defaultメソッドはそのまま使用(実装不要)
public class RoleManagementEntryService implements RoleManagementApi {
// getRequiredPermissions()は実装不要!
}
エラー2: Context Creator未使用
// ❌ 間違い: EntryServiceでDTO直接変換
Role role = new Role(
new RoleIdentifier(request.getRoleId()),
// ... 直接変換
);
// ✅ 正しい: Context Creator使用
RoleManagementContextBuilder creator =
new RoleManagementContextBuilder(tenant, request, dryRun);
RoleManagementContext context = creator.create();
次のステップ
✅ システムレベルAPI実装をマスターした!
📖 次に読むべきドキュメント
- 組織レベルAPI実装ガイド - より複雑なアクセス制御
- Repository実装ガイド - データアクセス層の実装
情報源: ClientManagementEntryService.java 最終更新: 2025-10-12