🔗 複合サービスの作成
このガイドでは、複数のNodeBlocksサービスを単一の複合サービスに組み合わせる方法を説明します。認証とユーザー管理機能を1つのアプリケーションに組み合わせた複合認証 + ユーザーサービスを構築します。このパターンは、サービス間でコンテキストを共有したり、統合APIを作成したりする場合に便利です。
📦 必要なパッケージ: この例では
ramda
パッケージを使用します。必ずインストールしてください:npm install ramda
🏗️ サービスアーキテクチャ
複合サービスパターンでは、以下が可能になります:
- 複数のサービスを組み合わせる - 認証とユーザー管理を統合
- コンテキストを共有 - 同じデータベース接続と設定を使用
- 統合ミドルウェア - 複数のサービスから単一のExpressミドルウェアを作成
- 簡素化されたデプロイ - 関連する複数のサービスを1つのアプリケーションとしてデプロイ
1️⃣ コンポーネントを理解する
複合サービスを構築する前に、何を組み合わせるかを理解しましょう:
認証サービスの機能
- 認証情報の登録 - メール/パスワードによるユーザー登録
- 認証情報でのログイン - ユーザー認証とトークン生成
ユーザーサービスの機能
- ユーザー作成 - 新しいユーザープロファイルを作成
- ユーザー機能の取得 - ユーザー情報を取得
- ユーザー機能の編集 - ユーザープロファイルを更新
- ユーザー機能の削除 - ユーザーアカウントを削除
- ユーザー機能のロック - ユーザーアカウントを無効化
2️⃣ サービスミドルウェアを作成
複数のサービス機能を統合ミドルウェアに組み合わせる compositeService.ts
ファイルを作成します:
src/compositeService.ts
import express from 'express';
import { partial } from 'lodash';
import cors from 'cors';
import {
middlewares,
features,
primitives,
drivers,
routes,
} from '@nodeblocks/backend-sdk';
const { getMongoClient } = drivers;
const client = getMongoClient('mongodb://localhost:27017', 'dev');
const { nodeBlocksErrorMiddleware } = middlewares;
const { defService, compose, withSchema } = primitives;
const {
registerCredentialsFeature,
loginWithCredentialsFeature,
// createUserFeature,
getUserFeatures,
deleteUserFeatures,
lockUserFeatures,
editUserFeatures,
findUsersFeatures,
} = features;
const {
createUserRoute,
} = routes;
export const userSchema: primitives.SchemaDefinition = {
$schema: 'http://json-schema.org/draft-07/schema#',
additionalProperties: false,
properties: {
email: { type: 'string' }, // メールアドレス
name: { type: 'string' }, // ユーザー名
status: { type: 'string' }, // ステータス
age: { type: 'number' }, // 年齢
},
type: 'object',
};
export const createUserSchema = withSchema({
requestBody: {
content: {
'application/json': {
schema: {
...userSchema,
required: ['email', 'name', 'status', 'age'], // 必須フィールド
},
},
},
required: true,
},
});
const createUserFeature = compose(createUserSchema, createUserRoute);
const authServiceMiddleware = compose(
registerCredentialsFeature, // 認証情報登録機能
loginWithCredentialsFeature // 認証情報ログイン機能
);
const userServiceMiddleware = compose(
createUserFeature, // ユーザー作成機能
getUserFeatures, // ユーザー取得機能
editUserFeatures, // ユーザー編集機能
deleteUserFeatures, // ユーザー削除機能
lockUserFeatures, // ユーザーロック機能
findUsersFeatures // ユーザー検索機能
);
const dataStores = {
identity: client.collection('identity'), // 認証コレクション
users: client.collection('users'), // ユーザーコレクション
};
const configuration = {
authSecrets: {
authEncSecret: 'your-encryption-secret', // JWT 暗号化シークレット
authSignSecret: 'your-signing-secret', // JWT 署名シークレット
},
maxFailedLoginAttempts: 5, // 最大ログイン失敗回数
accessTokenExpireTime: '2h', // アクセストークン有効期限
refreshTokenExpireTime: '2d', // リフレッシュトークン有効期限
};
const context = { dataStores, configuration };
const appMiddleware = compose(authServiceMiddleware, userServiceMiddleware);
express()
.use(
cors({
origin: ['*'],
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['*'],
})
)
// 単一の名前空間でサービスを定義可能、例: /api/v1/auth, /api/v1/user など
.use('/api', defService(partial(appMiddleware, context)))
.use(nodeBlocksErrorMiddleware())
.listen(8089, () => console.log('Server running'));
3️⃣ パターンを理解する
サービス構成
複合サービスの鍵は、複数の機能を組み合わせる compose
関数です:
// 個別のサービスミドルウェア
const authServiceMiddleware = compose(
registerCredentialsFeature, // 認証情報登録機能
loginWithCredentialsFeature // 認証情報ログイン機能
);
const userServiceMiddleware = compose(
createUserFeature, // ユーザー作成機能
getUserFeatures, // ユーザー取得機能
editUserFeatures, // ユーザー編集機能
deleteUserFeatures, // ユーザー削除機能
lockUserFeatures // ユーザーロック機能
);
// 統合ミドルウェア
const appMiddleware = compose(authServiceMiddleware, userServiceMiddleware);
共有コンテキスト
すべてのサービスが同じコンテキストオブジェクトを共有し、以下のような共有リソースを含みます:
- 認証サービスとユーザーサービスの両方のデータベースコレクション
- 認証とトークンの設定
- メールプロバイダーなどの外部サービスクライアント
- サービス間で必要なその他の共有依存関係
const context = {
dataStores: {
identity: client.collection('identity'), // 認証サービス用
users: client.collection('users'), // ユーザーサービス用
},
configuration: {
authSecrets: {
authEncSecret: 'your-encryption-secret', // JWT 暗号化シークレット
authSignSecret: 'your-signing-secret' // JWT 署名シークレット
},
accessTokenExpireTime: '2h', // アクセストークン有効期限
refreshTokenExpireTime: '2d' // リフレッシュトークン有効期限
}
mailService: sendGridClient // メールサービス
};
部分適用
Ramda の partial
関数は、ミドルウェアにコンテキストを事前適用します:
defService(partial(appMiddleware, [context]))
これにより、Express で使用できる準備済みのサービスファクトリが作成されます。
4️⃣ APIエンドポイント
複合サービスは、以下のエンドポイントを公開します:
認証エンドポイント
POST /api/auth/register
- 新しいユーザーを登録POST /api/auth/login
- 認証情報でログインPOST /api/auth/logout
- 認証情報でログイン
ユーザー管理エンドポイント
POST /api/users
- 新しいユーザーを作成GET /api/users/:userId
- IDでユーザーを取得GET /api/users
- 全ユーザーを一覧PATCH /api/users/:userId
- ユーザーを更新DELETE /api/users/:userId
- ユーザーを削除POST /api/users/:userId/lock
- ユーザーアカウントをロックPOST /api/users/:userId/unlock
- ユーザーアカウントをロック解除
5️⃣ 複合サービスのテスト
# 新しいユーザーを登録
curl -X POST http://localhost:8089/api/auth/register \
-H 'Content-Type: application/json' \
-d '{
"email": "user@example.com",
"password": "securepassword123"
}'
# 認証情報でログイン
curl -X POST http://localhost:8089/api/auth/login \
-H 'Content-Type: application/json' \
-d '{
"email": "user@example.com",
"password": "securepassword123"
}'
# ユーザープロファイルを作成
curl -X POST http://localhost:8089/api/users \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <access-token>' \
-d '{
"name": "John Doe",
"email": "john@example.com",
"status": "active"
}'
# IDでユーザーを取得
curl -X GET http://localhost:8089/api/users/USER_ID \
-H 'Authorization: Bearer <access-token>'
# ユーザーを更新
curl -X PATCH http://localhost:8089/api/users/USER_ID \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <access-token>' \
-d '{
"name": "John Smith"
}'
# ユーザーアカウントをロック
curl -X POST http://localhost:8089/api/users/USER_ID/lock \
-H 'Authorization: Bearer <access-token>'
# ユーザーを削除
curl -X DELETE http://localhost:8089/api/users/USER_ID \
-H 'Authorization: Bearer <access-token>'
6️⃣ 環境設定
本番環境では、設定を外部化する必要があります:
src/config.ts
export const config = {
database: {
url: process.env.MONGODB_URL || 'mongodb://localhost:27017', // MongoDB URL
name: process.env.DB_NAME || 'dev' // データベース名
},
auth: {
encSecret: process.env.AUTH_ENC_SECRET || 'your-encryption-secret', // JWT 暗号化シークレット
signSecret: process.env.AUTH_SIGN_SECRET || 'your-signing-secret', // JWT 署名シークレット
maxFailedAttempts: parseInt(process.env.MAX_FAILED_ATTEMPTS || '5'), // 最大ログイン失敗回数
accessTokenExpireTime: process.env.ACCESS_TOKEN_EXPIRE || '2h', // アクセストークン有効期限
refreshTokenExpireTime: process.env.REFRESH_TOKEN_EXPIRE || '2d' // リフレッシュトークン有効期限
},
server: {
port: parseInt(process.env.PORT || '8089') // サーバーポート
}
};
次に、複合サービスを更新します:
src/compositeService.ts
import { config } from './config';
const client = new MongoClient(config.database.url).db(config.database.name);
const context = {
dataStores: {
identity: client.collection('identity'), // 認証コレクション
users: client.collection('users'), // ユーザーコレクション
},
configuration: {
authSecrets: {
authEncSecret: config.auth.encSecret, // JWT 暗号化シークレット
authSignSecret: config.auth.signSecret // JWT 署名シークレット
},
maxFailedLoginAttempts: config.auth.maxFailedAttempts, // 最大ログイン失敗回数
accessTokenExpireTime: config.auth.accessTokenExpireTime, // アクセストークン有効期限
refreshTokenExpireTime: config.auth.refreshTokenExpireTime // リフレッシュトークン有効期限
}
};
// ... サービスの残りの部分
️📐 ベストプラクティス
1. ドメインごとに整理
関連する機能をグループ化します:
// ✅ 良い例: 論理的なグループ化
const authServiceMiddleware = compose(
registerCredentialsFeature, // 認証情報登録機能
loginWithCredentialsFeature // 認証情報ログイン機能
);
const userServiceMiddleware = compose(
createUserFeature, // ユーザー作成機能
getUserFeatures, // ユーザー取得機能
editUserFeatures, // ユーザー編集機能
deleteUserFeatures, // ユーザー削除機能
lockUserFeatures // ユーザーロック機能
);
// ❌ 避けるべき例: 混合された懸念事項
const mixedMiddleware = compose(
registerCredentialsFeature, // 認証機能
createUserFeature, // ユーザー機能
loginWithCredentialsFeature, // 認証機能
getUserFeatures // ユーザー機能
);
➡️ 次のステップ
これで複合サービスを拡張できるようになりました:
- サービスの追加 - 製品、注文、通知サービスを含める
- ミドルウェアの実装 - ログ、レート制限、CORS を追加
- カスタム機能の追加 - ドメイン固有のビジネスロジックを作成
- マイクロサービスの実装 - 必要に応じて個別のサービスに分割
🔗 関連項目
- カスタムサービスの作成 - 個別のサービス構築を学習
- サービスコンポーネント - サービスアーキテクチャを理解
- 機能コンポーネント - 機能構成について学習
- エラーハンドリング - 複合サービスでのエラーハンドリング