コンテナサービス

コンテナサービス

IoCコンテナガイドで説明したように、コンテナバインディングはAdonisJSのIoCコンテナが存在する主な理由の1つです。

コンテナバインディングは、オブジェクトを使用する前に構築するために必要なボイラープレートコードからコードベースをクリーンに保ちます。

次の例では、Databaseクラスを使用する前に、そのインスタンスを作成する必要があります。構築するクラスによっては、すべての依存関係を取得するために多くのボイラープレートコードを記述する必要があります。

import { Database } from '@adonisjs/lucid'
export const db = new Database(
// configやその他の依存関係を注入
)

しかし、IoCコンテナを使用すると、クラスの構築タスクをコンテナにオフロードし、事前に構築されたインスタンスを取得できます。

import app from '@adonisjs/core/services/app'
const db = await app.container.make('lucid.db')

コンテナサービスの必要性

コンテナを使用して事前に設定済みのオブジェクトを解決することは素晴らしいことです。ただし、container.makeメソッドを使用することにはいくつかのデメリットがあります。

  • エディターは自動インポートに優れています。変数を使用しようとすると、エディターが変数のインポートパスを推測できれば、インポートステートメントを自動的に書き込んでくれます。ただし、これはcontainer.make呼び出しでは機能しません。

  • インポートステートメントとcontainer.make呼び出しの組み合わせは、モジュールのインポート/使用に統一された構文がないため、直感的ではありません。

これらのデメリットを克服するために、container.make呼び出しを通常のJavaScriptモジュール内にラップし、importステートメントを使用してそれらを取得できるようにします。

たとえば、@adonisjs/lucidパッケージには、services/db.tsというファイルがあり、このファイルにはおおよそ以下の内容が含まれています。

const db = await app.container.make('lucid.db')
export { db as default }

アプリケーション内では、container.make呼び出しをservices/db.tsファイルを指すインポートに置き換えることができます。

import app from '@adonisjs/core/services/app'
const db = await app.container.make('lucid.db')
import db from '@adonisjs/lucid/services/db'

ご覧のように、私たちはまだコンテナに依存してDatabaseクラスのインスタンスを解決しています。ただし、間接的なレイヤーを追加することで、container.make呼び出しを通常のimportステートメントで置き換えることができます。

container.make呼び出しをラップするJavaScriptモジュールは、コンテナサービスとして知られています。 コンテナとやり取りするほとんどのパッケージには、1つ以上のコンテナサービスが含まれています。

コンテナサービスと依存性の注入

コンテナサービスは依存性の注入の代替手段です。たとえば、Diskクラスを依存関係として受け入れる代わりに、driveサービスにディスクインスタンスを要求します。いくつかのコード例を見てみましょう。

次の例では、@injectデコレータを使用してDiskクラスのインスタンスを注入しています。

import { Disk } from '@adonisjs/drive'
import { inject } from '@adonisjs/core'
@inject()
export default class PostService {
constructor(protected disk: Disk) {
}
async save(post: Post, coverImage: File) {
const coverImageName = 'random_name.jpg'
await this.disk.put(coverImageName, coverImage)
post.coverImage = coverImageName
await post.save()
}
}

driveサービスを使用する場合、drive.useメソッドを呼び出してs3ドライバーを使用したDiskのインスタンスを取得します。

import drive from '@adonisjs/drive/services/main'
export default class PostService {
async save(post: Post, coverImage: File) {
const coverImageName = 'random_name.jpg'
const disk = drive.use('s3')
await disk.put(coverImageName, coverImage)
post.coverImage = coverImageName
await post.save()
}
}

コンテナサービスはコードを簡潔に保つために優れています。一方、依存性の注入は異なるアプリケーションパーツ間の緩い結合を作成します。

どちらを選ぶかは、個人の選択とコードの構造を決めるアプローチによります。

コンテナサービスを使用したテスト

依存性の注入の明白な利点は、テストを書く際に依存関係を交換できる能力です。

コンテナサービスと同様のテストエクスペリエンスを提供するために、AdonisJSはテストを書く際に実装をフェイクするための一流のAPIを提供しています。

次の例では、drive.fakeメソッドを呼び出してドライブディスクをインメモリドライバーで置き換えます。フェイクが作成されると、drive.useメソッドへのすべての呼び出しはフェイクの実装を受け取ります。

import drive from '@adonisjs/drive/services/main'
import PostService from '#services/post_service'
test('save post', async ({ assert }) => {
/**
* Fake s3 disk
*/
drive.fake('s3')
const postService = new PostService()
await postService.save(post, coverImage)
/**
* Write assertions
*/
assert.isTrue(await drive.use('s3').exists(coverImage.name))
/**
* Restore fake
*/
drive.restore('s3')
})

コンテナバインディングとサービス

以下の表は、フレームワークコアとファーストパーティパッケージがエクスポートするコンテナバインディングと関連するサービスの一覧です。

バインディング クラス サービス
app Application @adonisjs/core/services/app
ace Kernel @adonisjs/core/services/kernel
config Config @adonisjs/core/services/config
encryption Encryption @adonisjs/core/services/encryption
emitter Emitter @adonisjs/core/services/emitter
hash HashManager @adonisjs/core/services/hash
logger LoggerManager @adonisjs/core/services/logger
repl Repl @adonisjs/core/services/repl
router Router @adonisjs/core/services/router
server Server @adonisjs/core/services/server
testUtils TestUtils @adonisjs/core/services/test_utils