Arquitetura de TypeScript: Guia Completo para Construir Projetos Escaláveis

Arquitetura de TypeScript: Guia Completo para Construir Projetos Escaláveis





Dominando a Arquitetura de TypeScript


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.