Senior
Como resolver o problema do uso de dependências de componentes periféricos no núcleo?
sobes.tech IA
Resposta da IA
Resolver o problema do uso de dependências de componentes periféricos (adaptadores, interfaces) no núcleo (lógica de domínio) em arquiteturas como a Arquitetura Limpa ou DDD, pode ser feito usando o princípio de Inversão de Dependências (Dependency Inversion Principle - DIP).
A essência do DIP:
- Os módulos de nível superior não devem depender dos módulos de nível inferior. Ambos devem depender de abstrações.
- As abstrações não devem depender dos detalhes. Os detalhes devem depender das abstrações.
Aplicação prática:
- O núcleo declara interfaces: O núcleo define abstrações (interfaces ou classes abstratas) que descrevem as funções necessárias dos componentes periféricos.
- Os componentes periféricos implementam interfaces: Os módulos externos (adaptadores para bases de dados, clientes HTTP, sistemas de arquivos, etc.) implementam essas interfaces.
- Injeção de dependências (Dependency Injection - DI): Os componentes periféricos são injetados no núcleo de fora, geralmente em um nível superior da aplicação (por exemplo, na raiz de composição ou usando um contêiner DI). O núcleo trabalha apenas com abstrações.
Exemplo de uso do DIP em Node.js:
Suponha que temos um núcleo que precisa de acesso a dados.
// src/domain/interfaces/UserRepository.ts
// O núcleo declara a interface
export interface UserRepository {
getUserById(id: string): Promise<User | null>;
}
// src/domain/entities/User.ts
// Entidade do núcleo
export class User {
constructor(public id: string, public name: string) {}
}
// src/domain/usecases/GetUserUseCase.ts
// Caso de uso do núcleo depende da abstração UserRepository
import { UserRepository } from '../interfaces/UserRepository';
import { User } from '../entities/User';
export class GetUserUseCase {
constructor(private userRepository: UserRepository) {}
async execute(id: string): Promise<User | null> {
return this.userRepository.getUserById(id);
}
}
// src/infrastructure/database/MongoDbUserRepository.ts
// Componente periférico que implementa a interface do núcleo
import { UserRepository } from '../../domain/interfaces/UserRepository';
import { User } from '../../domain/entities/User';
export class MongoDbUserRepository implements UserRepository {
// Aqui estará a lógica de interação com MongoDB
async getUserById(id: string): Promise<User | null> {
console.log(`Fetching user with id ${id} from MongoDB...`);
// Simulação de obtenção de dados
if (id === '123') {
return new User('123', 'John Doe');
}
return null;
}
}
// src/app/CompositionRoot.ts
// A raiz de composição monta as dependências
import { GetUserUseCase } from '../domain/usecases/GetUserUseCase';
import { MongoDbUserRepository } from '../infrastructure/database/MongoDbUserRepository';
// Injeção de dependência: O núcleo recebe a implementação MongoDbUserRepository via interface
const userRepository = new MongoDbUserRepository();
const getUserUseCase = new GetUserUseCase(userRepository);
// Agora podemos usar o caso de uso
async function run() {
const user = await getUserUseCase.execute('123');
if (user) {
console.log(`Found user: ${user.name}`);
} else {
console.log('User not found');
}
}
run();
Vantagens dessa abordagem:
- Independência do núcleo: O núcleo não conhece as implementações específicas dos componentes periféricos.
- Testabilidade: O núcleo é fácil de testar, substituindo implementações mock das interfaces.
- Flexibilidade: Pode-se facilmente substituir uma implementação (por exemplo, banco de dados) por outra, sem alterar o código do núcleo.
- Manutenção: Facilita a manutenção e alteração do código.