🚀 機能
機能は、リクエスト検証と 1 つのルートを対にするための命名規約です。SDK では各機能は compose(withSchema(...), withRoute(...))、すなわち Express ルーターではなく ServiceDefinition を変換する Composable です。
defFeature の登録 API はありません。機能は src/features/ からエクスポートされ、compose でサービスに結合されてから defService でマウントされます。
🔍 機能とは
エクスポートされた各機能は、1 つのスキーマコンポーザーと 1 つのルートコンポーザーを対にします。複数エンドポイントには、サービス内で合成する複数の機能が必要です。
// 1 つの機能 = 1 つのスキーマ + 1 つのルート
export const getIdentityFeature = compose(getIdentitySchema, getIdentityRoute);
サービスは多数の機能を結合します。
compose(getIdentityFeature, findIdentitiesFeature, updateIdentityFeature, ...)
単一の名前空間エクスポートから機能をインポートします。
import { features } from '@nodeblocks/backend-sdk';
// features 名前空間から(identity モジュール):
// getIdentityFeature, findIdentitiesFeature, updateIdentityFeature,
// deleteIdentityFeature, lockIdentityFeature, unlockIdentityFeature
const { getIdentityFeature, findIdentitiesFeature } = features;
機能を使用する理由:
- 検証とルーティングを再利用可能な単位にまとめる
- グローバル状態なしに
composeでサービスへ合成できる defServiceによるマウント時、partialで依存関係を注入できる- 異なる機能を合成してカスタムサービスで置き換えられる
機能のライフサイクル
schemas/*.ts + routes/*.ts → features/*.ts → services/*.ts → defService()
(withSchema/withRoute) (compose) (複数を合成) (router)
- 定義 —
src/schemas/とsrc/routes/にwithSchema、withRouteコンポーザーを作成 - 合成 —
src/features/<domain>.tsからcompose(schema, route)をエクスポート - バンドル — サービスファクトリー(
src/services/)で機能を結合 - 提供 —
defService(partial(compose(...), [deps]))でマウント。必要な場合は WebSocket サーバーをdefServiceの第 2 引数に渡す
⚙️ 仕組み
機能は (service: ServiceDefinition) => ServiceDefinition という関数です。内部では primitives.compose は Ramda の pipe です。
スキーマの付加には withNextRoute フックを使用します。
withSchemaがservice.withNextRouteを設定します。これは次のルートハンドラーを検証でラップする関数です。withRouteはルート登録時にwithNextRouteを使用してから消去します。compose(...)ではスキーマをルートより前に置く必要があります。
// スキーマは、検証するルートより前に合成する
export const getIdentityFeature = compose(getIdentitySchema, getIdentityRoute);
HTTP ルートでは、検証はルートハンドラーの前に実行されます。無効なリクエストは、ハンドラーの実行前にステータス 400 の NodeblocksError をスローします。WebSocket スキーマは代わりにクライアントメッセージを検証し、無効なメッセージをストリーム経由で通知します。
withSchema の詳細は Schema »、withRoute の詳細は Route » を参照してください。
🧑💻 機能を定義する
機能は登録手順のない通常の compose エクスポートです。合成プリミティブは SDK からインポートします。ルートおよびスキーマコンポーザーはアプリケーションで定義したものです。
import { primitives } from '@nodeblocks/backend-sdk';
import { getIdentityRoute } from './routes';
import { getIdentitySchema } from './schemas';
const { compose } = primitives;
export const getIdentityFeature = compose(getIdentitySchema, getIdentityRoute);
機能が返すのは Express ルーターではなく Composable です。ルーターを生成するには defService を使用します。
🔀 条件付きスキーマ
一部の機能は、サービス構成に基づき合成時にスキーマを選択します。認証機能は primitives の ifElse と match を使用して configuration.authMode を分岐します。
import { primitives, utils } from '@nodeblocks/backend-sdk';
import { logoutRoute } from './routes';
import { logoutBearerSchema, logoutCookieSchema } from './schemas';
const { compose, ifElse, match } = primitives;
const { isCookieMode } = utils;
export const logoutFeature = compose(
ifElse(
match(isCookieMode, ['configuration', 'authMode']),
logoutCookieSchema,
logoutBearerSchema
),
logoutRoute
);
同じパターンは refreshTokenFeature でも使用されます。authMode が 'cookie' の場合は Cookie スキーマ、それ以外では Bearer スキーマが合成されます。
🧑💻 サービスで機能を使用する
機能は defService を通じて利用されます。開始 ServiceDefinition として依存関係を事前適用するには Ramda の partial を使用します。
import { partial } from 'ramda';
import { Collection } from 'mongodb';
import { features, primitives, utils } from '@nodeblocks/backend-sdk';
const {
getIdentityFeature,
findIdentitiesFeature,
updateIdentityFeature,
deleteIdentityFeature,
lockIdentityFeature,
unlockIdentityFeature,
} = features;
const { compose, defService } = primitives;
const { getBearerTokenInfo, getCookieTokenInfo } = utils;
export const identitiesService = (
dataStores: { identities: Collection },
configuration: {
authSecrets: { authEncSecret: string; authSignSecret: string };
authMode?: 'bearer' | 'cookie';
}
) => {
return defService(
partial(
compose(
getIdentityFeature,
findIdentitiesFeature,
updateIdentityFeature,
deleteIdentityFeature,
lockIdentityFeature,
unlockIdentityFeature
),
[
{
dataStores,
configuration,
authenticate:
configuration.authMode === 'cookie'
? getCookieTokenInfo
: getBearerTokenInfo,
},
]
)
);
};
partial と defService の動作:
partial(compose(...features), [deps])は、初期ServiceDefinitionとしてdataStores、configuration、authenticate、省略可能な非 WebSocket ドライバーを事前適用します。- 各機能コンポーザーが、その定義にルート(およびスキーマメタデータ)を追加します。
defServiceが最終ServiceDefinitionを Express ルーターに変換します。
一般的に注入するフィールドは dataStores、configuration、authenticate です。省略可能なドライバーには mailService、fileStorageDriver、OAuth ドライバー(googleOAuthDriver、twitterOAuthDriver、lineOAuthDriver)、findAddressDriver があります。
WebSocket ルートは例外です。webSocketServer はサービスコンテキストに置きません。defService の第 2 引数として渡します。組み込みの chatService は、第 3 drivers 引数でこれを受け取り、defService に転送します。
Cookie 認証(authMode: 'cookie')を使用する場合は、サービスルーターをマウントする前に cookie-parser Express ミドルウェアを登録してください。
SDK は同じ合成パターンを持つ完成済みの
services.identitiesServiceファクトリーを提供します。
📐 良い実践
- 機能ごとに 1 操作 — 作成、読み取り、更新、削除には別々の機能を作成します。
- 一貫した命名 —
<verb><Entity>Featureにすると見つけやすくなります(getIdentityFeature、findIdentitiesFeature)。 - スキーマをルートより前に置く —
withNextRouteのため、検証はルート登録より前である必要があります。 - サービスで多くの機能を合成する — サービスは
compose(featureA, featureB, ...)であり、多数のルートを持つ 1 機能ではありません。
📦 SDK 機能モジュール
機能は SDK の features 名前空間でドメイン別に整理され、src/features/index.ts と対応しています。
| モジュール | 説明 | リファレンス |
|---|---|---|
| Address | 住所検索エンドポイント | SDK のみ — ドキュメント準備中 |
| Attributes | 属性管理 | Attribute 機能 » |
| Authentication | ログイン、ログアウト、MFA、トークン | Authentication 機能 » |
| Category | カテゴリ CRUD | Category 機能 » |
| Chat | チャンネル、メッセージ、サブスクリプション | Chat 機能 » |
| Identity | アイデンティティライフサイクル(CRUD、ロック/ロック解除) | Identity 機能 » |
| Invitation | 招待管理 | Invitation 機能 » |
| Location | 階層的な場所管理 | Location 機能 » |
| Notification | 通知エンドポイント | SDK のみ — ドキュメント準備中 |
| OAuth | Google、Twitter、LINE の OAuth フロー | OAuth 機能 » |
| Order | 注文管理 | Order 機能 » |
| Organization | 組織およびワークスペースのロジック | Organization 機能 » |
| Product | プロダクト管理 | Product 機能 » |
| Profile | プロフィール、アバター、ソーシャルエンゲージメント | Profile 機能 » |
これらの機能を合成する完成済みサービスファクトリーは services 名前空間にあります。Service » を参照してください。
➡️ 次
完成済みサービスファクトリーについては Service »、検証については Schema »、ハンドラー配線については Route » を確認してください。