【実務・中級編】Hackの『Newtype』パターン:型エイリアスを活用したドメイン駆動設計 – Hack言語 コア・静的型システムとHHVMのアーキテクチャ解析バイブル

プリミティブへの決別:Hack `newtype` でドメインモデルを「型」に閉じ込める

PHPの世界では、`string` や `int` は単なるデータの容れ物に過ぎない。しかし、大規模システムの開発現場において、`string` が「ユーザーID」なのか「メールアドレス」なのか「トークン」なのかを型レベルで区別できないことは、致命的なバグの温床だ。

私はHHVMの深淵で、何千もの型チェックの失敗を見てきた。その結論は明確だ。「プリミティブな型をそのままドメインロジックに晒すな」。

今日は、Hackの `newtype` を使い、コンパイル時にドメインの整合性を保証する「型駆動設計」の奥義を授ける。

—

1. なぜ `type` ではなく `newtype` なのか

Hackには `type`(型エイリアス)と `newtype`(不透明型)がある。
`type UserID = string;` と宣言した場合、それは単なる別名に過ぎず、型チェッカー(hh_client)は `string` と `UserID` を同列に扱う。これでは、誤って `ProductID` を `UserID` として渡してもエラーを吐かない。

一方、`newtype` は不透明(Opaque)だ。モジュールの外からは、それが何で構成されているかを知ることはできない。これにより、型システムは「契約」として機能し始める。

—

2. 実装パターン:ドメインの境界を定義する

以下のコードは、`UserID` をプリミティブから解放するための最小かつ最強の構成例だ。

namespace App\Domain;

// 型定義は公開するが、その実体(string)は隠蔽する
newtype UserID = string;

final class UserIdentity {
// コンストラクタで不変性を担保
public function __construct(private string $value) {}

// ファクトリメソッドでバリデーションを強制
public static function fromString(string $val): UserID {
if (strlen($val) < 8) { throw new \InvalidArgumentException('UserID must be at least 8 chars'); } // ここで型キャストではなく、opaqueなUserIDとしてラップして返す return $val :> UserID;
}
}

重要なアーキテクチャ上の洞察

  • `:> (As Operator)` の活用: `newtype` の内部実装へアクセスできるのは、定義されたファイル内のみだ。外部からは値の改竄ができない。
  • パフォーマンス: `newtype` はコンパイル時にのみ評価される「型」であるため、HHVMの実行時オーバーヘッドはゼロだ。クラスでラップする手法(Value Object)と異なり、メモリ消費も増えない。型システムの恩恵をタダで受けられるのだ。

—

3. 非同期API連携での活用:型安全な境界線

外部APIとの連携において、JSONの `string` をそのまま関数に渡すのは怠慢だ。`newtype` を使えば、受信したデータをドメイン層へ引き渡す前に、型レベルでバリデーション済みであることを保証できる。

namespace App\Infrastructure;

use App\Domain\UserID;

async function fetchUserData(UserID $uid): Awaitable {
// この関数が呼ばれた時点で、uidは既に検証済みのUserIDであることが確定している
// $uidが単なるstringでないことが型システムにより保証されているため、
// バリデーション漏れによるバグは物理的に不可能になる
}

// 利用側のコード
async function main(): Awaitable {
$raw_id = $_GET[‘id’] ?? ”;
// ここで境界を超えてUserIDを生成
$uid = UserIdentity::fromString($raw_id);
await fetchUserData($uid);
}

—

4. チーフアーキテクトからの忠告

この手法を導入する際、以下の3点だけは心に刻んでおいてほしい。

1. ライブラリの境界でキャストする: `newtype` はあくまで内部ドメインの整合性を高めるためのものだ。外部(データベースやJSONデコーダー)と通信する際は、必ず境界(Boundary)で `newtype` への変換・検証を行え。
2. 無理な抽象化を避ける: あらゆるプリミティブを `newtype` にする必要はない。ビジネスロジックの核心にある、取り違えが致命的なIDやキーに対してのみ適用せよ。
3. HSL(Hack Standard Library)の型を活用せよ: HSLには堅牢なコレクション型が備わっている。`newtype` と `vec` や `dict` を組み合わせることで、ドメインモデルの表現力は飛躍的に向上する。

結論

`newtype` を使った設計は、単なるコードの美学ではない。それは、「型チェッカーにドメインのルールを教え込む」ということだ。

プログラムを実行する前に、IDEが赤い波線で「それはUserIDではない」と教えてくれる。その安心感こそが、大規模開発を支える真のパフォーマンスであると知れ。

コードは書くものではない。構築するものだ。今日から君の型定義を、より厳格なものへと進化させろ。

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