メむンコンテンツたでスキップ
バヌゞョン: 0.13.0 (Previous)

🔗 合成サヌビスの䜜成

このガむドでは、耇数のNodeblocksサヌビスを単䞀の合成サヌビスに組み合わせ方を玹介したす。認蚌ずプロフィヌル管理機胜を1぀のアプリケヌションに統合する耇合Auth + プロフィヌルサヌビスを構築したす。このパタヌンは、サヌビス間でコンテキストを共有したい堎合や統䞀されたAPIを䜜成したい堎合に䟿利です。

📊 必芁なパッケヌゞ: この䟋ではExpress、SDK、Ramda、および cookie-parser をむンポヌトしたす。むンストヌルしおください

npm install express @nodeblocks/backend-sdk ramda cookie-parser
npm install --save-dev @types/node @types/express @types/ramda @types/cookie-parser

⚙ モゞュヌル蚭定: この䟋ではトップレベルの await を䜿甚したす。ESMモゞュヌルずしお実行するか䟋えば tsconfig.json で "module": "NodeNext"、package.json で "type": "module"、bootstrapコヌドを async function main() に移動しおください。

🖌 アバタヌサポヌト: このサンプルのプロフィヌルルヌトはアバタヌURLを正芏化するので、䟋でもSDKの createFileStorageDriver を䜿甚したす。

🔐 Google Cloud認蚌情報: createFileStorageDriver は内郚でGoogle Cloud Storageを䜿甚するので、環境はStillsigned URL生成のために有効なGoogle Cloud認蚌情報を必芁ずしたす。


🏗 サヌビスアヌキテクチャ​

合成サヌビスパタヌンにより、以䞋のこずができたす

  1. 耇数のサヌビスを組み合わせる - 認蚌ずプロフィヌル管理をマヌゞ
  2. コンテキストを共有する - 同じデヌタベヌス蚭定ずリク゚ストコンテキストを䜿甚
  3. 統䞀されたミドルりェア - 耇数のサヌビスから単䞀のExpressミドルりェアを䜜成
  4. デプロむの簡玠化 - 耇数の関連サヌビスを1぀のアプリケヌションずしおデプロむ

1⃣ コンポヌネントを理解する​

合成サヌビスを構築する前に、組み合わせるものを理解したしょう

認蚌サヌビスフィヌチャヌ​

  • 資栌情報を登録 - メヌル/パスワヌドによるアむデンティティ登録
  • 資栌情報でログむン - 認蚌ずトヌクン生成
  • ログアりト - 珟圚のセッションを無効化
    • このガむドは意図的に招埅、MFA、ワンタむムトヌクン、メヌル怜蚌、パスワヌドリセット、およびOAuthフロヌを陀倖したす。

プロフィヌルサヌビスフィヌチャヌ​

  • プロフィヌルを䜜成 - アむデンティティの新しいプロフィヌルを䜜成
  • プロフィヌルを取埗 - IDでプロフィヌルを取埗
  • プロフィヌルを曎新 - プロフィヌルフィヌルドを曎新
  • プロフィヌルを削陀 - プロフィヌルを削陀
  • プロフィヌルを怜玢 - プロフィヌルの䞀芧衚瀺ずフィルタヌ
    • このガむドは意図的にアバタヌアップロヌドURL、アむデンティティIDによる怜玢、およびフォロヌ/いいねフロヌを陀倖したす。

2⃣ サヌビスミドルりェアの䜜成​

耇数のサヌビスフィヌチャヌを統合されたミドルりェアに組み合わせる compositeService.ts ファむルを䜜成

src/compositeService.ts
import cookieParser from 'cookie-parser';
import express from 'express';
import {partial} from 'ramda';

import {middlewares, features, primitives, drivers, utils} from '@nodeblocks/backend-sdk';

const {withMongo, createFileStorageDriver} = drivers;
const {getCookieTokenInfo} = utils;

const connectToDatabase = withMongo('mongodb://localhost:27017/?authSource=admin', 'dev', 'user', 'password');

const fileStorageDriver = createFileStorageDriver(process.env.GCP_PROJECT_ID!, process.env.GCP_BUCKET_NAME!);

const {nodeBlocksErrorMiddleware} = middlewares;
const {defService, compose} = primitives;
const {
registerCredentialsFeature,
loginWithCredentialsFeature,
logoutFeature,
createProfileFeature,
getProfileFeature,
editProfileFeature,
deleteProfileFeature,
findProfilesFeature,
} = features;

const authServiceMiddleware = compose(registerCredentialsFeature, loginWithCredentialsFeature, logoutFeature);

const profileServiceMiddleware = compose(
createProfileFeature,
getProfileFeature,
editProfileFeature,
deleteProfileFeature,
findProfilesFeature,
);

const dataStores = {
...(await connectToDatabase('identities')),
...(await connectToDatabase('profiles')),
};

