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

🔧 コンポジションユーティリティ

Nodeblocks SDKは、ハンドラーの構成、非同期操作の処理、複雑なビジネスロジックパイプラインの構築のための必須ユーティリティを提供します。これらのユーティリティにより、シンプルな関数を予測可能な方法で組み合わせて、複雑でエラー安全なパイプラインを構築できます。


🎯 概要

コンポジションユーティリティは、堅牢で保守可能なビジネスロジックの構成要素を提供します。関数コンポジションとエラー伝播を処理し、操作を安全かつ効率的にチェーンできます。

主要機能

  • 関数コンポジション: 複数の関数を単一のパイプラインに結合
  • エラー安全操作: 自動エラー伝播と処理
  • 非同期サポート: 非同期操作のシームレスな処理
  • 型安全性: 適切な型付けによる完全なTypeScriptサポート

🔗 基本コンポジション

compose

複数の関数を単一のパイプラインに結合し、右から左に実行します。

import { primitives } from '@nodeblocks/backend-sdk';

const { compose } = primitives;

const processUser = compose(
validateUser,
saveUser,
formatResponse
);

// 次と同等: formatResponse(saveUser(validateUser(input)))

lift

通常の関数をペイロードコンテキストに持ち上げます。

import { primitives } from '@nodeblocks/backend-sdk';

const { lift } = primitives;

const normalizeUser = (user: User) => {
const { _id, ...normalized } = user;
return normalized;
};

// 関数をコンポジションで動作するように持ち上げ
const handler = compose(
getUserFromDb,
lift(normalizeUser) // ペイロード結果で動作するように
);

mergeData

コンポジションパイプラインの後続ハンドラーで使用するため、ペイロードコンテキストにデータをマージします。

import { handlers } from '@nodeblocks/backend-sdk';

const { mergeData } = handlers;

const enrichUserData = async (payload: RouteHandlerPayload) => {
const profile = await profileService.getProfile(payload.context.data.userId);
return mergeData(payload, { profile });
};

const getUserHandler = compose(
getUserById,
enrichUserData,
formatUserResponse
);

⚡ 非同期コンポジション

flatMapAsync

非同期操作をチェーンし、Resultを返します。

import { primitives } from '@nodeblocks/backend-sdk';

const { flatMapAsync } = primitives;

const createUserPipeline = compose(
validateUserInput,
createUser, // 非同期操作
flatMapAsync(sendWelcomeEmail), // 非同期チェーン
lift(formatResponse)
);

flatMap

同期操作をチェーンし、Resultを返します。

import { primitives } from '@nodeblocks/backend-sdk';

const { flatMap } = primitives;

const processDataPipeline = compose(
loadData,
flatMap(validateData), // 同期チェーン
flatMap(transformData), // さらに同期チェーン
lift(formatOutput)
);

🔄 エラー処理パターン

Result型での作業

import { ok, err, Result } from 'neverthrow';
import { primitives } from '@nodeblocks/backend-sdk';

const { NodeblocksError } = primitives;

const validateAge = (age: number): Result<number, NodeblocksError> => {
if (age < 18) {
return err(new NodeblocksError(400, '18歳以上である必要があります'));
}
return ok(age);
};

const processUser = compose(
extractAge,
flatMap(validateAge), // エラーが自動的に伝播
flatMap(createAccount),
lift(formatResponse)
);

エラー伝播

// エラーは自動的にチェーンを通じて伝播
const userRegistration = compose(
validateEmail, // エラーが発生すると、残りはスキップ
checkEmailExists, // 前のステップが成功した場合のみ実行
createUserAccount, // 前のステップが成功した場合のみ実行
sendConfirmationEmail, // 前のステップが成功した場合のみ実行
lift(registrationSuccessResponse)
);

🎯 高度なコンポジションパターン

条件付きパイプライン

const conditionalProcessing = (condition: boolean) => compose(
loadUser,
condition ? updateUserProfile : identity, // 条件付きステップ
saveUser,
lift(formatResponse)
);

パラレル処理

import { all } from 'neverthrow';

const parallelEnrichment = async (payload: RouteHandlerPayload) => {
const userId = payload.context.data.userId;

// 複数の操作を並列実行
const [profile, preferences, activity] = await Promise.all([
getProfile(userId),
getPreferences(userId),
getActivity(userId)
]);

return mergeData(payload, { profile, preferences, activity });
};

const enrichedUserHandler = compose(
getUserById,
parallelEnrichment,
lift(formatEnrichedResponse)
);

エラー回復

const withFallback = <T, E>(
primary: () => Result<T, E>,
fallback: () => Result<T, E>
) => {
return primary().orElse(() => fallback());
};

const robustDataLoader = compose(
(payload) => withFallback(
() => loadFromPrimaryDB(payload),
() => loadFromCache(payload)
),
lift(formatData)
);

🔧 ユーティリティ関数

pipe

左から右のコンポジション(読みやすさのため):

import { pipe } from '@nodeblocks/backend-sdk';

const processData = pipe(
validateInput, // 最初に実行
transformData, // 次に実行
saveData, // 最後に実行
formatResponse
);

tap

副作用を実行しながら値を通す:

const withLogging = tap((data) => {
console.log('Processing:', data);
});

const loggedPipeline = compose(
loadData,
withLogging, // ログを出力するが値は変更しない
processData,
lift(formatResponse)
);

🎯 ベストプラクティス

1. 小さく構成可能な関数

// 良い - 小さな再利用可能な関数
const validateUser = (user: User) => { /* 検証ロジック */ };
const saveUser = (user: User) => { /* 保存ロジック */ };
const notifyUser = (user: User) => { /* 通知ロジック */ };

const userRegistration = compose(validateUser, saveUser, notifyUser);

// 悪い - 大きなモノリシック関数
const registerUserEverything = (user: User) => {
// すべてのロジックが1つの関数に
};

2. 適切なエラーハンドリング

// 良い - 具体的なエラー型
const validateEmail = (email: string): Result<string, NodeblocksError> => {
if (!email.includes('@')) {
return err(new NodeblocksError(400, '有効なメールアドレスが必要です'));
}
return ok(email);
};

// 悪い - 一般的なエラー
const validateEmail = (email: string) => {
if (!email.includes('@')) {
throw new Error('Invalid email'); // 具体性に欠ける
}
return email;
};

3. 型安全なコンポジション

// 良い - 型安全なパイプライン
interface User { id: string; email: string; }
interface UserProfile { user: User; profile: Profile; }

const enrichUser: (user: User) => Promise<Result<UserProfile, NodeblocksError>>
= async (user) => {
// 実装
};

// 悪い - 型なしコンポジション
const enrichUser = async (user: any) => {
// 型安全性なし
};

➡️ 次へ

エンティティユーティリティについて学習して、データベースエンティティの作成と管理のパターンを理解しましょう!