Outra implementação da Injeção de Dependência no JavaScript é com módulos ES6, com a capacidade de usar o mesmo código em um navegador e em nodejs e não usar transpilers.

Sob o corte está minha visão sobre o DI, seu lugar em aplicativos da Web modernos, a implementação fundamental de um contêiner de DI que pode criar objetos na frente e no verso, além de uma explicação do que Michael Jackson tem a ver com isso.
Peço fortemente aos que consideram trivial no artigo que não se estuprem e não leiam até o fim, para que mais tarde, quando estiverem desapontados, não façam um "sinal de menos". Eu não sou contra os "menos" - mas apenas se o menos for acompanhado por um comentário, o que exatamente na publicação causou uma reação negativa. Este é um artigo técnico, portanto, tente ser condescendente com o estilo de apresentação e critique com precisão o componente técnico acima. Obrigada
Objetos no aplicativo
Eu realmente respeito a programação funcional, mas dediquei a maior parte da minha atividade profissional à criação de aplicativos que consistem em objetos. O JavaScript me impressiona com o fato de que as funções nele também são objetos. Ao criar aplicativos, penso em objetos, essa é minha deformação profissional.
De acordo com a vida útil, os objetos no aplicativo podem ser divididos nas seguintes categorias:
- permanente - surgem em algum estágio do pedido e são destruídos somente quando o pedido é concluído;
- temporário - surgem quando é necessário executar alguma operação e são destruídos quando essa operação é concluída;
A esse respeito, na programação, existem padrões de design como:
Ou seja, do meu ponto de vista, o aplicativo consiste em solitários permanentemente existentes que executam as operações necessárias ou geram objetos temporários para executá-las.
Contêiner de Objetos
A injeção de dependência é uma abordagem que facilita a criação de objetos em um aplicativo. Ou seja, no aplicativo há um objeto especial que "sabe" como criar todos os outros objetos. Esse objeto é chamado de Contêiner de Objetos (às vezes, um Gerenciador de Objetos).
O Contêiner de Objetos não é um Objeto Divino , porque sua tarefa é apenas criar objetos significativos do aplicativo e fornecer acesso a outros objetos para eles. A grande maioria dos objetos de aplicativo, gerados pelo Container e localizados nele, não tem idéia do próprio Container. Eles podem ser colocados em qualquer outro ambiente, com as dependências necessárias, e também funcionarão maravilhosamente lá (os testadores sabem o que quero dizer).
Local de implementação
Em geral, existem duas maneiras de injetar dependências em um objeto:
- através do construtor;
- através de uma propriedade (ou seu acessador);
Basicamente, usei a primeira abordagem, portanto continuarei a descrição com o ponto de vista da injeção de dependência por meio do construtor.
Digamos que temos um aplicativo composto por três objetos:

No PHP (esta linguagem com tradições DI de longa data, atualmente tenho bagagem ativa, passarei para o JS um pouco mais tarde), uma situação semelhante pode ser refletida desta maneira:
class Config { public function __construct() { } } class Service { private $config; public function __construct(Config $config) { $this->config = $config; } } class Application { private $config; private $service; public function __construct(Config $config, Service $service) { $this->config = $config; $this->service = $service; } }
Essas informações devem ser suficientes para que um contêiner de DI (por exemplo, liga / contêiner ), se configurado adequadamente, também possa criar suas dependências de Service
e Config
mediante solicitação, para criar um objeto Application
e passá-los para o construtor do objeto Application
.
Identificadores de dependência
Como o Contêiner de Objetos entende que o construtor do objeto Application
requer dois objetos de Config
e Service
? Analisando o objeto por meio da API de reflexão ( Java , PHP ) ou analisando o código do objeto diretamente (anotações de código). Ou seja, no caso geral, podemos determinar os nomes das variáveis que o construtor de objetos espera ver na entrada e, se a linguagem for tipável, também podemos obter os tipos dessas variáveis.
Assim, como identificadores de objetos, o Container pode operar com os nomes dos parâmetros de entrada do construtor ou com os tipos de parâmetros de entrada.
Criar objetos
O objeto pode ser criado explicitamente pelo programador e colocado no container sob o identificador correspondente (por exemplo, "configuração")
$container->add("configuration", $config);
e pode ser criado pelo contêiner de acordo com certas regras específicas. Essas regras, em geral, se resumem a corresponder o identificador do objeto ao seu código. As regras podem ser definidas explicitamente (mapeamento na forma de código, XML, JSON, ...)
[ ["object_id_1", "/path/to/source1.php"], ["object_id_2", "/path/to/source2.php"], ... ]
ou na forma de algum algoritmo:
public function getSource($id) {. return "/path/to/source/${id}.php"; }
No PHP, as regras para corresponder um nome de classe a um arquivo com seu código-fonte são padronizadas ( PSR-4 ); em Java, a correspondência é feita no nível de configuração da JVM ( carregador de classes ). Se o Contêiner fornecer uma pesquisa automática de fontes ao criar objetos, os nomes das classes serão bons identificadores suficientes para objetos em um Contêiner.
Namespaces
Geralmente em um projeto, além de seu próprio código, também são utilizados módulos de terceiros. Com o advento dos gerenciadores de dependência (maven, compositor, npm), o uso de módulos foi bastante simplificado e o número de módulos em projetos aumentou muito. Os espaços para nome permitem que elementos de código com o mesmo nome existam em um único projeto a partir de vários módulos (classes, funções, constantes).
Existem idiomas nos quais o espaço para nome é construído inicialmente (Java):
package vendor.project.module.folder;
Existem idiomas nos quais o espaço para nome foi adicionado durante o desenvolvimento da linguagem (PHP):
namespace Vendor\Project\Module\Folder;
Uma boa implementação de namespace permite endereçar inequivocamente qualquer elemento do código:
\Doctrine\Common\Annotations\Annotation\Attribute::$name
O espaço para nome resolve o problema de organizar muitos elementos de software em um projeto, e a estrutura do arquivo resolve o problema de organizar arquivos no disco. Portanto, não há muito em comum entre eles, e às vezes muito - em Java, por exemplo, uma classe pública no espaço para nome deve ser unida exclusivamente a um arquivo com o código dessa classe.
Portanto, usar o identificador de uma classe de objeto no namespace do projeto como identificadores de objeto no Container é uma boa idéia e pode servir como base para criar regras para a detecção automática de códigos-fonte ao criar o objeto desejado.
$container->add(\Vendor\Project\Module\ObjectType::class, $obj);
Inicialização de código
No PHP composer
espaço para nome composer
módulo é mapeado para o sistema de arquivos dentro do módulo no descritor do módulo composer.json
:
"autoload": { "psr-4": { "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" } }
A comunidade JS poderia fazer um mapeamento semelhante no package.json
se houvesse espaços para nome no JS.
Identificadores de dependência JS
Acima, indiquei que o Container pode usar os nomes dos parâmetros de entrada do construtor ou os tipos de parâmetros de entrada como identificadores. O problema é que:
- JS é uma linguagem com digitação dinâmica e não especifica tipos ao declarar uma função.
- JS usa minificadores que podem renomear parâmetros de entrada.
Os desenvolvedores do contêiner DI do awilix sugerem o uso do objeto como o único parâmetro de entrada para o construtor, e as propriedades desse objeto como dependências:
class UserController { constructor(opts) { this.userService = opts.userService } }
O identificador de propriedade do objeto em JS pode consistir em caracteres alfanuméricos, "_" e "$" e não pode começar com um dígito.
Como precisaremos mapear identificadores de dependência para o caminho para suas fontes no sistema de arquivos para carregamento automático, é melhor abandonar o uso de "$" e usar a experiência do PHP. Antes que o operador de namespace
aparecesse em algumas estruturas (por exemplo, no Zend 1), os seguintes nomes foram usados para classes:
class Zend_Config_Writer_Json {...}
Assim, poderíamos refletir nossa aplicação de três objetos ( Application
, Config
, Service
) em JS, algo como isto:
class Vendor_Project_Config { constructor() { } } class Vendor_Project_Service { constructor({Vendor_Project_Config}) { this.config = Vendor_Project_Config; } } class Vendor_Project_Application { constructor({Vendor_Project_Config, Vendor_Project_Service}) { this.config = Vendor_Project_Config; this.service = Vendor_Project_Service; } }
Se publicarmos o código de cada classe:
export default class Vendor_Project_Application { constructor({Vendor_Project_Config, Vendor_Project_Service}) { this.config = Vendor_Project_Config; this.service = Vendor_Project_Service; } }
no seu arquivo dentro do nosso módulo de projeto:
./src/
./Application.js
./Config.js
./Service.js
Em seguida, podemos conectar o diretório raiz do módulo ao "namespace" raiz do módulo na configuração do Container:
const ns = "Vendor_Project"; const path = path.join(module_root, "src"); container.addSourceMapping(ns, path);
e, a partir dessas informações, construa o caminho para as fontes correspondentes ( ${module_root}/src/Config.js
) com base no identificador de dependência ( ${module_root}/src/Config.js
).
Módulos ES6
O ES6 oferece um design geral para carregar módulos ES6:
import { something } from 'path/to/source/with/something';
Como precisamos anexar um objeto (classe) a um arquivo, faz sentido na fonte exportar essa classe por padrão:
export default class Vendor_Project_Path_To_Source_With_Something {...}
Em princípio, é possível não escrever um nome tão longo para a classe, apenas Something
funcionará também, mas no Zend 1 eles escreveram e não quebraram, e a exclusividade do nome da classe no projeto afeta positivamente os recursos do IDE (avisos de preenchimento automático e contextual), portanto e ao depurar:

