Serviços de contêiner
Como discutimos no guia de contêiner IoC, as vinculações de contêiner são uma das principais razões para o contêiner IoC existir no AdonisJS.
As vinculações de contêiner mantêm sua base de código limpa do código boilerplate necessário para construir objetos antes que eles possam ser usados.
No exemplo a seguir, antes de usar a classe Database, você terá que criar uma instância dela. Dependendo da classe que você está construindo, você pode ter escrito muito código boilerplate para obter todas as suas dependências.
import { Database } from '@adonisjs/lucid'
export const db = new Database(
// injetar configuração e outras dependências
)No entanto, ao usar um contêiner IoC, você pode descarregar a tarefa de construir uma classe para o contêiner e buscar uma instância pré-construída.
import app from '@adonisjs/core/services/app'
const db = await app.container.make('lucid.db')A necessidade de serviços de contêiner
Usar o contêiner para resolver objetos pré-configurados é ótimo. No entanto, usar o método container.make tem suas desvantagens.
Os editores são bons com importações automáticas. Se você tentar usar uma variável e o editor puder adivinhar o caminho de importação da variável, ele escreverá a instrução de importação para você. No entanto, isso não funciona com chamadas
container.make.Usar uma mistura de instruções de importação e chamadas
container.makeparece pouco intuitivo em comparação a ter uma sintaxe unificada para importar/usar módulos.
Para superar essas desvantagens, envolvemos chamadas container.make dentro de um módulo JavaScript regular, para que você possa buscá-las usando a instrução import.
Por exemplo, o pacote @adonisjs/lucid tem um arquivo chamado services/db.ts e esse arquivo tem aproximadamente o seguinte conteúdo.
const db = await app.container.make('lucid.db')
export { db as default }Dentro do seu aplicativo, você pode substituir a chamada container.make por uma importação apontando para o arquivo 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'Como você pode ver, ainda estamos contando com o contêiner para resolver uma instância da classe Database para nós. No entanto, com uma camada de indireção, podemos substituir a chamada container.make por uma instrução import regular.
O módulo JavaScript que envolve as chamadas container.make é conhecido como um serviço de contêiner. Quase todo pacote que interage com o contêiner é enviado com um ou mais serviços de contêiner.
Serviços de contêiner vs. Injeção de dependência
Os serviços de contêiner são uma alternativa à injeção de dependência. Por exemplo, em vez de aceitar a classe Disk como uma dependência, você pede ao serviço drive para fornecer uma instância de disco. Vamos dar uma olhada em alguns exemplos de código.
No exemplo a seguir, usamos o decorador @inject para injetar uma instância da classe 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()
}
}Ao usar o serviço drive, chamamos o método drive.use para obter uma instância de Disk com o driver s3.
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()
}
}Os serviços de contêiner são ótimos para manter seu código conciso. Enquanto isso, a injeção de dependência cria um acoplamento frouxo entre diferentes partes do aplicativo.
A escolha de um em vez do outro se resume à sua escolha pessoal e à abordagem que você deseja adotar para estruturar seu código.
Testando com serviços de contêiner
O benefício absoluto da injeção de dependência é a capacidade de trocar dependências no momento da escrita de testes.
Para fornecer uma experiência de teste semelhante com serviços de contêiner, o AdonisJS fornece APIs de primeira classe para falsificar implementações ao escrever testes.
No exemplo a seguir, chamamos o método drive.fake para trocar discos de unidade com um driver na memória. Depois que uma falsificação é criada, qualquer chamada para o método drive.use receberá a implementação falsa.
import drive from '@adonisjs/drive/services/main'
import { PostService } from '#services/post_service'
test('save post', async ({ assert }) => {
/**
* Disco s3 falso
*/
drive.fake('s3')
const postService = new PostService()
await postService.save(post, coverImage)
/**
* Escrever afirmações
*/
assert.isTrue(await drive.use('s3').exists(coverImage.name))
/**
* Restaurar falso
*/
drive.restore('s3')
})Ligações e serviços de contêiner
A tabela a seguir descreve uma lista de ligações de contêiner e seus serviços relacionados exportados pelo núcleo do framework e pacotes primários.
| Binding | Classe | Serviço |
|---|---|---|
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 |