Imported from DionesioJr/claude (
backend/nestjs-create-facade/SKILL.md). Install upstream withnpx skills add DionesioJr/claude --skill nestjs-create-facade. Copyright stays with the author.
Skill: Criar Facade
Você está criando a camada Facade de um projeto NestJS que segue [[nestjs-clean-architecture-cqrs]].
Para padrões com exemplos completos: patterns.md
Regra de ouro
A Facade coordena o como as coisas acontecem, mas nunca decide o que é correto no domínio.
Se a Facade começa a parecer inteligente demais, ela já está errada.
Posição no fluxo (imutável)
Controller
↓
Facade ← você está aqui
↓──────────────────↓
Query Service Command
↓
Repository → Prisma
Responsabilidades
A Facade DEVE:
- Obter contexto via serviço de usuário atual (ex:
CurrentUserService) —accountId,userId,tenantId - Orquestrar Queries e Commands na ordem correta
- Encadear ações (buscar → validar existência → executar Command)
- Resolver UUIDs em IDs antes de chamar Commands
- Chamar Facades de outros módulos quando necessário
- Ter métodos nomeados como casos de uso do negócio
A Facade NÃO DEVE:
- Acessar
PrismaServiceou Repository diretamente - Conter regras de domínio profundas (cálculos, validações de estado)
- Resolver contexto nos Commands — isso é exclusivo da Facade
- Virar "Deus Object" com lógica demais
Regras de dependência
Permitido:
Controller → Facade
Facade → Query
Facade → Command
Facade → Facade de outro módulo
Facade → CurrentUserService
Facade → serviços de infraestrutura (email, storage etc.)
Proibido:
Facade → Repository ← bypass da arquitetura
Facade → PrismaService ← bypass da arquitetura
Query → Facade ← dependência circular
Command → Facade ← dependência circular
Antes de criar: perguntas obrigatórias
- Qual módulo? → A Facade pertence a esse módulo
- Quais casos de uso? → Liste os métodos públicos (um por operação do Controller)
- Precisa de contexto? →
getAccountId()e/ougetUserId()? - Quais Queries e Commands? → Mapeie Query/Command para cada caso de uso
- Dependências externas? → Outros módulos, serviços de email, storage?
- Há fluxos compostos? → Ex: buscar → validar → criar → notificar
Estrutura obrigatória
src/modules/nome-modulo/
└── facades/ ← ou facade/, conforme convenção do projeto
└── nome-modulo.facade.ts
Convenções de nomenclatura
| Item | Padrão | Exemplo |
|---|---|---|
| Classe | XxxFacade |
AccountsFacade |
| Arquivo | xxx.facade.ts |
accounts.facade.ts |
| Pasta | facades/ |
facades/ |
| Métodos | casos de uso | create, update, findOne, addUserToAccount |
Skeleton de uma Facade
import { Injectable, Logger } from '@nestjs/common';
import { CurrentUserService } from '@/shared/current-user/current-user';
import { FindXxxQuery } from '../queries/find-xxx.query';
import { ListXxxQuery } from '../queries/list-xxx.query';
import { CreateXxxCommand } from '../commands/create-xxx.command';
import { UpdateXxxCommand } from '../commands/update-xxx.command';
import { DeleteXxxCommand } from '../commands/delete-xxx.command';
import { CreateXxxDto } from '../dto/create-xxx.dto';
import { UpdateXxxDto } from '../dto/update-xxx.dto';
@Injectable()
export class XxxFacade {
private readonly logger = new Logger(XxxFacade.name);
constructor(
private readonly currentUserService: CurrentUserService,
private readonly findXxxQuery: FindXxxQuery,
private readonly listXxxQuery: ListXxxQuery,
private readonly createXxxCommand: CreateXxxCommand,
private readonly updateXxxCommand: UpdateXxxCommand,
private readonly deleteXxxCommand: DeleteXxxCommand,
) {}
async create(dto: CreateXxxDto) {
const accountId = this.currentUserService.getAccountId();
return this.createXxxCommand.execute(accountId, dto);
}
async findAll() {
const accountId = this.currentUserService.getAccountId();
return this.listXxxQuery.execute(accountId);
}
async findOne(uuid: string) {
const accountId = this.currentUserService.getAccountId();
return this.findXxxQuery.execute(uuid, accountId);
}
async update(uuid: string, dto: UpdateXxxDto) {
const accountId = this.currentUserService.getAccountId();
const record = await this.findXxxQuery.execute(uuid, accountId); // valida existência
return this.updateXxxCommand.execute(record.id, record.uuid, accountId, dto);
}
async remove(uuid: string) {
const accountId = this.currentUserService.getAccountId();
return this.deleteXxxCommand.execute(uuid, accountId);
}
}
Padrões obrigatórios
1. Resolver contexto sempre na Facade
// SEMPRE primeiro passo de qualquer método
const accountId = this.currentUserService.getAccountId();
2. Validar existência antes de escrever (find → command)
async update(uuid: string, dto: UpdateXxxDto) {
const accountId = this.currentUserService.getAccountId();
const record = await this.findXxxQuery.execute(uuid, accountId); // lança NotFoundException se não existir
return this.updateXxxCommand.execute(record.id, record.uuid, accountId, dto);
}
3. Resolver UUID → ID antes do Command
// Command recebe ID numérico, não UUID
// A Facade faz a resolução
const folder = await this.findFolderByUuidQuery.execute(folderUuid, accountId);
return this.createXxxCommand.execute(accountId, dto, folder.id); // passa folder.id, não folderUuid
4. Fluxos compostos com métodos privados
// Método público: delega para privados para manter clareza
async create(dto: CreateXxxDto) {
await this.validateInputs(dto); // ← método privado para validações de fluxo
const result = await this.createXxxCommand.execute(dto);
await this.sendNotification(result); // ← método privado para pós-processamento
return result;
}
private async validateInputs(dto: CreateXxxDto): Promise<void> {
const existing = await this.findXxxByEmailQuery.execute(dto.email);
if (existing) throw new ConflictException('Registro já existe.');
}
5. Dependência circular entre módulos: forwardRef
import { Inject, forwardRef } from '@nestjs/common';
constructor(
@Inject(forwardRef(() => OtherModuleQuery))
private readonly otherModuleQuery: OtherModuleQuery,
) {}
Registro no Module
@Module({
imports: [CurrentUserModule],
controllers: [XxxController],
providers: [
XxxRepository,
FindXxxQuery,
ListXxxQuery,
CreateXxxCommand,
UpdateXxxCommand,
DeleteXxxCommand,
XxxFacade, // ← registrar
],
exports: [XxxFacade], // ← exportar (único export padrão)
})
export class XxxModule {}
Checklist de validação
Estrutura
- Arquivo em
facades/oufacade/do módulo - Classe termina com
Facade - Decorado com
@Injectable() -
Loggerinicializado comLogger(XxxFacade.name)
Contexto
-
currentUserService.getAccountId()chamado dentro de cada método (não no construtor) - Commands recebem IDs numéricos, não UUIDs de entidades relacionadas
Fluxo
- Métodos nomeados como casos de uso (não como operações CRUD genéricas)
- Nenhum acesso direto a Repository ou PrismaService
-
forwardRefusado onde há dependência circular entre módulos
Module
- Registrado em
providers - Exportado em
exports - Módulo do serviço de usuário atual importado
Qualidade
- Métodos públicos sem lógica de domínio profunda
- Fluxos complexos extraídos para métodos privados