Quando utilizar Entidades, Objetos de Valor, DTOs e Mappers


Resumo rápido:

  • Entidade → Tem identidade única e carrega regras de negócio.
  • Objeto de Valor → Imutável, definido apenas por seus valores, sem identidade.
  • DTO → Estrutura simples de transporte de dados entre camadas (API, UI, banco) e o domínio
  • Mapper → Serviço de conversão entre camadas.
  • Adapter → É responsável por traduzir dados de um sistema externo para algo que o domínio entenda.

Saiba mais sobre isso lendo o Guia para Nomeação de Classes, Métodos e Funções


1. Entidades

  • Definição: Representam conceitos centrais do domínio que possuem identidade própria. Mesmo que seus atributos mudem, a Entidade continua sendo a mesma ao longo do tempo.
  • Características:
    • Possuem ID único.
    • Representam algo que evolui no sistema (ex.: Usuário, Pedido, Produto).
    • Carregam regras de negócio relacionadas ao ciclo de vida do objeto.
  • Quando usar:
    • Sempre que precisar representar algo único e persistente no domínio.

Exemplo:

class User {
  constructor(
    public readonly id: string,
    private name: string,
    private email: string,
  ) {}

  changeEmail(newEmail: string) {
    if (!newEmail.includes('@')) {
      throw new Error('Email inválido');
    }
    this.email = newEmail;
  }
}

2. Objetos de Valor (Value Objects)

  • Definição: Objetos do domínio sem identidade própria, definidos apenas por seus atributos e sempre imutáveis.
  • Características:
    • Não têm ID.
    • Dois objetos com os mesmos valores são considerados iguais.
    • São imutáveis: qualquer alteração gera um novo objeto.
  • Quando usar:
    • Para modelar conceitos representados como valores compostos, garantindo consistência e regras locais.

Exemplo:

class Email {
  constructor(private readonly value: string) {
    if (!value.includes('@')) {
      throw new Error('Email inválido');
    }
  }

  getValue(): string {
    return this.value;
  }
}

3. DTO (Data Transfer Object)

  • Definição:
    • Objetos usados apenas para transferência de dados entre camadas (ex.: Controllers → Use Cases, API → Application).
  • Características:
    • Estruturas simples, sem lógica de domínio.
    • Podem representar entrada ou saída de operações.
    • Facilitam a serialização (ex.: JSON, resposta HTTP).
  • Quando usar:
    • Para transportar dados de camadas externas para a aplicação.
    • Para retornar informações processadas ao cliente.

Exemplo: em TS

interface CreateUserDTO {
  name: string;
  email: string;
}

interface UserResponseDTO {
  id: string;
  name: string;
  email: string;
}

//Em PHP
class UserResponseDTO
{
    public function __construct(
        public readonly string $id,
        public readonly string $name,
        public readonly string $email
    ) {}
}

⚠️ DTOs não contêm regras de negócio, apenas dados


4. Como Entidades, Objetos de Valor e DTOs se relacionam

Fluxo básico:

  1. O Controller recebe um DTO
  2. Converte em Entidade + Objetos de Valor
  3. Executa as regras de negócio
  4. Retorna outro DTO para fora

Passos:

  1. O Controller recebe um DTO de entrada (CreateUserDTO).
  2. Converte o campo email em um Objeto de Valor (Email).
  3. Cria a Entidade User com os valores validados.
  4. Executa regras de negócio (ex.: verificar se já existe usuário com o mesmo e-mail).
  5. Persiste a Entidade (via repositório).
  6. Retorna um DTO de saída (UserResponseDTO).

5. Mappers (Conversão entre Domínio e DTOs)

  • Definição: Responsáveis por converter dados entre DTOs e Entidades/Objetos de Valor. Garantem isolamento entre domínio e infraestrutura.
  • Quando usar:
    • Recebendo DTO de entrada → converter para Entidade + Value Objects.
    • Enviando resposta → converter Entidade em DTO de saída.
  • Onde ficam:
    • Camada de application ou infrastructure.
    • Nunca dentro do domínio.
  • Boas práticas:
    • Não colocar lógica de negócio.
    • Converter dados de forma simples e direta.

Exemplo:

class UserMapper {
  static toDTO(user: User): UserResponseDTO {
    return {
      id: user.id,
      name: user.name,
      email: user.getEmail(),
    };
  }

  static toEntity(dto: CreateUserDTO): User {
    const email = new Email(dto.email);
    return new User(crypto.randomUUID(), dto.name, email);
  }
}

6. Ports e Adapters

  • Definição: Parte da estratégia de Ports & Adapters. Mantêm o domínio independente de tecnologias externas, criando contratos (ports) e implementações (adapters).
  • Quando usar:
    • Integrações com APIs externas (ex.: Footstats, Firebase).
    • Interações com infraestruturas (ex.: filas, provedores de email, cache).
    • Sempre que o formato externo não for compatível com o domínio.

Exemplo:

// SDK fictício de terceiro
class ExternalEmailService {
  async send(payload: { to: string; subject: string; body: string }): Promise<void> {
    console.log('Enviando email externo...', payload);
  }
}
// Port (contrato do domínio)
interface EmailProvider {
  sendEmail(to: string, subject: string, body: string): Promise<void>;
}
// Adapter (implementação concreta)
class ExternalEmailAdapter implements EmailProvider {
  constructor(private externalService: ExternalEmailService) {}

  async sendEmail(to: string, subject: string, body: string): Promise<void> {
    await this.externalService.send({ to, subject, body });
  }
}
  • Onde ficam:
    • Port = contrato definido pelo domínio (ex.: EmailProvider).
    • Adapter = implementação do contrato para uma tecnologia específica (ex.: ExternalEmailAdapter).
    • Geralmente na camada de infrastructure.

7. Boas práticas gerais

  • Entidades nunca recebem nem retornam DTOs. Trabalham apenas com Objetos de Valor e outras Entidades.
  • DTOs só existem nas bordas. Nunca dentro do domínio.
  • Objetos de Valor são usados pelas Entidades. Ex.: User trabalha com Email, não com string.
  • Transformações ficam em Mappers/Adapters. Ex.: UserMapper.toDTO(user) ou UserMapper.toEntity(dto).