TypeScriptにおけるブランド型

TypeScriptでエンティティをモデル化する際、次のようなインターフェースを得ることは非常に一般的です。

TypeScript

 

問題点

プロパティの型には意味がありません。型の観点から見ると、User.idOrder.idOrder.yearなどはすべて同じです:数字であり、数字としては互換性がありますが、意味的にはそうではありません。

前の例に従って、エンティティに対して操作を行う関数のセットを作成できます。例えば:

TypeScript

 

これらの関数は、数値の意味に関係なく、任意の引数で任意の数字を受け入れます。例えば:

TypeScript

 

明らかに、それは大きな間違いであり、コードを読むのを避けるのは簡単に思えるかもしれませんが、コードは常に例のように単純ではありません。

同様のことがgetOrdersFilteredにも当てはまります:日と月の値を入れ替えることができ、警告やエラーは発生しません。エラーは、日が12より大きい場合に発生しますが、結果が期待通りでないことは明らかです。

解決策

オブジェクトカリステニクスのルールはそれに対する解決策を提供します:すべてのプリミティブと文字列をラップします(関連プリミティブへの執着に関するアンチパターン)。ルールは、意味を表すオブジェクトでプリミティブをラップすることです(DDDではこれをValueObjectsと呼びます)。

しかし、TypeScriptでは、それにクラスやオブジェクトを使用する必要はありません:年を表すものとは異なる数字が年の代わりに使えないことを保証するために型システムを使用できます。

ブランデッドタイプ

このパターンは、意味を保証するプロパティを追加するために型の拡張性を利用します:

TypeScript

 

このシンプルな行は、数値として機能する新しい型を作成しますが、実際には数値ではなく、年です。

TypeScript

 

解決策の一般化

ブランドごとに型を書くのを避けるために、次のようなユーティリティ型を作成できます。

TypeScript

 

これは、プロパティとの競合を避けるためにブランドプロパティ名としてユニークなシンボルを使用し、元の型とブランドをジェネリックパラメータとして取得します。

これにより、モデルと関数を次のようにリファクタリングできます。

TypeScript

 

この例では、IDEはidUserIdであり、deleteOrderOrderIdを期待しているため、エラーを表示します。

TypeScript

 

トレードオフ

小さなトレードオフとして、XBrandとして使用する必要があります。たとえば、プリミティブから新しい値を作成する際には、const year = 2012 as Yearとしますが、これは値オブジェクトを使用する場合、new Year(2012)に相当します。「コンストラクタ」のように機能する関数を提供することもできます。

TypeScript

 

ブランド型による検証

ブランド型は、特定の型を持つ検証済みデータがあるため、データが有効であることを保証するのにも便利です。ユーザーが検証されたことを信頼できます。

TypeScript

 

Readonlyは必須ではありませんが、検証後にデータが変更されないようにするためには、非常に推奨されます。

まとめ

ブランド型は、次の内容を含むシンプルな解決策です。

  • コードの可読性を向上させる:各引数で使用すべき値が明確になります。
  • 信頼性: コード内の検出が難しい間違いを避けるのに役立ちます; 現在、IDE(および型チェック)が、値が正しい場所にあるかどうかを検出するのに役立ってくれます
  • データ検証: ブランド型を使用してデータが有効であることを確認できます。

ブランド型は、クラスを使用せずに、単に型と関数を使ったValueObjectsの一種のバージョンと考えることができます。

型の力を楽しんでください!

Source:
https://dzone.com/articles/branded-types-in-typescript