ASP .NET Core - Implementando a Onion Architecture - III


Neste artigo vamos criar uma aplicação ASP .NET Core WEBAPI fazendo uma implementação básica da arquitetura em Cebola ou Onion Architecture.

Continuando a segunda parte do artigo vamos iniciar a implementação da camada Core no projeto eStore.Application.

Implementação da camada Core : projeto eStore.Application

O projeto Application representa a camada Application da Onion Architecture e esta  camada é a ponte entre a infraestrutura externa e as camadas de domínio. As camadas de domínio geralmente precisam de informações ou funcionalidade para completar a funcionalidade de negócios, no entanto, não devem depender diretamente delas. Em vez disso, a camada de aplicativo precisa depender dos contratos definidos na camada de Serviços de Domínio.

Assim nesta camada vamos definir as interfaces dos serviços e suas implementações, os DTOs e os mapeamentos, e outros recursos que permitiram acessar a camada de domínio.

Assim esta camada vai precisar possuir uma dependência com a camada de domínio e para isso vamos clicar com o botão direito sobre o projeto Application e selecionar Add-> Project Reference;

A seguir na janela a seguir marcar o projeto eStore.Domain e clicar em OK;

Vamos iniciar definindo os Data Transfer Objects - DTOs.

Um DTO (objetos de transferência de dados) é um contêiner de dados para mover dados entre camadas. Eles também são denominados como objetos de transferência. Um DTO é usado apenas para passar dados e não contém nenhuma lógica de negócios. Eles têm apenas setters e getters simples e podem conter também Data Annotations para efeito de validação na camada de interface do cliente.

