メインコンテンツまでスキップ
バージョン: 0.4.2

🔗 複合サービスの作成

このガイドでは、複数のNodeblocksサービスを単一の複合サービスに結合する方法を説明します。認証とユーザー管理機能を1つのアプリケーションに組み合わせた複合認証+ユーザーサービスを構築します。このパターンは、サービス間でコンテキストを共有したり、統一されたAPIを作成したい場合に有用です。

📦 必要なパッケージ: この例ではramdaパッケージを使用します。必ずインストールしてください:

npm install ramda

🏗️ サービスアーキテクチャ

複合サービスパターンにより以下が可能になります:

  1. 複数サービスの結合 - 認証とユーザー管理をマージ
  2. コンテキストの共有 - 同じデータベース接続と設定を使用
  3. 統一ミドルウェア - 複数サービスから単一のExpressミドルウェアを作成
  4. 簡略化されたデプロイメント - 関連する複数サービスを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 { nodeBlocksErrorMiddleware } = middlewares;
const { compose, defService } = primitives;
const { getMongoClient } = drivers;

// 認証サービスフィーチャー
const {
registerCredentialsFeature,
loginWithCredentialsFeature,
} = features.authentication;

// ユーザーサービスフィーチャー
const {
createUserFeature,
getUserFeatures,
editUserFeatures,
deleteUserFeatures,
lockUserFeatures,
} = features.user;

/**
* 認証とユーザー管理を組み合わせた複合サービスを作成
*/
export const compositeAuthUserService = (
dataStores: any,
configuration: any,
mailClient?: any
) => {
// すべてのフィーチャーを1つのサービスに組み合わせ
return defService(
partial(
compose(
// 認証フィーチャー
registerCredentialsFeature,
loginWithCredentialsFeature,
// ユーザー管理フィーチャー
createUserFeature,
getUserFeatures,
editUserFeatures,
deleteUserFeatures,
lockUserFeatures
),
{ dataStores, configuration, mailClient }
)
);
};

/**
* 複合サービス用のExpressサーバー設定
*/
export const setupCompositeServer = async () => {
// データベース接続
const client = getMongoClient(
process.env.MONGO_URL || 'mongodb://localhost:27017',
process.env.DB_NAME || 'nodeblocks-composite'
);

const dataStores = {
users: client.collection('users'),
identity: client.collection('identity'),
};

const configuration = {
authSecrets: {
authEncSecret: process.env.AUTH_ENC_SECRET || 'your-encryption-secret',
authSignSecret: process.env.AUTH_SIGN_SECRET || 'your-signing-secret',
},
jwtOpts: {
expiresIn: '24h'
}
};

// メールクライアント設定(オプション)
const mailClient = {
// メール設定をここに追加
};

// Expressアプリを作成
const app = express();

// ミドルウェア設定
app.use(cors());
app.use(express.json());

// 複合サービスを統合
app.use('/api', compositeAuthUserService(dataStores, configuration, mailClient));

// エラーハンドリングミドルウェア
app.use(nodeBlocksErrorMiddleware());

return app;
};

3️⃣ カスタムフィーチャーの追加

複合サービスに独自のフィーチャーを追加することもできます:

src/compositeService.ts
import { withRoute } from '@nodeblocks/backend-sdk';

// カスタムヘルスチェックルート
const healthCheckRoute = withRoute({
method: 'GET',
path: '/health',
handler: () => ({
status: 'ok',
services: ['auth', 'user'],
timestamp: new Date().toISOString()
})
});

// カスタムユーザー統計ルート
const userStatsRoute = withRoute({
method: 'GET',
path: '/users/stats',
handler: async (payload) => {
const { context } = payload;
const totalUsers = await context.db.users.countDocuments();
const activeUsers = await context.db.users.countDocuments({ status: 'active' });

return {
total: totalUsers,
active: activeUsers,
inactive: totalUsers - activeUsers
};
}
});

// 拡張された複合サービス
export const enhancedCompositeService = (
dataStores: any,
configuration: any,
mailClient?: any
) => {
return defService(
partial(
compose(
// 既存フィーチャー
registerCredentialsFeature,
loginWithCredentialsFeature,
createUserFeature,
getUserFeatures,
editUserFeatures,
deleteUserFeatures,
lockUserFeatures,
// カスタムフィーチャー
healthCheckRoute,
userStatsRoute
),
[{ dataStores, configuration, mailClient }]
)
);
};

