【入門編】Type Aliasによる「ドメイン駆動設計(DDD)」の型付け:値オブジェクトの表現 – TypeScript コア・型システムの基礎解析バイブル

こんにちは。TypeScriptという言語の深淵へようこそ。

多くの開発者が、TypeScriptを単なる「JavaScriptに型が付いたもの」と誤解しています。しかし、真の使い手にとってTypeScriptは、「ドメインの概念をコードに書き込み、コンパイラを最強のレビューアーに変えるための言語」です。

今日は、DDD(ドメイン駆動設計)の入り口であり、TypeScriptを使いこなすための最大の武器、「ブランド型(Branded Types)」についてお話ししましょう。

—

なぜ「ただのstring」ではいけないのか?

DDDにおいて、プリミティブな型(stringやnumber)をそのまま使うことは「プリミティブ執着(Primitive Obsession)」というアンチパターンとみなされます。

例えば、ユーザーIDとメールアドレスを考えてみましょう。

type UserId = string;
type Email = string;

function sendEmail(id: UserId, email: Email) {
// ここで誤って sendEmail(email, id) と渡してしまっても、
// TypeScriptは何も文句を言いません。
// なぜなら、どちらもただの「string」だからです。
}

コンパイラは「文字列であること」しか確認しません。しかし、ビジネスドメインの世界では「ユーザーID」と「メールアドレス」は全く別の概念ですよね。ここを型レベルで分離する技術が、ブランド型です。

—

ブランド型(Branded Types)の魔法

ブランド型とは、TypeScriptの構造的部分型(Structural Typing)の性質を逆手に取り、型に「刻印(Brand)」を押す手法です。

実装の基本形

// インターセクション型(&)を使って、型に「看板」をぶら下げます
type UserId = string & { readonly __brand: ‘UserId’ };
type Email = string & { readonly __brand: ‘Email’ };

// 値を生成するヘルパー関数
function createUserId(id: string): UserId {
return id as UserId; // コンパイラを説得して型をキャスト
}

このコードのポイントは、`__brand` というプロパティです。実行時にはこのプロパティは存在しません(型システム上の幻影です)。しかし、コンパイル時にはこれが「壁」として機能します。

なぜこれで安全になるのか?

function sendEmail(id: UserId, email: Email) {
console.log(`Sending to ${id} at ${email}`);
}

const myId = createUserId(“user-123”);
const myEmail = “test@example.com” as Email;

// OK: 型が一致している
sendEmail(myId, myEmail);

// エラー!: 引数が逆だと、ブランドが一致しないためコンパイルエラーになる
// Argument of type ‘Email’ is not assignable to parameter of type ‘UserId’.
sendEmail(myEmail, myId);

どうでしょう? コンパイラが「それはEmailであってUserIdではない」と怒ってくれるようになりました。これで、関数の引数の順番間違いという「よくある事故」を、コードを書いている瞬間に封じ込めることができます。

—

陥りやすい罠:なぜ「readonly」が必要なのか?

初心者がよく忘れるのが `readonly` の付与です。

もし `readonly` を付けないと、TypeScriptは「構造的に互換性があるなら同じ型だ」とみなしてしまい、意図しない場所で型が混ざってしまうリスクがあります。また、`__brand` 自体は値を持たない(空オブジェクトのようなもの)ため、誤って値を書き換えてしまう余地を消しておくのが、堅牢な設計の鉄則です。

—

ドメイン層での活用イメージ

ドメイン駆動設計では、値オブジェクト(Value Object)を定義します。先ほどのブランド型を応用して、コンストラクタでバリデーションを行うのが実務の定石です。

type PositiveNumber = number & { readonly __brand: ‘PositiveNumber’ };

function createPositiveNumber(value: number): PositiveNumber {
if (value <= 0) { throw new Error("値は正数である必要があります"); } return value as PositiveNumber; } // 実行時にバリデーションを行い、型で安全を保証する const price = createPositiveNumber(100); このように、「生成時にだけバリデーションを通し、以降は型が正しさを保証する」という設計は、アプリケーションの信頼性を劇的に向上させます。

—

最後に:型は「ドキュメント」以上の存在

TypeScriptの型は、単なるコード補完のためのツールではありません。それは、あなたが定義したドメインモデルを、コンパイラという「超高速な自動テスト」で守り抜くための最強の設計図です。

「ただのstring」で済ませるのではなく、そこにあなたのビジネスドメインの魂を吹き込んでください。この小さな工夫の積み重ねが、半年後のあなた自身を、そしてチームメイトを救うことになります。

ここをマスターできれば、あなたはもうTypeScriptの中級者を超え、アーキテクトとしての視座を手に入れています。さあ、次はどんな型を定義しましょうか?

タイトルとURLをコピーしました