Dominando a Arquitetura de TypeScript
Guia técnico e prático para estruturar aplicações TypeScript com foco em escalabilidade, tipagem estável e arquitetura limpa.
Eu sou programador sênior e redator do yurideveloper.com. Neste post, compartilho minha abordagem para
organizar código TypeScript de forma disciplinada, mantendo contratos bem definidos entre as camadas.
1. Fundamentos da Arquitetura em TypeScript
Ao trabalhar com TypeScript, a base está em separar responsabilidades e definir contratos claros entre camadas. A prática de Design de Software que mais se aplica aqui é a separação entre domínio, aplicação, infraestrutura e apresentação, aliada aos princípios de inversão de dependência (DIP) e de responsabilidade única.
- Domínio: encapsula regras de negócio, entidades de domínio e invariantes a partir de um conjunto mínimo de operações empresariais.
- Aplicação: orquestra casos de uso, transforma dados entre forma interna e externa (DTOs), sem expor detalhes de implementação.
- Infraestrutura: integra com frameworks, bancos de dados, serviços externos e plataformas; depende de abstrações definidas na camada de domínio/app.
- Apresentação: adaptadores (controllers, gateways) que recebem comandos/requisições e devolvem respostas sem expor dependências internas.
Com TS, aproveito tipagem estática, generics e interfaces para criar contratos que induzem respeito às regras de negócio e reduzem acoplamento entre camadas.
2. Organização de Pastas e Boundaries
Uma estrutura clara facilita a evolução do sistema. A prática comum é separar por domínio de responsabilidade e por camada, mantendo contratos estáveis entre elas. Exemplo de estrutura típica:
src/
domain/ // entidades, regras de negócio, contratos básicos
entities/
services/
repositories/
application/ // casos de uso, DTOs, orquestração
use-cases/
dtos/
infrastructure/ // implementações concretas: repositórios, gateways
repositories/
gateways/
presentation/ // controllers/API handlers, adapters
controllers/
routes/
shared/ // utilitários, types comuns
Essa organização favorece a verificação de dependências: camadas de domínio e aplicação não devem depender de detalhes de infraestrutura, mantendo a arquitetura coerente à medida que o projeto cresce.
3. Tipagem Avançada e Padrões em TypeScript
Aproveitar generics, tipos utilitários e tipos condicionais permite criar contratos mais expressivos e segurar invariantes de forma estática. Alguns recursos úteis:
- Generics para repositórios e serviços: abstraem operações comuns para qualquer entidade.
- Tipos discriminados para eventos e estados: facilita o tratamento de fluxos de negócio complexos.
- Mapped e utility types (Partial, Pick, Omit): facilitar transformação entre entidades e DTOs sem duplicação de código.
- Type guards e narrowing: verifica no tempo de execução se uma variável corresponde a uma entidade esperada.
Abaixo, um exemplo simples de padrão de repositório genérico com uma entidade de domínio e uma implementação de memória em tempo de execução:
<!-- TS snippet -->
type Id = string | number;
interface IEntity { id: Id; }
interface IRepository<T extends IEntity> {
getById(id: T['id']): Promise<T | null>;
save(entity: T): Promise<void>;
delete(id: T['id']): Promise<void>;
}
class User implements IEntity {
constructor(public id: string, public name: string, public email: string) {}
}
class InMemoryRepository<T extends IEntity> implements IRepository<T> {
private items = new Map<string, T>();
async getById(id: T['id']): Promise<T | null> {
return this.items.get(String(id)) ?? null;
}
async save(entity: T): Promise<void> {
this.items.set(String(entity.id), entity);
}
async delete(id: T['id']): Promise<void> {
this.items.delete(String(id));
}
}
async function getUserProfile(repo: IRepository<User>, id: string) {
const user = await repo.getById(id);
if (!user) return null;
// Transformação de domínio para apresentação (DTO)
return { id: user.id, name: user.name, email: user.email };
}
</TS>
Observação: o código demonstra como criar contratos de repositório genéricos e utilizá-los com uma entidade de domínio sem amarrar o domínio às tecnologias de infraestrutura.
4. Configurações, Ferramentas e Boas Práticas
Para manter consistência e desempenho em projetos TypeScript, preze pela configuração bem definida de tsconfig, resolução de módulos e convenções de importação. Algumas diretrizes comuns:
- Habilite strict (strict: true) para validar invariantes em tempo de compilação.
- Use path aliases para reduzir dependências absolutas entre pastas (ex.: “@domain/*”, “@app/*”).
- Ative incremental builds para acelerar ciclos de feedback durante o desenvolvimento.
- Isolate modules quando necessário para evitar dependências circulares e facilitar o tree-shaking.
Exemplo conceitual de configuração (trechos):
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"strict": true,
"baseUrl": ".",
"paths": {
"@domain/*": ["src/domain/*"],
"@application/*": ["src/application/*"]
},
"incremental": true,
"esModuleInterop": true
}
}
Adote uma cadência de revisões de código, garanta que contratos entre camadas sejam verificáveis por testes e busque manter o código legível para futuras evoluções.
Gostou? Leia mais conteúdos úteis
Explore outros posts do Yurideveloper para aprofundar em temas de arquitetura, tipagem e práticas recomendadas em TypeScript.
Sou Apaixonado pela programação e estou trilhando o caminho de ter cada diz mais conhecimento e trazer toda minha experiência vinda do Design para a programação resultando em layouts incríveis e idéias inovadoras! Conecte-se Comigo!