.NET -  Data Transfers Objects - DTOs


 Hoje vamos rever o conceito dos Data Transfers Objects - DTOs.

Os Data Transfer Objects (DTOs) são objetos usados para transferir dados entre diferentes camadas ou componentes da aplicação.

Eles são usados para encapsular e transportar apenas as informações relevantes de um objeto de domínio para as partes interessadas, como as camadas de apresentação, serviços ou integração com bancos de dados.

Os DTOs atuam como uma "ponte" para transferência de dados, permitindo uma comunicação mais eficiente e controlada entre as diversas partes do sistema. Eles oferecem algumas vantagens, tais como:

  1. Minimizar o acoplamento: Ao utilizar DTOs, você pode evitar a dependência direta entre as camadas da aplicação, permitindo que cada camada opere de forma independente. Isso facilita a manutenção e evolução do código, uma vez que alterações em uma camada não afetarão diretamente as outras.
  2. Reduzir o tráfego de rede: Ao transferir apenas as informações necessárias, você pode economizar recursos de rede, especialmente quando a aplicação se comunica com serviços externos.
  3. Controlar a exposição de dados: Os DTOs permitem que você controle quais dados e propriedades são expostos para as partes externas da aplicação. Isso é particularmente útil quando você precisa ocultar ou anonimizar informações sensíveis ou quando deseja fornecer apenas um subconjunto específico de dados.
  4. Aumentar a segurança: Com DTOs, é possível evitar que dados internos sensíveis sejam expostos indevidamente à camada de apresentação ou aos clientes.

Vamos mostrar um exemplo bem simples e para isso vamos criar um projeto Console no VS 2022 chamado AppDTO.

No projeto crie a pasta Entities e a seguir crie a classe Funcionario nesta pasta:

public class Funcionario
{
    public int Id { get; set; }
    public string Nome { get; set; }
    public string Sobrenome { get; set; }
    public char Sexo { get; set; }
    public string Email { get; set; }
    public decimal Salario { get; set; }
}

Agora, vamos considerar um cenário onde você deseja buscar informações dos funcionários no banco de dados e transferi-las usando DTOs onde deseja obter o nome completo do funcionário.

Para isso vamos criar uma pasta DTOs no projeto e nesta pasta criar a classe FuncionarioDTO:

public class FuncionarioDTO
{
    public int Id { get; set; }
    public string NomeCompleto { get; set; }
    public char Sexo { get; set; }
    public string Email { get; set; }
    public decimal Salario { get; set; }
}

No arquivo Program do projeto vamos definir o código abaixo:

using AppDTO.DTOs;
using AppDTO.Entities;

// Simulando a busca de funcionários do banco de dados
List<Funcionario> funcionariosDoBanco = ObterFuncionariosDoBanco();

// Mapear os objetos Funcionario para FuncionarioDTO
List<FuncionarioDTO> funcionariosDTO = MapearParaDTO(funcionariosDoBanco);

// Exibir os dados usando DTOs
foreach (var funcionarioDTO in funcionariosDTO)
{
    Console.WriteLine($"ID: {funcionarioDTO.Id}");
    Console.WriteLine($"Nome: {funcionarioDTO.NomeCompleto}");
    Console.WriteLine($"Sexo: {funcionarioDTO.Sexo}");
    Console.WriteLine($"Email: {funcionarioDTO.Email}");
    Console.WriteLine($"Salario: {funcionarioDTO.Salario}");
    Console.WriteLine();
}

Console.ReadKey();

static List<Funcionario> ObterFuncionariosDoBanco()
{
    // Simulação da busca no banco de dados
    return new List<Funcionario>
     {
        new Funcionario { Id = 1, Nome = "João", Sobrenome = "Silva", Sexo = 'M', Email = "joao@email.com", Salario = 5000 },
        new Funcionario { Id = 2, Nome = "Maria", Sobrenome = "Santos", Sexo = 'F', Email = "maria@email.com", Salario = 6000 },
        new Funcionario { Id = 3, Nome = "Amanda", Sobrenome = "Bueno", Sexo = 'F', Email = "amanda@email.com", Salario = 4800 }
     };
}