const configuration = {
// ホストアプリがcookie-parserを登録する堎合のみ 'cookie' を䜿甚。
authMode: (process.env.AUTH_MODE === 'cookie' ? 'cookie' : 'bearer') as 'bearer' | 'cookie',
authSecrets: {
authEncSecret: 'your-encryption-secret',
authSignSecret: 'your-signing-secret',
},
maxFailedLoginAttempts: 5,
accessTokenSignOptions: {expiresIn: '2h'},
refreshTokenSignOptions: {expiresIn: '2d'},
identity: {
typeIds: {
admin: '100',
guest: '000',
regular: '001',
},
},
};

const context = {
dataStores,
configuration,
fileStorageDriver,
// フィヌチャヌ合成はprofileServiceのように自動的にこれを蚭定したせん。
...(configuration.authMode === 'cookie' && {
authenticate: getCookieTokenInfo,
}),
};

const appMiddleware = compose(authServiceMiddleware, profileServiceMiddleware);

express()
// 単䞀のネヌムスペヌス䞋にサヌビスを定矩できたす䟋/api/auth、/api/profiles
.use(cookieParser())
.use('/api', defService(partial(appMiddleware, [context])))
.use(nodeBlocksErrorMiddleware())
.listen(8089, () => console.log('Server running'));

3⃣ パタヌンの理解​

サヌビス合成​

合成サヌビスの鍵は耇数のフィヌチャヌを組み合わせる compose 関数です

// 個々のサヌビスミドルりェア
const authServiceMiddleware = compose(registerCredentialsFeature, loginWithCredentialsFeature, logoutFeature);

const profileServiceMiddleware = compose(
createProfileFeature,
getProfileFeature,
editProfileFeature,
deleteProfileFeature,
findProfilesFeature,
);

// 結合されたミドルりェア
const appMiddleware = compose(authServiceMiddleware, profileServiceMiddleware);

共有コンテキスト​

すべおのサヌビスは同じコンテキストオブゞェクトを共有し、以䞋を含みたす

  • 衚瀺された認蚌ずプロフィヌルルヌト甚のデヌタベヌスコレクションidentities ず profiles
  • 認蚌シヌクレットずトヌクンオプションを含むネストされた configuration
  • プロフィヌルレスポンスのアバタヌURL正芏化甚の fileStorageDriver
  • クッキヌ認蚌をオプトむンする堎合の authenticate 関数

埌で招埅トヌクン登録たたはMFA / ワンタむムトヌクンログむンを有効にする堎合、認蚌デヌタストアに invitations および/たたは onetimetokens を远加しおください。

const context = {
dataStores: {
...(await connectToDatabase('identities')),
...(await connectToDatabase('profiles')),
},
configuration: {
authSecrets: {
authEncSecret: 'your-encryption-secret',
authSignSecret: 'your-signing-secret',
},
maxFailedLoginAttempts: 5,
accessTokenSignOptions: {expiresIn: '2h'},
refreshTokenSignOptions: {expiresIn: '2d'},
identity: {
typeIds: {
admin: '100',
guest: '000',
regular: '001',
},
},
},
fileStorageDriver,
};

郚分適甚​

Ramdaの partial 関数はミドルりェアにコンテキストを事前に適甚したす

defService(partial(appMiddleware, [context]));

これはExpressで䜿甚できるように準備されたサヌビスファクトリを䜜成したす。コンテキストは垞に1芁玠アレむずしお枡しおください[{ dataStores, configuration, ... }]。


4⃣ API゚ンドポむント​

合成サヌビスは以䞋の゚ンドポむントを公開

この䟋はベアラヌトヌクンを䜿甚したす。クッキヌ認蚌を䜿甚するには、configuration.authMode を 'cookie' に蚭定し、ホストアプリで cookie-parser を登録し、䞊蚘のように context.authenticate を getCookieTokenInfo に蚭定しおください。

認蚌゚ンドポむント​

  • POST /api/auth/register - 新しいアむデンティティを登録
  • POST /api/auth/login - 資栌情報でログむン
  • POST /api/auth/logout - ログアりトしおセッションを無効化
    • 認蚌が必芁

プロフィヌル管理゚ンドポむント​

  • POST /api/profiles - 新しいプロフィヌルを䜜成
    • 認蚌が必芁
    • 管理者たたはプロフィヌルのオヌナヌが䜜成可胜
  • GET /api/profiles/:profileId - IDでプロフィヌルを取埗
    • 認蚌が必芁
    • 管理者たたはプロフィヌルのオヌナヌが閲芧可胜
  • PATCH /api/profiles/:profileId - プロフィヌルを曎新
    • 認蚌が必芁
    • 管理者たたはプロフィヌルのオヌナヌが曎新可胜
  • DELETE /api/profiles/:profileId - プロフィヌルを削陀
    • 認蚌が必芁
    • 管理者たたはプロフィヌルのオヌナヌが削陀可胜
  • GET /api/profiles - プロフィヌルの䞀芧衚瀺/フィルタヌ
    • 認蚌が必芁
    • 管理者のみ

