🧩 ブロック
ブロックは、特定の操作のアプリケーションロジックの核心を含むパラメータ駆動のビジネスロジック関数です。再利用可能でテスト可能かつ構成可能なビルディングブロックとして設計され、ビジネスロジックをペイロード処理やルーティングの関心事から分離します。
ブロック登録APIはありません — ブロックはSDK blocks モジュールのエクスポートされた関数であり、primitives.applyPayloadArgs 経由でルートに接続されます。
🔍 ブロックとは何か
ブロック は必要なデータのみを受信し、完全な RouteHandlerPayload を受信するわけではありません。ほとんどのドメインブロックは明示的なエラー処理のために Result を返します; ヘルパー関数やターミネーター関数はプレーンな値またはレスポンス形状を返す可能性があります。ルートは applyPayloadArgs によってペイロードとブロックの間を橋渡しし、ペイロードから値を抽出し、ブロックの結果を context.data に格納します。
┌─────────────┐
│ Route │ compose(applyPayloadArgs(...), flatMapAsync(...), lift(orThrow(...)))
├─────────────┤
│ Block ▷ │ パラメータ駆動のビジネスロジック
│ │ getIdentityById(db, identityId)
└─────────────┘
設計原則:
- 関心の分離 — ブロックはビジネスロジックを含み、ルートは
applyPayloadArgsを使用してペイロードとブロックの間を橋渡しする。 - パラメータ駆動 — ブロックは抽出された引数Via必要なもののみを受け取り、完全なペイロードではない(ユニットテストが容易)。
- エラー処理 — ブロックは
Result型を返す;orThrowはドメインエラーをチェーンの末尾でHTTPレスポンスにマッピングする。 - 構成可能性 — ブロックは
flatMapAsyncと共にルートハンドラーパイプラインで連結される。 - 規約ベース —
defBlock、レジストリ、またはメタデータスキーマはない;src/blocks/から関数をエクスポートし、ルートで接続する。
ブロックのライフサイクル
blocks/*.ts → routes/*.ts → features/*.ts → defService()
(定義) (接続) (スキーマ + ルート) (提供)
- 定義 —
src/blocks/<domain>.tsで関数をエクスポート - 接続 —
src/routes/<domain>.tsでapplyPayloadArgsを使用してルートハンドラーに構成 - 検証 — ルートとスキーマを
src/features/<domain>.tsでペアリング - 提供 —
defService(feature)でフィーチャーをマウント
🧑💻 ブロックの定義
ブロックはプレーンなエクスポートされた関数である — 登録ステップなし:
import { Collection } from 'mongodb';
import { err, ok } from 'neverthrow';
import { primitives } from '@nodeblocks/backend-sdk';
const { BlockError } = primitives;
class ThingBlockError extends BlockError {}
class ThingNotFoundBlockError extends ThingBlockError {}
// src/blocks/my-domain.ts — アプリケーションコード、登録不要
export async function getCustomThingById(db: Collection, id: string) {
try {
const result = await db.findOne({ id: String(id) });
if (!result) {
return err(new ThingNotFoundBlockError('Thing not found'));
}
return ok(result);
} catch {
return err(new ThingBlockError('Failed to get thing'));
}
}
この例はアプリケーションブロックを定義しています; SDKエクスポートではありません。SDKブロックは単一の blocks名前空間からインポートします。例: const { getIdentityById } = blocks。
🧑💻 ルートでのブロックの使用
ブロックは applyPayloadArgs によってルートに構成されます。ブロック関数、ペイロードから引数を抽出するためのパスタプル、および結果を context.data に格納するオプションのキーを取ります:
import { blocks, primitives, validators } from '@nodeblocks/backend-sdk';
const { getIdentityById, normalizeIdentity } = blocks;
const { applyPayloadArgs, compose, flatMapAsync, lift, orThrow, withRoute } =
primitives;
const { isAuthenticated, checkIdentityType } = validators;
export const getIdentityRoute = withRoute({
method: 'GET',
path: '/identities/:identityId',
validators: [isAuthenticated(), checkIdentityType(['admin'])],
handler: compose(
applyPayloadArgs(
getIdentityById,
[
['context', 'db', 'identities'],
['params', 'requestParams', 'identityId'],
],
'rawIdentity'
),
flatMapAsync(
applyPayloadArgs(
normalizeIdentity,
[['context', 'data', 'rawIdentity']],
'identity'
)
),
lift(orThrow([], [['context', 'data', 'identity']]))
),
});
applyPayloadArgs(fn, paths, key?) は AsyncRouteHandler を返します -- ペイロードを第二引数として取りません。
applyPayloadArgs の動作
| 動作 | 詳細 |
|---|---|
| 引数の抽出 | 各パスタプル(例: ['context', 'db', 'identities'])は、Ramda の path を通じてペイロードから解決されます |
任意の key | 指定すると、ブロック結果は context.data[key] に格納されます。省略すると、戻り値全体が context.data にマージされます |
| 同期と非同期 | 同期ブロックと非同期ブロックの両方に対応します。Promise ではない戻り値も自動的に処理されます |
| Result の伝播 | ブロックが Result.err() を返すと、チェーンは短絡します。Result.ok() の値はペイロードにマージされます |
キーあり(最も一般的):
applyPayloadArgs(getIdentityById, [
['context', 'db', 'identities'],
['params', 'requestParams', 'identityId'],
], 'rawIdentity')
// → context.data.rawIdentity = identity オブジェクト
キーなし(戻り値を context.data に格納):
applyPayloadArgs(buildUpdatePayload, [
['params', 'requestBody'],
])
// → 戻り値が context.data に格納される
同期ブロック(例: normalizeIdentity):
applyPayloadArgs(normalizeIdentity, [['context', 'data', 'rawIdentity']], 'identity')
// normalizeIdentity は同期関数ですが、applyPayloadArgs は同じように処理します
⚠️ エラー処理とターミネーター
ドメインブロックは通常 Result<T, Error> を返します。ヘルパー関数とターミネーター関数は、代わりにプレーンな値またはレスポンス形状を返す場合があります。エラーの型はドメインによって異なります。
- ほとんどのドメイン — ドメイン固有の
*BlockError extends BlockError(例:ProfileBlockError、AuthenticationBlockError) - Identity ブロック — HTTP ステータスコードを組み込んだ
NodeblocksError(BlockErrorパターンの例外)
ブロックチェーンは lift(orThrow(errorMap, successMap)) で終了します。
errorMap—BlockErrorのサブクラスを HTTP ステータスコードに対応付けますsuccessMap— ペイロードパスからレスポンスデータを抽出します(例:[['context', 'data', 'identity']])
lift(orThrow(
[[ProfileNotFoundBlockError, 404], [ProfileBlockError, 500]],
[['context', 'data', 'profile']]
))
errorMap が空([])の場合、一致しないエラーはそのまま再スローされます。これは、ブロックがすでにステータスコード付きの NodeblocksError を返す場合(Identity ルート)に使用されます。
完全なターミネーターパターンについては、ルート » を参照してください。
🔧 共通パターン
withLogging
本番環境のルートでは、可観測性のためにブロックステップをラップすることがよくあります。
flatMapAsync(withLogging(applyPayloadArgs(getIdentityById, paths, 'rawIdentity')))
ブロックターミネーター
blocks モジュールの一部の関数は、ハンドラー形式のターミネーターです。Result<RouteHandlerPayload, Error> を受け取り、HTTP レスポンスを組み立てます。たとえば deleteIdentityTerminator は { statusCode: 204 } を返します。
compose(
applyPayloadArgs(deleteIdentity, paths, 'deleteIdentity'),
lift(deleteIdentityTerminator)
)
新しいルートでは lift(orThrow(...)) を優先してください。ブロックターミネーターは、レガシーなレスポンス形状または特殊なレスポンス形状のために存在します。
mapMatchingErrorToFalse
認証ルートでは、特定のブロックエラーをチェーンの失敗ではなく ok(false) に変換するために使用します。
import { blocks, primitives } from '@nodeblocks/backend-sdk';
const { mapMatchingErrorToFalse } = primitives;
const { checkEmailIsUniqueInIdentities, AuthenticationConflictError } = blocks;
const safeCheck = mapMatchingErrorToFalse(checkEmailIsUniqueInIdentities, [
AuthenticationConflictError,
]);
// AuthenticationConflictError → err(...) ではなく ok(false)
⚡ パラメータ駆動ブロックの例外
ほとんどのブロックはドメインデータのみ(db、ID、DTO)を受け取ります。一部のモジュールは applyPayloadArgs で抽出した Express の型も受け取ります。
| モジュール | 追加パラメーター |
|---|---|
OAuth(blocks/oauth/*) | Request、Response |
Authentication(blocks/authentication.ts) | Request(ヘッダー、フィンガープリント) |
Utils(blocks/utils.ts) | Response(redirectTo) |
これらのブロックも同じ方法で接続します。applyPayloadArgs がペイロードから context.request または context.response を抽出します。
🔄 ブロックとハンドラーの比較
| 観点 | ブロック | ハンドラー |
|---|---|---|
| 入力 | 抽出されたパラメーターのみ | 完全な RouteHandlerPayload |
| 接続方法 | applyPayloadArgs(block, paths, key) | compose() に直接渡す |
| テスト容易性 | プレーンな引数を渡す | ペイロードフィクスチャが必要 |
パラメータ駆動のロジックにはブロックを使用し、複数のペイロードフィールドへ直接アクセスする必要がある関数にはハンドラーを使用します。完全な例と WebSocket ハンドラーについては、ハンドラー » を参照してください。
📦 SDK ブロックモジュール
ブロックは、src/blocks/index.ts に対応する SDK の blocks 名前空間でドメイン別に整理されています。
| モジュール | 説明 | リファレンス |
|---|---|---|
| Address | 住所検索と正規化 | SDK のみ — ドキュメント準備中 |
| Authentication | 認証とトークンのロジック | 認証ブロック » |
| Avatar | アバターの正規化とファイル管理 | アバターブロック » |
| Chat | メッセージングとリアルタイム通信 | チャットブロック » |
| Common | 共通ブロックユーティリティ | 共通ブロック » |
| File Storage | 安全なファイル操作と署名付き URL | ファイルストレージブロック » |
| Identity | ID ライフサイクルとセキュリティ管理 | ID ブロック » |
| Location | 階層型ロケーション管理 | ロケーションブロック » |
| Mongo | MongoDB データベース操作とユーティリティ | Mongo ブロック » |
| Notification | 通知の作成と配信 | SDK のみ — ドキュメント準備中 |
| OAuth | サードパーティ OAuth プロバイダーとの統合 | OAuth ブロック » |
| Order | 注文のクエリとフィルタリング | 注文ブロック » |
| Organization | 組織とワークスペースのロジック | 組織ブロック » |
| Product | 商品管理操作 | 商品ブロック » |
| Profile | プロフィールの関係性とソーシャルエンゲージメント | プロフィールブロック » |
| Utils | リダイレクトヘルパーとパスワード生成 | SDK のみ — ドキュメント準備中 |
➡️ 次のステップ
ルート » でブロックをハンドラーチェーンに合成する方法を学ぶか、表にあるドメインブロックのリファレンスを参照してください。