容器服务

容器服务

正如我们在 IoC 容器指南 中所讨论的,容器绑定是 AdonisJS 中 IoC 容器存在的主要原因之一。

容器绑定让你的代码库摆脱了在使用对象之前构建它们所需的样板代码。

在以下示例中,在使用 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 调用上无法生效。

  • 混用 import 语句和 container.make 调用,相比拥有一套统一的导入/使用模块的语法,会让人感觉不够直观。

为了克服这些缺点,我们将 container.make 调用封装在普通的 JavaScript 模块中,这样你就可以通过 import 语句来获取它们。

例如,@adonisjs/lucid 包中有一个名为 services/db.ts 的文件,该文件大致包含以下内容。

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

在你的应用中,你可以用指向 services/db.ts 文件的 import 语句来替换 container.make 调用。

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

如你所见,我们仍然依赖容器为我们解析 Database 类的实例。不过,借助一层间接封装,我们可以用普通的 import 语句来替换 container.make 调用。

封装了 container.make 调用的 JavaScript 模块被称为容器服务。几乎每个与容器交互的包都会附带一个或多个容器服务。

容器服务 vs. 依赖注入

容器服务是依赖注入的一种替代方案。例如,与其将 Disk 类作为依赖接受,不如让 drive 服务为你提供一个磁盘实例。让我们看一些代码示例。

在以下示例中,我们使用 @inject 装饰器注入一个 Disk 类的实例。

import { Disk } from '@adonisjs/drive'
import { inject } from '@adonisjs/core'
@inject()
export 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 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 磁盘。一旦创建了伪造对象,任何对 drive.use 方法的调用都会收到伪造的实现。

import drive from '@adonisjs/drive/services/main'
import { PostService } from '#services/post_service'
test('save post', async ({ assert }) => {
/**
* 伪造 s3 磁盘
*/
drive.fake('s3')
const postService = new PostService()
await postService.save(post, coverImage)
/**
* 编写断言
*/
assert.isTrue(await drive.use('s3').exists(coverImage.name))
/**
* 恢复伪造对象
*/
drive.restore('s3')
})

容器绑定与服务

下表列出了框架核心和官方包导出的容器绑定及其相关服务。

Binding Class Service
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