タイプスクリプトによるリポジトリパターン
27541 ワード
ハイ👋
この記事では、リポジトリのパターンについて話をし、認証を処理するためのリポジトリをどのように実装しているかを示しています.それでは、それらのテストの書き方を教えてあげましょう.また、依存関係の注入、抽象化などの他のトピックを簡単に行きます.など
リポジトリとは何かを知らない場合は、以下のような空想的定義を行います.
今、私はより人間の読みやすいバージョンを与えるようにします、そして、私は我々の認証機能を例として使います.
私は、ユーザーがGoogleを私のアプリにサインするのを使用することができて欲しいです、流れはこのようなものです: ユーザーがGoogleでログインします. Googleから返されたトークンを取得し、バックエンドに送信します. バックエンドはすべての将来のリクエストに使用できるアクセストークンを返します. トークンをローカルに保持し、ユーザをサインしておく. このことから、3つのデータソースが必要です. それから、我々はある
The
そして、どのようにリポジトリは、実装の詳細を要約すると、データソースも同じことを行う必要があります.あまりにもインタフェースを動かしましょう.
実装するクラスを作成します
フィールドは、リポジトリからデータソースにアクセスできるように意味をなさないため、プライベートにする必要があります.
また、コンストラクタで依存関係を渡すことが重要です.まず、データソースの実装を書くことはできません.また、このような依存関係を注入することで、データソースの模擬実装を通過できます.
今、私たちは実際の実装を書くことができます
今までのところ何もエラー処理していません.私たちの倉庫は、現時点では非常に安全ではない、何も間違って行くことができます.例えば、ユーザのネットワークは不安定であるかもしれません、そして、若干の要請はtimeoutです.または何かが間違ってGoogle Authで行くことができる、あなたは知っている.これは、データ層には非常に厄介な予測不可能な世界です.
はい、あなたは倉庫のすべての呼び出しをラップすることができます
リポジトリパターンを使用する別の利点があります.リポジトリ内のすべてのエラーをキャッチできます.
ネットワークエラー:ユーザーがオフラインまたは接続が不安定な場合
一般的なエラー 私たちは多くの点でこれらのエラーを表すことができます.
戻り値のタイプを変えましょう
ここでは楽しい部分は、我々は我々の倉庫を実装して以来、我々はそれを試して、非常に簡単です今テストします.すべての依存関係がコンストラクタに注入されるので、私たちは単にモック依存関係を作成することができます、そして、それらと共に倉庫をinstanciateします.あなたは冗談を使ってこれを得ることができますが、より良い方法があると思います.
とてもクールな図書館がありますts-mockito , あなたがコードに依存注入を使用するとき、それは非常によく働きます(現在我々がしているように).それはあなたがモック、スタブ関数呼び出しを作成し、他の多くのクールな機能は、間違いなくそれをチェックアウトすることができます!テストするために使います
この長いテストファイルを見てください.
ここでは、この記事を通して学んだ主なポイントです.
リポジトリ:複数のデータソースをグループ化し、APIを公開するコンポーネント/クラスは、すべての実装の詳細を隠します.また、“エラーバスター”として、それはすべての野生の例外をキャッチし、失敗の場合は、通常のオブジェクトを返します.
抽象化(インターフェイスを使用する):インターフェイスを使用すると、同じAPI(例えばモックと実際の実装)に対して複数の実装を持つことができます.また、変更を容易にする
依存性注入:この記事のDIの詳細については知りませんでしたが、クラスコンストラクタに依存性を注入する方法を見ました.また、これを行うことなく、すべての依存関係を実装するまで、リポジトリを実装する能力を持っていません.あなたは、抽象化とDIを互いに補完すると言うことができます.
役に立つライブラリ
ts-mockito : モッキング、および一般的なテストを支援する非常に便利なライブラリです.
fp-ts : すべての機能プログラミンググッズを入力するライブラリです.私たちは 私はあなたが良い読んで、さよならを願っています.
この記事では、リポジトリのパターンについて話をし、認証を処理するためのリポジトリをどのように実装しているかを示しています.それでは、それらのテストの書き方を教えてあげましょう.また、依存関係の注入、抽象化などの他のトピックを簡単に行きます.など
リポジトリ
リポジトリとは何かを知らない場合は、以下のような空想的定義を行います.
Repositories are classes or components that encapsulate the logic required to access data sources. They centralize common data access functionality, providing better maintainability and decoupling the infrastructure or technology used to access databases from the domain model layer.
今、私はより人間の読みやすいバージョンを与えるようにします、そして、私は我々の認証機能を例として使います.
私は、ユーザーがGoogleを私のアプリにサインするのを使用することができて欲しいです、流れはこのようなものです:
GoogleAuth
AuthApi
: それはバックエンドと通信するLocalStorage
: ログインしているユーザを保持する場所AuthRepository
内部的にこれらの3つのデータソースを使用し、認証を処理するために使用できる単純なAPIを公開します.私たちはすぐにこれを実装する方法を見るでしょう、しかし、これは倉庫が私の単純な視点で働く方法です.抽象化はあなたの友人
The
AuthRepository
非常に良い抽象例です.それはそれを使用する他のすべてのパーティーからの実装の詳細を非表示にします.例えば、反応コンポーネントはこの倉庫を使用することができて、ちょうど呼び出しによってユーザに署名することができますauthRepo.signInWithGoogle()
. コンポーネントがどのようにサインインを行うのか気にしない、または知っていない、それは我々が作業を行うには、3つのデータソースを使用することを知りません.この抽象化は非常に便利です、例えば、我々がコンポーネントをテストしたいならば、我々はちょうど倉庫を模擬することができて、それを確かめることができますsignInWithGoogle
が呼び出されました.私たちは、私たちがすでにテストを書いていたので、サインinが働くと確信するでしょうAuthRepository
. この抽象化を書きましょう.interface IAuthRepository {
signInWithGoogle(): Promise<User>;
}
NOTE1: The 'I' in 'IAuthRepository' stands for Interface (duh), and It's just a naming convention.
NOTE2: There should be more function declarations in the interface, like signOut() ...etc, but I wrote only one for sake of simplicity.
そして、どのようにリポジトリは、実装の詳細を要約すると、データソースも同じことを行う必要があります.あまりにもインタフェースを動かしましょう.
interface IGoogleAuth {
// returns a token
signIn(): Promise<string>
}
interface IAuthApi {
signInWihGoogle(token: string): Promise<User>;
}
interface ILocalStorage {
write(key: string, value: string): Promise<null>;
read(key: string): Promise<string | null>;
}
あなたはUser
任意の方法を入力します.簡単にするには、次のように使います.type User = {
accessToken: string
}
今、我々は、我々が実装を開始するために必要なすべてを持っているAuthRepository
実装
実装するクラスを作成します
IAuthRepository
, また、すべての関数宣言に対して具体的な実装を行います(この場合、1つだけです).class AuthRepository implements IAuthRepository {
signInWithGoogle(): Promise<User> {
throw new Error("Method not implemented.");
}
}
クラス内のデータソースへのアクセスが必要なので、それらに依存するようにしましょう.class AuthRepository implements IAuthRepository {
private _authApi: IAuthApi;
private _googleAuth: IGoogleAuth;
private _localStorage: ILocalStorage;
constructor(
authApi: IAuthApi,
googleAuth: IGoogleAuth,
localStorage: ILocalStorage,
) {
this._authApi = authApi;
this._googleAuth = googleAuth;
this._localStorage = localStorage;
}
signInWithGoogle(): Promise<User> {
throw new Error("Method not implemented.");
}
}
プライベートフィールドを追加し、コンストラクタの中で初期化しました.フィールドは、リポジトリからデータソースにアクセスできるように意味をなさないため、プライベートにする必要があります.
また、コンストラクタで依存関係を渡すことが重要です.まず、データソースの実装を書くことはできません.また、このような依存関係を注入することで、データソースの模擬実装を通過できます.
今、私たちは実際の実装を書くことができます
signInWithGoogle()
:async signInWithGoogle(): Promise<User> {
const token = await this._googleAuth.signIn();
const user = await this._authApi.signInWihGoogle(token);
await this._localStorage.write('accessToken', user.accessToken);
return user;
}
それは、私たちのリポジトリが実装されます.すぐにテストを開始できます.でも前に...エラー処理
今までのところ何もエラー処理していません.私たちの倉庫は、現時点では非常に安全ではない、何も間違って行くことができます.例えば、ユーザのネットワークは不安定であるかもしれません、そして、若干の要請はtimeoutです.または何かが間違ってGoogle Authで行くことができる、あなたは知っている.これは、データ層には非常に厄介な予測不可能な世界です.
はい、あなたは倉庫のすべての呼び出しをラップすることができます
try catch
ブロック.本当にやりたいですか?まず第一に、あなたはそれを行う義務がありません、どんな規則時間エラーもないので、あなたに思い出させるどんな規則もあなたに思い出させません.エラーをキャッチすることを忘れたので、クラッシュするアプリをしたくない.第二にtry catch
ちょうど醜いです.リポジトリパターンを使用する別の利点があります.リポジトリ内のすべてのエラーをキャッチできます.
try catch
再び外に.まず、認証中に発生するすべてのエラーを指定する必要があります.ネットワークエラー:ユーザーがオフラインまたは接続が不安定な場合
一般的なエラー
enum AuthError {
general,
network,
}
それから、我々は我々の機能を戻す必要がありますAuthError
Aの代わりにUser
を返します.最初、私はそれを組合に帰ろうとしましたAuthError | User
, しかし、私はリターン値の正確なタイプをチェックする良い方法を見つけませんでした.私はタイプガードを使うことができました、しかし、私はより良い方法があると思います.The fp-ts ライブラリは、多くの機能プログラミンググッズを提供していますEither
種類:type Either<E, A> = Left<E> | Right<A>
見てわかるようにEither
のいずれかを左または右のインスタンスです.左は失敗のために使われます、そして、権利は成功のために使われます(see the docs) .戻り値のタイプを変えましょう
signInWithGoogle
関数を返すEither<AuthError, User>
, インターフェイスと実装されているクラスの両方で行うべきです. interface IAuthRepository {
signInWithGoogle(): Promise<Either<AuthError, User>>;
}
実装では、right(user)
の代わりにuser
万事うまくいくならば、我々は帰りますleft(error)
:async signInWithGoogle(): Promise<Either<AuthError,User>> {
try {
const token = await this._googleAuth.signIn();
const user = await this._authApi.signInWihGoogle(token);
await this._localStorage.write('accessToken', user.accessToken);
return right(user);
} catch (e) {
// Check for the type of error here
// and return the corresponding value
// if (e is a network error)
// return left(AuthError.network);
return left(AuthError.general);
}
}
今、我々が呼ぶときはいつでもsignInWithGoogle
, 返り値がleft
or right
, を実行します.const result = await authRepo.signInWithGoogle();
if (isRight(result)) {
// The sign in succeeded
const user = result.right;
// do something with the user
} else {
// The sign in failed
const error = result.left;
// do something with the error
}
このメソッドを使用すると、すべての例外は、リポジトリに到着したときに伝播を停止し、後で処理できる正規オブジェクトに変換されます.テスト
ここでは楽しい部分は、我々は我々の倉庫を実装して以来、我々はそれを試して、非常に簡単です今テストします.すべての依存関係がコンストラクタに注入されるので、私たちは単にモック依存関係を作成することができます、そして、それらと共に倉庫をinstanciateします.あなたは冗談を使ってこれを得ることができますが、より良い方法があると思います.
エントランス
とてもクールな図書館がありますts-mockito , あなたがコードに依存注入を使用するとき、それは非常によく働きます(現在我々がしているように).それはあなたがモック、スタブ関数呼び出しを作成し、他の多くのクールな機能は、間違いなくそれをチェックアウトすることができます!テストするために使います
AuthRepository
.この長いテストファイルを見てください.
import { left, right } from "fp-ts/lib/Either";
import { anything, instance, mock, reset, verify, when } from "ts-mockito";
/// Also import the interfaces and other things...
/// Create mock dependencies
const MockGoogleAuth = mock<IGoogleAuth>();
const MockAuthApi = mock<IAuthApi>();
const MockStorage = mock<ILocalStorage>();
/// Instatiate AuthRepository with the mocks
const authRepo: IAuthRepository = new AuthRepository(
instance(MockAuthApi),
instance(MockGoogleAuth),
instance(MockStorage)
);
// Reset the mocks before each test
// So tests won't be dependent of each other
beforeEach(() => {
reset(MockAuthApi);
reset(MockGoogleAuth);
reset(MockStorage);
});
// Testing `signInWithGoogle`
describe("signInWithGoogle", () => {
// Test case 1
test("should persist and return the user if all goes well", async () => {
// arrange
const googleToken = "googleToken";
const user: User = { accessToken: "accessToken" };
// When signIn is called on MockGoogleAuth, resolve with `googleToken`
when(MockGoogleAuth.signIn()).thenResolve(googleToken);
// When signInWithGoogle is called on MockAuthApi, resolve with `user`
when(MockAuthApi.signInWihGoogle(googleToken)).thenResolve(user);
// act
const result = await authRepo.signInWithGoogle();
// assert
// the result should be `right(user)`
expect(result).toStrictEqual(right(user));
// MockGoogleAuth.signIn should be called once
verify(MockGoogleAuth.signIn()).once();
// MockAuthApi.signInWithGoogle should be called once, with `googleToken`
verify(MockAuthApi.signInWihGoogle(googleToken)).once();
// The access token should be persisted
verify(MockStorage.write("accessToken", user.accessToken)).once();
});
test("should return an auth error if something wrong happened", async () => {
// arrange
// Make it so GoogleAuth throws an exception
when(MockGoogleAuth.signIn()).thenReject(new Error("some error"));
// act
const result = await authRepo.signInWithGoogle();
// assert
// the result should be `left(AuthError.general)`
expect(result).toStrictEqual(left(AuthError.general));
// MockGoogleAuth.signIn should be called once
verify(MockGoogleAuth.signIn()).once();
/// MockAuthApi.signInWithGoogle should never be called
verify(MockAuthApi.signInWihGoogle(anything())).never();
// No access token should be persisted
verify(MockStorage.write("accessToken", anything())).never();
});
});
うまくいけば、このテスト戦略は明確でした、そして、それはあなたに将来あなたのコードをテストする方法のアイデアを与えました.それはかなり簡単だと思う.結論
ここでは、この記事を通して学んだ主なポイントです.
リポジトリ:複数のデータソースをグループ化し、APIを公開するコンポーネント/クラスは、すべての実装の詳細を隠します.また、“エラーバスター”として、それはすべての野生の例外をキャッチし、失敗の場合は、通常のオブジェクトを返します.
抽象化(インターフェイスを使用する):インターフェイスを使用すると、同じAPI(例えばモックと実際の実装)に対して複数の実装を持つことができます.また、変更を容易にする
AuthApi
たとえば、残りの実装からGraphSQLに.など.これは、開発プロセスをスピードアップ、特に場合は、チームとして働いている.あなただけのインターフェイスを設定し、それに依存する他のものを実装を開始します.あなたがこの記事で見たように、我々はAuthApi
, その他の依存関係もない.依存性注入:この記事のDIの詳細については知りませんでしたが、クラスコンストラクタに依存性を注入する方法を見ました.また、これを行うことなく、すべての依存関係を実装するまで、リポジトリを実装する能力を持っていません.あなたは、抽象化とDIを互いに補完すると言うことができます.
役に立つライブラリ
ts-mockito : モッキング、および一般的なテストを支援する非常に便利なライブラリです.
fp-ts : すべての機能プログラミンググッズを入力するライブラリです.私たちは
Either
この記事のタイプは、それは多くを提供しています.Reference
この問題について(タイプスクリプトによるリポジトリパターン), 我々は、より多くの情報をここで見つけました https://dev.to/aouahib/the-repository-pattern-with-typescript-3ibnテキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol