Spraxium logoSpraxium

Módulos

Monte módulos de feature com fronteiras claras, conecte-os ao módulo raiz e entenda como a DI é resolvida. Aprenda a estrutura de pastas recomendada, as propriedades de módulo, módulos globais e as regras que mantêm seu bot sustentável.

Por que módulos importam

Toda funcionalidade do seu bot pertence a exatamente um módulo. Cada módulo declara o que fornece, o que importa e o que expõe para fora. Essa fronteira permite crescer a aplicação adicionando novos módulos sem precisar mexer nos já existentes.

Arquitetura recomendada para módulos

O layout padrão isola cada domínio atrás de uma fronteira de módulo com uma pasta própria contendo, no mínimo, o arquivo do módulo e um serviço. Cada módulo pode evoluir de forma independente, enquanto o container de DI resolve toda a ligação entre eles no boot, sem configuração manual nem factories customizadas.

Arquitetura orientada a módulos
main.ts# Bootstrap da aplicação e ligação com o módulo raiz
app.module.ts# Módulo central que compõe as features
module-name.module.ts# Fronteira da feature e wiring do módulo
module-name.service.ts# Lógica de negócio e providers

Declarando o módulo raiz

O arquivo de entrada main.ts é onde você valida o ambiente, cria a aplicação, carrega o módulo raiz e começa a ouvir eventos do Discord. A chamada app.useModule(AppModule) é o ponto de composição. A partir dela, o Spraxium percorre todos os módulos importados recursivamente, resolve providers via DI e registra automaticamente cada listener no client do Discord.

src/main.ts
import { IntentPreset, Logger, SpraxiumFactory } from '@spraxium/core';
import { EnvValidator } from '@spraxium/env';
import { AppEnv } from './app.env';
import { AppModule } from './app.module';

async function main() {
  const logger = new Logger('Main');
  const environment = EnvValidator.validate(AppEnv);
  const app = await SpraxiumFactory.create({ token: environment.token });

  app.useModule(AppModule);
  app.intents(IntentPreset.Standard);

  logger.info('Starting application...');
  await app.listen();
}

main();

Construindo a raiz de composição

O módulo raiz AppModule compõe features importando outros módulos, mas não deve carregar lógica de negócio. Sempre que um novo domínio aparecer, o caminho mais limpo é criar um módulo próprio e adicioná-lo aqui.

src/app.module.ts
import { Module } from '@spraxium/common';
import { AnalyticsModule } from '@spraxium/analytics';
import { ModuleNameModule } from './modules/module-name/module-name.module';

@Module({
  imports: [AnalyticsModule, ModuleNameModule],
})
export class AppModule {}

Referência das propriedades do módulo

Módulos globais

Por padrão, os providers de um módulo só ficam disponíveis para módulos que o importem explicitamente, e esse é o comportamento desejado para quase todas as features, porque deixa dependências visíveis e rastreáveis. Marcar um módulo com @Global() faz com que seus exports fiquem acessíveis em toda a aplicação sem imports explícitos, mas isso deve ficar restrito a preocupações transversais como logging, analytics ou configuração compartilhada, que realmente precisem aparecer em qualquer lugar do código.

Use @Global() com moderação

Marcar módulos demais como globais enfraquece o encapsulamento que torna o sistema de módulos valioso e dificulta rastrear dependências conforme o projeto cresce. Reserve @Global() para infraestrutura que toda a aplicação realmente precisa, como logging ou analytics, e mantenha serviços de feature restritos ao próprio módulo com exports explícitos.

src/modules/analytics/analytics.module.ts
import { Global, Module } from '@spraxium/common';
import { AnalyticsService } from './analytics.service';

@Global()
@Module({
  providers: [AnalyticsService],
  exports: [AnalyticsService],
})
export class AnalyticsModule {}

Use módulos globais com parcimônia, porque eles atravessam justamente as fronteiras que fazem o sistema de módulos valer a pena. Serviços específicos de uma feature devem continuar locais ao módulo de origem, com exports explícitos. Se você perceber que está marcando vários módulos como globais, normalmente isso indica que o desenho das fronteiras precisa ser revisto, e não contornado.

Regras para manter módulos sustentáveis

Uma fronteira de negócio por pasta de módulo. Exporte só o que outros módulos realmente precisam consumir. Separe declarações de comandos e listeners dos detalhes internos dos serviços. Prefira vários módulos pequenos e focados, compostos pelo AppModule, em vez de poucos módulos grandes e genéricos.

Quando um domínio crescer além do escopo original, divida em dois módulos menores e mais focados. O próprio sistema de módulos facilita esse tipo de refatoração, porque cada área já nasce com a própria pasta e com dependências explícitas.