Skip to content

容器服务

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

容器绑定使你的代码库不再需要在使用对象之前构造对象所需的样板代码。

在下面的例子中,在你使用 Database 类之前,你必须创建它的实例。根据你构造的类,你可能需要编写大量样板代码来获取其所有依赖项。

ts
import { Database } from '@adonisjs/lucid'
export const db = new Database(
  // 注入配置和其他依赖项
)

然而,当使用 IoC 容器时,你可以将构造类的任务交给容器并获取预构建的实例。

ts
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 的文件,这个文件大致有以下内容。

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

在你的应用程序中,你可以用指向 services/db.ts 文件的导入替换 container.make 调用。

ts
import db from '@adonisjs/lucid/services/db'

如你所见,我们仍然依靠容器为我们解析 Database 类的实例。然而,通过一层间接,我们可以用常规 import 语句替换 container.make 调用。

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

容器服务 vs. 依赖注入

容器服务是依赖注入的替代方案。例如,你可以要求 drive 服务给你一个磁盘实例,而不是接受 Disk 类作为依赖项。让我们看一些代码示例。

在下面的例子中,我们使用 @inject 装饰器注入 Disk 类的实例。

ts
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 实例。

ts
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.use 方法的调用都将接收伪造的实现。

ts
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')
})

容器绑定和服务

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

绑定服务
appApplication@adonisjs/core/services/app
aceKernel@adonisjs/core/services/kernel
configConfig@adonisjs/core/services/config
encryptionEncryption@adonisjs/core/services/encryption
emitterEmitter@adonisjs/core/services/emitter
hashHashManager@adonisjs/core/services/hash
loggerLoggerManager@adonisjs/core/services/logger
replRepl@adonisjs/core/services/repl
routerRouter@adonisjs/core/services/router
serverServer@adonisjs/core/services/server
testUtilsTestUtils@adonisjs/core/services/test_utils