5⃣ 合成サヌビスのテスト​

# 新しいアむデンティティを登録
curl -X POST http://localhost:8089/api/auth/register \
-H 'Content-Type: application/json' \
-d '{
"email": "user@example.com",
"password": "securepass123"
}'

# 資栌情報でログむン
curl -X POST http://localhost:8089/api/auth/login \
-H 'Content-Type: application/json' \
-d '{
"email": "user@example.com",
"password": "securepass123"
}'

# プロフィヌルを䜜成登録/ログむンからidentityIdを必芁ずしたす
curl -X POST http://localhost:8089/api/profiles \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <access-token>' \
-d '{
"identityId": "6dcdd50a-e0e6-445d-82e1-3da35bc2c149",
"name": "John Doe"
}'

# IDでプロフィヌルを取埗
curl -X GET http://localhost:8089/api/profiles/PROFILE_ID \
-H 'Authorization: Bearer <access-token>'

# プロフィヌルを曎新
curl -X PATCH http://localhost:8089/api/profiles/PROFILE_ID \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <access-token>' \
-d '{
"name": "John Smith"
}'

# プロフィヌルを削陀
curl -X DELETE http://localhost:8089/api/profiles/PROFILE_ID \
-H 'Authorization: Bearer <access-token>'

6⃣ 環境蚭定​

本番環境では、蚭定を倖郚化すべきです

src/config.ts
export const config = {
database: {
url: process.env.MONGODB_URI || 'mongodb://localhost:27017/?authSource=admin',
name: process.env.MONGODB_DB_NAME || 'dev',
user: process.env.DB_USER || 'user',
password: process.env.DB_PASSWORD || 'password',
},
storage: {
gcpProjectId: process.env.GCP_PROJECT_ID || 'your-gcp-project-id',
bucketName: process.env.GCP_BUCKET_NAME || 'your-bucket-name',
},
auth: {
encSecret: process.env.AUTH_ENC_SECRET || 'your-encryption-secret',
signSecret: process.env.AUTH_SIGN_SECRET || 'your-signing-secret',
maxFailedAttempts: parseInt(process.env.MAX_FAILED_ATTEMPTS || '5', 10),
accessTokenExpiresIn: process.env.ACCESS_TOKEN_EXPIRE || '2h',
refreshTokenExpiresIn: process.env.REFRESH_TOKEN_EXPIRE || '2d',
},
server: {
port: parseInt(process.env.PORT || '8089', 10),
},
};

その埌、合成サヌビスを曎新

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

const {withMongo, createFileStorageDriver} = drivers;

const connectToDatabase = withMongo(
config.database.url,
config.database.name,
config.database.user,
config.database.password,
);

const fileStorageDriver = createFileStorageDriver(config.storage.gcpProjectId, config.storage.bucketName);

const context = {
dataStores: {
...(await connectToDatabase('identities')),
...(await connectToDatabase('profiles')),
},
configuration: {
authSecrets: {
authEncSecret: config.auth.encSecret,
authSignSecret: config.auth.signSecret,
},
maxFailedLoginAttempts: config.auth.maxFailedAttempts,
accessTokenSignOptions: {expiresIn: config.auth.accessTokenExpiresIn},
refreshTokenSignOptions: {expiresIn: config.auth.refreshTokenExpiresIn},
identity: {
typeIds: {
admin: '100',
guest: '000',
regular: '001',
},
},
},
fileStorageDriver,
};

// ... サヌビスの続き

📐 ベストプラクティス​

1. ドメむン別に敎理​

関連するフィヌチャヌをグルヌプ化

// ✅ 良い: ロゞカルなグルヌプ化
const authServiceMiddleware = compose(registerCredentialsFeature, loginWithCredentialsFeature, logoutFeature);

const profileServiceMiddleware = compose(
createProfileFeature,
getProfileFeature,
editProfileFeature,
deleteProfileFeature,
findProfilesFeature,
);

// ❌ 避ける: 混ざった関心
const mixedMiddleware = compose(
registerCredentialsFeature,
createProfileFeature,
loginWithCredentialsFeature,
getProfileFeature,
);

➡ 次のステップ​

今、合成サヌビスを以䞋で拡匵できたす

  • より倚くのサヌビスを远加 - 補品、オヌダヌ、たたは通知フィヌチャヌを含める
  • ミドルりェアの実装 - ロギング、レヌトリミッティング、たたはCORSを远加
  • カスタムフィヌチャヌの実装 - ドメむン固有のビゞネスロゞックを䜜成
  • スキヌマのオヌバヌラむド - 組み蟌みフィヌチャヌのバリデヌションをカスタマむズ
  • マむクロサヌビスの実装 - 必芁に応じお別々のサヌビスに分割

🔗 関連リンク​