A importação de uma classe e a criação de um objeto, neste caso, são assim:
import Something from 'path/to/source/with/something'; const something = new Something();
Importação frontal e traseira
A importação funciona no navegador e no nodejs, mas há nuances. Por exemplo, o navegador não entende a importação dos módulos nodejs:
import path from "path";
Ocorreu um erro no navegador:
Failed to resolve module specifier "path". Relative references must start with either "/", "./", or "../".
Ou seja, se queremos que nosso código funcione no navegador e no nodejs, não podemos usar construções que o navegador ou o nodejs não entendem. Eu me concentro especificamente nisso, porque essa conclusão é natural demais para pensar sobre isso. Como respirar.
O lugar da DI em aplicações web modernas
Esta é apenas a minha opinião pessoal, devido à minha experiência pessoal, como tudo nesta publicação.
Em aplicativos da Web, o JS praticamente ocupa seu lugar na frente, no navegador, quase sem alternativa. No lado do servidor, Java, PHP, .Net, Ruby, python, cavaram densamente ... Mas com o advento do nodejs, o JavaScript também penetrou no servidor. E as tecnologias usadas em outros idiomas, incluindo DI, começaram a penetrar no JS do lado do servidor.
O desenvolvimento do JavaScript é devido ao comportamento assíncrono do código no navegador. A assincronia não é uma característica excepcional do JS, mas sim inata. Agora, a presença de JS no servidor e na frente não surpreende ninguém, mas incentiva o uso das mesmas abordagens nas duas extremidades do aplicativo da web. E o mesmo código. Obviamente, a frente e a parte de trás são muito diferentes em essência e nas tarefas a serem resolvidas para usar o mesmo código lá e ali. Mas podemos assumir que em um aplicativo mais ou menos complexo haverá navegador, servidor e código geral.
O DI já está em uso na frente, no RequireJS :
define( ["./config", "./service"], function App(Config, Service) {} );
É verdade que aqui os identificadores de dependências são escritos explícita e imediatamente na forma de links para as fontes (você pode configurar o mapeamento de identificadores na configuração do gerenciador de inicialização).
Em aplicativos modernos da Web, o DI existe não apenas no lado do servidor, mas também no navegador.
O que Michael Jackson tem a ver com isso?
Quando você ativa o suporte ao módulo ES no nodejs (sinalizador --experimental-modules
), o mecanismo identifica o conteúdo dos arquivos com a *.mjs
como módulos EcmaScript (diferentemente dos módulos comuns com a *.cjs
).
Essa abordagem às vezes é chamada de " Solução de Michael Jackson " e os scripts são chamados de scripts de Michael Jackson ( *.mjs
).
Concordo que a intriga com o KDPV foi resolvida, mas ... o camon dos caras, Michael Jackson ...
Mais uma implementação de DI
Bem, como esperado, o seu próprio a bicicleta Módulo DI - @ teqfw / di
Esta não é uma solução pronta para o combate, mas uma implementação fundamental. Todas as dependências devem ser módulos ES e usar recursos comuns para o navegador e o nodejs.
Para resolver dependências, o módulo usa a abordagem awilix :
constructor(spec) { /** @type {Vendor_Module_Config} */ const _config = spec.Vendor_Module_Config; /** @type {Vendor_Module_Service} */ const _service = spec.Vendor_Module_Service; }
Para executar o exemplo anterior:
import Container from "./src/Container.mjs"; const container = new Container(); container.addSourceMapping("Vendor_Module", "../example"); container.get("Vendor_Module_App") .then((app) => { app.run(); });
no servidor:
$ node --experimental-modules main.mjs
Para executar o exemplo da frente ( example.html
):
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>DI in Browser</title> <script type="module" src="./main.mjs"></script> </head> <body> <p>Load main script './main.mjs', create new DI container, then get object by ID from container.</p> <p>Open browser console to see output.</p> </body> </html>
você precisa colocar o módulo no servidor e abrir a página example.html
no navegador (ou usar os recursos do IDE). Se você abrir example.html
diretamente, o erro no Chrom é:
Access to script at 'file:///home/alex/work/teqfw.di/main.mjs' from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, https.
Se tudo correu bem, no console (navegador ou nodejs) haverá algo como isto:
Create object with ID 'Vendor_Module_App'. Create object with ID 'Vendor_Module_Config'. There is no dependency with id 'Vendor_Module_Config' yet. 'Vendor_Module_Config' instance is created. Create object with ID 'Vendor_Module_Service'. There is no dependency with id 'Vendor_Module_Service' yet. 'Vendor_Module_Service' instance is created (deps: [Vendor_Module_Config]). 'Vendor_Module_App' instance is created (deps: [Vendor_Module_Config, Vendor_Module_Service]). Application 'Vendor_Module_Config' is running.
Sumário
AMD, CommonJS, UMD?
ESM !