4️⃣ 設定の管理

複合サービス用の設定を管理:

src/config.ts
export interface CompositeConfig {
database: {
url: string;
name: string;
};
auth: {
encSecret: string;
signSecret: string;
jwtExpiry: string;
};
email?: {
host: string;
port: number;
user: string;
pass: string;
};
server: {
port: number;
cors: {
origin: string[];
};
};
}

export const loadConfig = (): CompositeConfig => ({
database: {
url: process.env.MONGO_URL || 'mongodb://localhost:27017',
name: process.env.DB_NAME || 'nodeblocks-composite'
},
auth: {
encSecret: process.env.AUTH_ENC_SECRET || 'default-enc-secret',
signSecret: process.env.AUTH_SIGN_SECRET || 'default-sign-secret',
jwtExpiry: process.env.JWT_EXPIRY || '24h'
},
email: process.env.EMAIL_HOST ? {
host: process.env.EMAIL_HOST,
port: parseInt(process.env.EMAIL_PORT || '587'),
user: process.env.EMAIL_USER || '',
pass: process.env.EMAIL_PASS || ''
} : undefined,
server: {
port: parseInt(process.env.PORT || '3000'),
cors: {
origin: process.env.CORS_ORIGINS?.split(',') || ['http://localhost:3000']
}
}
});

5️⃣ サーバーの起動

完全なサーバー起動ファイル:

src/index.ts
import { setupCompositeServer, loadConfig } from './compositeService';

const startServer = async () => {
try {
const config = loadConfig();
const app = await setupCompositeServer();

app.listen(config.server.port, () => {
console.log(`🚀 複合サービスが起動しました - ポート ${config.server.port}`);
console.log(`📚 利用可能なエンドポイント:`);
console.log(` POST /api/auth/register - ユーザー登録`);
console.log(` POST /api/auth/login - ユーザーログイン`);
console.log(` POST /api/users - ユーザー作成`);
console.log(` GET /api/users/:id - ユーザー取得`);
console.log(` PATCH /api/users/:id - ユーザー更新`);
console.log(` DELETE /api/users/:id - ユーザー削除`);
console.log(` GET /api/health - ヘルスチェック`);
console.log(` GET /api/users/stats - ユーザー統計`);
});
} catch (error) {
console.error('❌ サーバーの起動に失敗しました:', error);
process.exit(1);
}
};

startServer();

6️⃣ テスト

複合サービスをテスト:

# サーバーを起動
npm start

# ヘルスチェック
curl http://localhost:3000/api/health

# ユーザー登録
curl -X POST http://localhost:3000/api/auth/register \
-H "Content-Type: application/json" \
-d '{"email":"user@example.com","password":"password123"}'

# ログイン
curl -X POST http://localhost:3000/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"user@example.com","password":"password123"}'

# ユーザー統計
curl http://localhost:3000/api/users/stats

🎯 利点

複合サービスパターンの利点:

1. コンテキスト共有

// 同じデータベース接続とトランザクション
const sharedTransaction = async (operations) => {
const session = client.startSession();
try {
await session.withTransaction(async () => {
await operations.forEach(op => op(session));
});
} finally {
await session.endSession();
}
};

2. 統一されたミドルウェア

// 1つの認証ミドルウェアですべてのサービスを保護
app.use('/api', authMiddleware);
app.use('/api', compositeService);

3. 簡略化されたデプロイメント

# 単一のDockerイメージ
FROM node:16
COPY . .
RUN npm install
EXPOSE 3000
CMD ["npm", "start"]

⚠️ 考慮事項

複合サービスを使用する際の考慮事項:

1. スケーラビリティ

  • 個別スケーリングができない
  • 全体のパフォーマンスがボトルネックの影響を受ける

2. 独立性の喪失

  • 一部の障害が全体に影響
  • 独立したデプロイができない

3. 複雑性の増加

  • デバッグが困難になる可能性
  • 依存関係の管理が複雑

➡️ 次のステップ