はじめての effect-ts
Intro
こんにちは、Kaiです。
今回のブログ投稿はTypeScriptについての続きで、effect-ts
ライブラリ(Effect)を使った、より型安全な開発についてお話しします。
Effectは非常に豊富な機能を持つライブラリであり、すべての機能を一つのブログ投稿で紹介することは不可能です。そのため、このブログではEffectがどのようなものか、そして開発中にどのように安全なアプリケーションを作る手助けをしてくれるかについて簡単に紹介します。
Effectとは何か
Effectは何をするのでしょうか?
公式サイトによると、Effectは以下のことを可能にします:
- イミュータブルデータ構造の定義
- 非同期キュー&パブサブ
- 設定&依存関係管理
そして、バンドルサイズを小さく保つための多くの機能を含んでいます:
- データの検証&シリアライズ
- CLI&HTTPアプリケーション用フレームワーク
- 全てのプラットフォームに対応した強力な抽象化
また、強力なエラーハンドリング機能も持っています:
- 値としての型安全なエラー
- 強力なリトライ&リカバリAPI
- ロギング&トレーシングのためのツール
さらに、多くの機能があります...
一方で、いくつかの注意点もあります:
- 学習曲線: Effectを学ぶのはかなり難しいですが、生産的になるのにそれほど時間はかかりません。TypeScriptを学ぶのに似ていますが、価値があります。
- 異なるプログラミングスタイル: 高レベルの型安全性、組み合わせ可能性、ツリーシェイカビリティを確保するため、Effectのプログラミングスタイルは普段のスタイルとは異なるかもしれません。
- 広範なAPI: Effectにはあらゆる状況に対応するAPIがあります。すべてを学ぶのには時間がかかりますが、最も一般的なものをいくつか覚えるだけで十分です。
Effectは内部的に関数型プログラミングの原則とパターンを使用しますが、単に賢いPromiseとして使用することで、関数型プログラミングの存在を忘れても問題ありません。
前述したように、Effectのすべての側面を一つのブログ投稿でカバーすることは不可能なので、基本的な部分と開発フェーズでのコードの型安全さにどのように役立つかについて紹介します。
EffectなしのTypeScript
次の関数シグネチャを見てみましょう。
const getUserById = (id: string): Promise<User> => { /** */ }
何をするのでしょうか?非常に明確です: id
を渡すと、有効なUser
を返します。
多くのTypeScript開発者にとって、このシグネチャは普通で「正しい」と思われるでしょう。
しかし、実際にはそうではありません。いくつかの問題があります。
1. どのようなエラーがスローされるのか?
多くのことがエラー出る可能性があります:
- ユーザーが見つからない
- 接続がない
- 認証がない
- 無効なユーザー
しかし、型にはそのことは表現されていません:
const getUserById = (id: string): Promise<User> => { /** */ }
このシグネチャは、string
を渡すと、Promise<User>
を返すと言っています。TypeScriptはそれで問題ないとし、ただUser
を返します。
そして、何かが間違った場合は単にクラッシュします。
この関数が何をスローされるのかを知る唯一の方法は、その実装を見ることです。これは理想的ではありません。
User
はどこから来るのか?
2. 一般的には、APIやデータベースとやり取りしているが、接続はどこで開かれますか?それは閉じられますか?認証情報はどこにありますか?
これは明確ではありません。
3. このコードをどうテストするのか?
getUserById
関数をテストするように求められた場合、どこから始めますか?
この関数はstring
を渡すことしかできません。したがって、多くのstring
を渡し、APIリクエストをインターセプトしたり、データベースパラメータをどこかから変更したりして、さまざまな状況をシミュレートすることになります。
すぐにかなり複雑になります。
Effectを使うとき
Effectは、TypeScriptの型システムの全力を活用してこの複雑さを管理します。
前の関数に戻りましょう:
const getUserById = (id: string): Promise<User> => { /** */ }
Effectの最初のステップはEffect
型です。
Effect<A, E, R>
は、関数の戻り値(A
)、エラー(E
)、依存(R
)を記述します。
まず、戻り値の型をEffect
に変更してみましょう:
--const getUserById = (id: string): Promise<User> => { /** */ }
++const getUserById = (id: string): Effect<User> => { /** */ }
これは最初のステップです:関数の戻り値の型をEffect
に変更することです。
普通のTypeScriptとあまり変わりません。
Effectは型シグネチャで明示的なエラーをどのように提供するか
前に挙げた可能なエラーを戻りましょう
- ユーザーが見つからない
- 接続がない
- 認証がない
- 無効なユーザー
Effect
はこれらのエラーを明示的に、型シグネチャに直接表現することができます。
E
パラメータを使用します:
const getUserById = (id: string): Effect<
User,
MissingUser | MissingConnection | MissingAuthentication | InvalidUser
> => { /** */ }
これで何が間違う可能性があるのかが明確になりました。
他の開発者は関数の実装を読むことなく、すべてのthrow
やAPIリクエストやデータベース接続に関連する可能性のある問題を特定することができます。
型シグネチャでの明示的な依存
ユーザーを取得するためのデータベース接続はどうでしょうか?
この情報も明示的にすることができます。Effect
のR
パラメータを使用します:
const getUserById = (id: string): Effect<
User,
MissingUser | MissingConnection | MissingAuthentication | InvalidUser,
DatabaseService
> => { /** */ }
これでこの関数についてすべてが完全にわかります。すべての複雑さを型シグネチャに抽出しました。
TypeScriptは最終的にJavaScriptにトランスパイルされますが、TypeScriptコンパイラとEffectの追加の安全層を使用することで、開発フェーズ中にエラーを早期にキャッチすることができます。
次のステップ
ここでは関数シグネチャについて話し、その関数のユーザーに対してどのように明確にするかについて説明しました。
Effectについて話すべきことはまだたくさんあります。
最初は複雑に見えるかもしれませんが、アプリケーションのロジックが成長するにつれて、コードの複雑さは通常の方法と比べて低いです。
公式ドキュメントでEffectとその使用法についてさらに探索することができます。(https://effect.website/)
または次のブログ投稿を待ってください!
参考
- Effect – The best way to build robust apps in TypeScript
- Next-level type safety with Effect: an intro
- Complete introduction to using Effect in Typescript | Sandro Maglione
- antoine-coulon/effect-introduction: Effect introduction about the whys, helping transitioning from raw TypeScript to Effect TypeScript
Discussion