ユニオン型は、動作しないクラスのコレクションがあるが、すべて同じように動作させる必要がある場合に役立ちます。.
Swimlaneのソフトウェア開発者として、私は時折、自分自身やチームメンバーだけでなく、製品の有効性向上にも役立つ新しいことを学ぶ機会に恵まれます。最近携わっていたプロジェクトでは、監査ログを追加するという課題がありました。要件は、データベースコレクションへのすべての変更(作成、更新、削除など)を新しいコレクション「AuditLog」に記録し、各レコードに発生したアクションのラベルを付けるというものでした。実にシンプルです。.
しかし、Updateアクションには複雑な点がありました。ワークフローエンジン内のオブジェクトはそれぞれ異なる方法で更新される可能性があり(例:ユーザーの有効化、無効化、ログイン、プラグインのアップグレードなど)、Update関数自体をログ記録ロジックで煩雑にしたくなかったのです。そこで、ドキュメントの変更前後の状態を引数として受け取る関数を作成する必要がありました。そして、ドキュメントの変更内容に基づいて、変更を行ったアクションを判別します。しかし、これらの引数の型はどのようなものにすべきでしょうか?
クラス AuditLogsService { 非同期ログ(前: ???、後: ???): Promise { //... } }
一つの解決策はインターフェースを使うことでした。私たちのドキュメントには基底クラスもインターフェースも実装されていないので、これはシンプルに思えました。BaseDocumentインターフェースを作成し、8つのドキュメントクラスすべてにそれを実装します。そうすれば、beforeとafterをインターフェースに型付けできます。.
インターフェース AuditLogable {}
この解決策の問題点は、どのドキュメントクラスもプロパティを共有しておらず、メソッドも持っていないため、インターフェースが意味をなさなくなることです。まあ、これは完全に正しいわけではありません。ドキュメントクラスは「id」フィールドを共有しています。つまり、たった一つのプロパティしか持たずメソッドを持たないインターフェースを実装するために、8つの異なるクラスを修正することになります。.
インターフェース AuditLogable { id: string; }
インターフェースにログメソッドを追加して、各ドキュメントタイプが独自のログ動作を定義できるようにするのはどうでしょうか?これは正しいオブジェクト指向ではないでしょうか?しかし、今では8つのログメソッドを実装しなければならず、それぞれがほとんど同じような動作をします。インターフェースではなく基底クラスを使ってDRY(Don't Repeat Yourself)にまとめるのはどうでしょうか?なぜなら、各ログメソッドの動作は たいてい 同様です。例えば、プラグインは「作成」アクションを持たない唯一のドキュメントタイプです。代わりに「インストール」アクションと呼びます。.
この問題へのアプローチが逆になっていることに気づきました。ドキュメントは監査ログを記録するのではなく、ドキュメント自身に監査ログが記録されるようにするべきです。そうすれば、意味のないインターフェースや分かりにくい抽象化を実装することなく、すべてを単一の関数で記述できます。.
幸いなことに、Typescriptにはこのパターンを簡単に構築する方法があります。それがユニオン型です。ユニオン型を使えば、プロパティや動作を抽象化する必要がなくなります。つまり、次のように記述できます。,
“「引数 'before' は、監査ログに記録できるいずれかの型です。」 type AuditLogable = User | Plugin | Playbook; // … class AuditLogsService { async log(before: AuditLogable, after: AuditLogable): Promise { //... } }
実行時に型情報が失われるため、前後の引数として渡すユニオン内の型を示す追加のパラメータも渡す必要があります。.
enum SwimType = { user, plugin, playbook, // ... } class AuditLogsService { async log(before: AuditLogable, after: AuditLogable, type: SwimType): Promise { //... } }
これで、AuditLogging を単一の関数と新しいユニオン型で実装できました。ログに記録されるドキュメントを変更する必要がないため、ロガーに新しいクラスを追加するのは、AuditLogable に追加し、必要な動作を単一のログ関数に追加するだけです。すべてが簡単にテストでき、理解も容易です。.