Podemos usar DTOs para realizar as seguintes tarefas:

  • Remover as referências circulares;
  • Ocultar propriedades específicas que os clientes não devem exibir;
  • Omitir algumas propriedades para reduzir o tamanho da carga;
  • Nivelar grafos de objeto que contêm objetos aninhados, para torná-los mais convenientes para os clientes;
  • Evite vulnerabilidades de "excesso de postagens";
  • Desassociar sua camada de serviço da camada de banco de dados;
  • A seguir vamos criar a pasta DTOs no projeto Application e criar as classes CategoryDTO e ProductDTO definindo as propriedades e as anotações de dados pertinentes :

    1- CategoryDTO

    using System.ComponentModel;
    using System.ComponentModel.DataAnnotations;
    namespace eStore.Application.DTOs
    {
        public class CategoryDTO
        {
            public int Id { get; set; }
            [Required(ErrorMessage = "The Name is Required")]
            [MinLength(3)]
            [MaxLength(100)]
            [DisplayName("Name")]
            public string Name { get; set; }
        }
    }

    2- ProductDTO

    using eStore.Domain.Entities;
    using System;
    using System.ComponentModel.DataAnnotations;
    using System.ComponentModel.DataAnnotations.Schema;
    namespace eStore.Application.DTOs
    {
        public class ProductDTO
        {
            public int Id { get; set; }
            [Required(ErrorMessage = "The Name is Required")]
            [MinLength(3)]
            [MaxLength(100)]
            public string Name { get; set; }
            [Required(ErrorMessage = "The Description is Required")]
            [MinLength(5)]
            [MaxLength(200)]
            public string Description { get; set; }
            [Required(ErrorMessage = "The Price is Required")]
            [Column(TypeName = "decimal(18,2)")]
            [DisplayFormat(DataFormatString = "{0:C2}")]
            [DataType(DataType.Currency)]
            public decimal Price { get; set; }
            [Required(ErrorMessage = "The Stock is Required")]
            [Range(1, 9999)]
            public int Stock { get; set; }
            [MaxLength(250)]
            public string Image { get; set; }
            public Category Category { get; set; }
            public int CategoryId { get; set; }
        }
    }

    Observe que as classes DTOs definidas não possuem lógica alguma e serão usadas para transferência de dados e para não expor o nosso modelo de domínio abstraindo os objetos do domínio da camada de apresentação.

    E para nos ajudar a realizar o mapeamento entre as classes do domínio e as classe DTOs vamos incluir uma referência neste projeto à biblioteca do AutoMapper:

    A seguir vamos criar a pasta Mappings e nesta pasta criar a classe DomainToDTOMappingProfile e definir o mapeamento das classes Product e Category para as classes ProductDTO e CategoryDTO :

    using AutoMapper;
    using eStore.Application.DTOs;
    using eStore.Domain.Entities;
    namespace eStore.Application.Mappings
    {
        public class DomainToDTOMappingProfile : Profile
        {
            public DomainToDTOMappingProfile()
            {
                CreateMap<Product, ProductDTO>().ReverseMap();
                CreateMap<Category, CategoryDTO>().ReverseMap();
            }
        }
    }

    Aqui neste código estamos mapeando as mesmas propriedades mas como nunca devemos acessar as classes do domínio diretamente vamos implementar o mapeamento pois com certeza em uma aplicação de produção você vai precisar definir este mapeamento de forma mais sensível e pontual.

    A seguir vamos criar a pasta Interfaces neste projeto e definir as interfaces ICategoryService e IProductService:

    1- ICategoryService

    using eStore.Application.DTOs;
    using System.Collections.Generic;
    using System.Threading.Tasks;
    namespace eStore.Application.Interfaces
    {
        public interface ICategoryService
        {
            Task<IEnumerable<CategoryDTO>> GetCategories();
            Task<CategoryDTO> GetById(int? id);
            Task Add(CategoryDTO category);
            Task Update(CategoryDTO category);
            Task Remove(CategoryDTO category);
        }
    }

    2- IProductService

    using eStore.Application.DTOs;
    using System.Collections.Generic;
    using System.Threading.Tasks;
    namespace eStore.Application.Interfaces
    {
        public interface IProductService
        {
                Task<IEnumerable<ProductDTO>> GetProducts();
                Task<ProductDTO> GetById(int? id);
                Task Add(ProductDTO product);
                Task Update(ProductDTO product);
                Task Remove(ProductDTO product);
        }
    }

    A seguir vamos criar uma pasta Services e criar as classes CategoryService e ProductService que implementam as interfaces dos serviços onde vamos acessar a implementação do repositório e usar o AutoMapper:

    1- CategoryService

    using AutoMapper;
    using eStore.Application.DTOs;
    using eStore.Application.Interfaces;
    using eStore.Domain.Entities;
    using eStore.Domain.Interfaces;
    using System.Collections.Generic;
    using System.Threading.Tasks;

    namespace eStore.Application.Services
    {
        public class CategoryService : ICategoryService
        {
            private ICategoryRepository _categoryRepository;
            private readonly IMapper _mapper;
            public CategoryService(IMapper mapper, ICategoryRepository productRepository)
            {
                _categoryRepository = productRepository;
                _mapper = mapper;
            }

            public async Task<CategoryDTO> GetById(int? id)
            {
                var result = await _categoryRepository.GetById(id);
                return _mapper.Map<CategoryDTO>(result);
            }

            public async Task<IEnumerable<CategoryDTO>> GetCategories()
            {
                var result = await _categoryRepository.GetCategories();
                return _mapper.Map<IEnumerable<CategoryDTO>>(result);
            }
            public async Task Add(CategoryDTO category)
            {
                var mapCategory = _mapper.Map<Category>(category);
                await _categoryRepository.Create(mapCategory);
            }

            public async Task Remove(CategoryDTO category)
            {
                var mapCategory = _categoryRepository.GetById(category.Id).Result;
                await _categoryRepository.Remove(mapCategory);
            }

            public async Task Update(CategoryDTO category)
            {
                var mapCategory = _mapper.Map<Category>(category);
                await _categoryRepository.Update(mapCategory);
            }
        }
    }

    2- ProductService

    using AutoMapper;
    using eStore.Application.DTOs;
    using eStore.Application.Interfaces;
    using eStore.Domain.Entities;
    using eStore.Domain.Interfaces;
    using System.Collections.Generic;
    using System.Threading.Tasks;

    namespace eStore.Application.Services
    {
        public class ProductService : IProductService
        {
            private IProductRepository _productRepository;
            private readonly IMapper _mapper;
            public ProductService(IMapper mapper, IProductRepository productRepository)
            {
                _productRepository = productRepository;
                _mapper = mapper;
            }

            public async Task Add(ProductDTO product)
            {
                var mapProduct = _mapper.Map<Product>(product);
                await _productRepository.Create(mapProduct);
            }

            public async Task<ProductDTO> GetById(int? id)
            {
                var result = await _productRepository.GetById(id);
                return _mapper.Map<ProductDTO>(result);
            }

            public async Task<IEnumerable<ProductDTO>> GetProducts()
            {
                var result = await _productRepository.GetProducts();
                return _mapper.Map<IEnumerable<ProductDTO>>(result);
            }

            public async Task Remove(ProductDTO product)
            {
                var mapProduct = _productRepository.GetById(product.Id).Result;
                await _productRepository.Remove(mapProduct);
            }

            public async Task Update(ProductDTO product)
            {
                var mapProduct = _mapper.Map<Product>(product);
                await _productRepository.Update(mapProduct);
            }
        }
    }

    Observe que na implementação feita estamos injetando as instâncias de interface do repositório e do AutoMapper e teremos que configurar e registrar estes serviços nas camadas mais externas.

    A implementação refere-se ao CRUD básico bem como a consulta de produtos e categorias.

    Temos assim a implementação no projeto Application da camada Core concluída.

    Agora podemos nos dedicar à camada de Infrastructure onde vamos definir o contexto da aplicação usando o EF Core e implementar os repositórios e registrar os serviços criados até o momento.

    Assim teremos a seguinte visão do projeto eStore.Application em nossa solução na janela Solution Explorer, onde à esquerda vemos o arquivo de projeto mostrando a dependência com o projeto Domain a a referência ao pacote do AutoMapper:

    Assim o projeto Application depende do projeto Domain.

    Na próxima parte do artigo iremos continuar a implementação da camada Infrastructure focando no projeto Persistence.

    "Não me envergonho do evangelho, porque é o poder de Deus para a salvação de todo aquele que crê: primeiro do judeu, depois do grego."
    Romanos 1:16

    Referências:


    José Carlos Macoratti