DTO no Rails: o que é, quando usar e como ele se diferencia de Entities, Repositories e Serializers

Ontem à noite (23/12/2025), acabei caindo em um vídeo de um dos meus canais favoritos no YouTube, do Augusto Galego, com um título bem direto: “Por que usar DTOs?”

Confesso que o título me travou por alguns segundos.

DTO não era um termo totalmente estranho, mas eu nunca tinha parado para pensar conscientemente nele como um conceito isolado dentro das aplicações Rails que já trabalhei.

Assisti ao vídeo, fiz uma leitura rápida sobre o assunto e, como de costume, fui complementar com pesquisas e conversas técnicas. A primeira reação foi quase automática: “isso aqui não é só um serializer?”. Mas não — não exatamente.

O que mais me chamou atenção é que, olhando para trás, percebi que muitas das aplicações Rails em que trabalhei já enfrentavam esse tipo de problema, mesmo sem chamar isso explicitamente de DTO. Conforme a aplicação cresce, deixa de ser apenas CRUD e começa a lidar com mais regra, mais integração e mais responsabilidade, algumas perguntas começam a surgir naturalmente.

Enquanto assistia ao vídeo, me peguei anotando dúvidas que, na prática, já tinham aparecido em outros projetos:

  • O que exatamente é um DTO?
  • Ele substitui o Model?
  • Qual a diferença entre DTO, Entity, Repository e Serializer?
  • Rails tem algum padrão oficial para isso?

Essas perguntas não aparecem no início de um projeto. Elas surgem quando a aplicação começa a ficar maior, quando respostas deixam de vir de um único model e quando o código começa a pedir mais organização.

A ideia deste post é justamente organizar esses conceitos de forma prática, com exemplos reais em Rails, e ajudar a entender quando cada abordagem faz sentido — e quando ela só adiciona mais uma camada sem necessidade.


O que é um DTO (Data Transfer Object)

DTO significa Data Transfer Object.

Na prática, é um objeto simples cujo único papel é transportar dados entre camadas do sistema. Ele não contém regra de negócio, não valida estado e não conhece banco de dados.

Algumas características importantes

  • Não persiste dados
  • Não conhece ActiveRecord
  • Não executa regras de negócio
  • Serve apenas para carregar dados estruturados

Exemplo simples

class UserDTO
  attr_reader :id, :name, :email

  def initialize(id:, name:, email:)
    @id = id
    @name = name
    @email = email
  end
end

Gosto de pensar que o DTO responde a uma pergunta bem objetiva:
“como esses dados precisam viajar dentro da aplicação?”


DTO vs Model (Entity)

Entity (Model no Rails)

class User < ApplicationRecord
  validates :email, presence: true

  def adult?
    age >= 18
  end
end

A Entity representa o domínio. É onde vivem as regras que fazem sentido para aquele conceito do negócio.

Responsabilidades da Entity:

  • Representar o domínio
  • Garantir invariantes
  • Conter regras de negócio intrínsecas
  • Conhecer o próprio estado

DTO

UserDTO = Struct.new(:id, :name, :email, keyword_init: true)

Já o DTO não representa domínio nenhum. Ele apenas carrega dados de um ponto a outro.

Responsabilidades do DTO:

  • Transportar dados
  • Estruturar payloads
  • Não conter regras de negócio

Diferença clara

Entity DTO
Tem comportamento Não tem
Conhece o domínio Não conhece
Pode validar Não valida
Vive no core do sistema Vive nas bordas

DTO vs Serializer (muito comum no Rails)

No ecossistema Rails, serializers acabam sendo usados como uma espécie de “DTO de saída”, principalmente em APIs.

Alguns exemplos comuns:

  • ActiveModel::Serializer
  • Blueprinter
  • FastJsonapi
  • Jbuilder
class UserSerializer < ActiveModel::Serializer
  attributes :id, :name, :email
end

Responsabilidade do Serializer:

  • Converter objetos em JSON
  • Controlar quais campos são expostos
  • Formatar respostas de API

Quando um Serializer é suficiente

  • Model → JSON simples
  • APIs REST tradicionais
  • CRUDs diretos

Quando um DTO começa a fazer mais sentido

  • Dados vêm de vários models
  • Existem agregações ou cálculos
  • A resposta não mapeia 1:1 com uma entidade
  • Dashboards, relatórios ou integrações

Em aplicações simples, o serializer resolve bem.
Conforme a complexidade aumenta, o DTO tende a organizar melhor o fluxo.


DTO vs Repository

Aqui costuma surgir bastante confusão.

Repository

Repository é uma abstração para acesso a dados.

class UserRepository
  def active_users
    User.where(active: true)
  end
end

Responsabilidades do Repository:

  • Buscar dados
  • Persistir dados
  • Centralizar queries
  • Esconder detalhes do ActiveRecord

O Repository responde à pergunta:
“de onde esses dados vêm?”


DTO

class DashboardDTO
  attr_reader :total_users, :active_users

  def initialize(total_users:, active_users:)
    @total_users = total_users
    @active_users = active_users
  end
end

O DTO responde a outra pergunta:
“como esses dados precisam ser transportados?”

Comparação direta

Aspecto Repository DTO
Fala com banco Sim Não
Contém queries Sim Não
Transporta dados Não Sim
Pode ter regra de negócio Às vezes Não
Depende de ActiveRecord Geralmente Não

Eles não competem. Cada um resolve um problema diferente.


Um fluxo bem organizado usando Entity, Repository e DTO

Controller
   ↓
Service (caso de uso)
   ↓
Repository (acesso a dados)
   ↓
Entity (regras do domínio)
   ↓
DTO (formato de transporte)
   ↓
Controller / API

Exemplo prático

class DashboardService
  def initialize(user_repository:)
    @user_repository = user_repository
  end

  def call
    users = @user_repository.active_users

    DashboardDTO.new(
      total_users: users.count,
      active_users: users.map(&:id)
    )
  end
end

Erros comuns

Repository virando DTO

def dashboard_data
  {
    total: User.count,
    active: User.where(active: true).count
  }
end

Aqui tudo acaba misturado: acesso a dados, regra e formato de saída.


DTO com regra de negócio

class UserDTO
  def adult?
    age >= 18
  end
end

Esse tipo de regra pertence à Entity, não ao DTO.


Rails tem um padrão oficial para DTO?

Não.

Rails segue a estrutura clássica:

  • Model
  • Controller
  • View / Serializer

Na prática, projetos médios e grandes costumam adotar uma separação mais explícita.


Organização comum em projetos Rails mais maduros

app/
 ├── entities/
 ├── repositories/
 ├── services/
 ├── dtos/
 └── serializers/

DTO como Struct

UserDTO = Struct.new(:id, :name, :email, keyword_init: true)

Simples, legível e suficiente para muitos casos.


Quando DTO é exagero

  • CRUD simples
  • Model direto para JSON
  • Aplicação pequena
  • Sem agregações ou múltiplas fontes de dados

Nesses casos, um serializer costuma resolver melhor.


Quando DTO realmente ajuda

  • Domínio mais complexo
  • Dashboards e relatórios
  • Integrações externas
  • Respostas que combinam vários models
  • Necessidade de reduzir acoplamento com ActiveRecord

Uma regra prática para não se perder

  • Entity: o que é
  • Repository: de onde vem
  • Service: o que acontece
  • DTO: como os dados viajam

Conclusão

DTO não é moda e não é obrigatório no Rails.

É apenas uma ferramenta para organizar melhor o código quando a complexidade começa a aparecer.
Se resolver um problema real, vale usar. Se não, provavelmente é só mais uma camada sem necessidade.