static List<FuncionarioDTO> MapearParaDTO(List<Funcionario> funcionarios)
{
    var funcionariosDTO = new List<FuncionarioDTO>();

    foreach (var funcionario in funcionarios)
    {
        funcionariosDTO.Add(new FuncionarioDTO
        {
            Id = funcionario.Id,
            NomeCompleto = $"{funcionario.Nome} {funcionario.Sobrenome}",
            Sexo = funcionario.Sexo,
            Email = funcionario.Email,
            Salario = funcionario.Salario
        });
    }

    return funcionariosDTO;
}

Executando o projeto teremos o seguinte resultado:

Nesse exemplo, estamos simulando a busca de funcionários no banco de dados, mapeando os objetos da entidade Funcionario para DTOs da classe FuncionarioDTO e, em seguida, exibindo as informações usando os DTOs.

O método MapearParaDTO é responsável por converter uma lista de objetos da entidade Funcionario em uma lista de objetos DTO da classe FuncionarioDTO. O processo de mapeamento envolve a transferência de dados relevantes da entidade para o DTO, para que esses dados possam ser usados em diferentes camadas da aplicação sem expor detalhes internos da estrutura da entidade.

Agora imagine uma aplicação ASP.NET Core onde temos as entidades Categoria e Produto com um relacionamento um para muitos entre categoria e produto. Neste cenário em um controlador CategoriasController podemos ter um endpoint para retornar as categorias com seus produtos.

Para fazer isso sem obter um erro de referência cíclica vamos criar os DTOs:

CategoriaDTO

public class CategoriaDTO
{
 
public int CategoriaId { get; set; }
 
public string? Nome { get; set; }
 
public string? ImagemUrl { get; set; } 
}

ProdutoDTO

public class ProdutoDTO
{
  public int ProdutoId { get; set; }
 
public string? Nome { get; set; }
 
public string? Descricao { get; set; }
 
public decimal Preco { get; set; }
 
public string? ImagemUrl { get; set; }
 
public float Estoque { get; set; }
 
public DateTime DataCadastro { get; set; }
 
public CategoriaDTO? Categoria { get; set; }
}

CategoriaComProdutosDTO

public class CategoriaComProdutosDTO
{
  public int CategoriaId { get; set; }
 
public string Nome { get; set; }
 
public string? ImagemUrl { get; set; }
 
public ICollection<ProdutoDTO>? Produtos { get; set; }
}

A seguir no controlador CategoriasController podemos definir o endpoint para retornar as categorias com seus produtos:

public class CategoriasController : ControllerBase
{
    private readonly AppDbContext _dbContext;

    public CategoriasController(AppDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    [HttpGet]
    public IActionResult GetCategoriasComProdutos()
    {

        var categorias = _dbContext.Categorias.Include(c => c.Produtos).ToList();

        var categoriasComProdutosDTO = new List<CategoriaComProdutosDTO>();

        foreach (var categoria in categorias)
        {
            var categoriaComProdutosDTO = new CategoriaComProdutosDTO
            {
                CategoriaId = categoria.CategoriaId,
                Nome = categoria.Nome,
                ImagemUrl = categoria.ImagemUrl,
                Produtos = categoria.Produtos.Select(p => new ProdutoDTO
                {
                    ProdutoId = p.ProdutoId,
                    Nome = p.Nome,
                    Descricao = p.Descricao,
                    Preco = p.Preco,
                    ImagemUrl = p.ImagemUrl,
                    Estoque = p.Estoque,
                    DataCadastro = p.DataCadastro,
                }).ToList()
            };

            categoriasComProdutosDTO.Add(categoriaComProdutosDTO);
        }

        return Ok(categoriasComProdutosDTO);
    }
}

Essa é uma forma manual de realizar o mapeamento entre as entidade e os DTOs para obter o resultado esperado. (Podemos usar uma biblioteca de terceiros, como o AutoMapper, para nos ajudar a realizar este mapeamento.)

O uso de DTOs ajuda a isolar a camada de apresentação da camada de persistência de dados, melhorando a separação de preocupações na aplicação.

Pegue o projeto aqui:  AppDTO.zip

"Porque para mim o viver é Cristo, e o morrer é ganho."
Filipenses 1:21

Referências:


José Carlos Macoratti