具有干净架构和 AutoMapper 的 asp.net core - 将 DTO 通过服务层传递到控制器

Rie*_*edi 1 dto automapper service-layer clean-architecture asp.net-core-webapi

我构建了一个具有干净架构的 asp.net core Web api,我的层是:

  • 网络应用程序接口
  • 服务层
  • 领域层
  • 数据层

在我的 Web API 中,我从外部 Exchange Web 服务加载数据。我想为我的客户提供我自己的 Web API。

为此,我在数据层实现了一个存储库,该存储库从外部 Web 服务获取数据作为 Class 的对象Appointment

在服务层,我使用 AutoMapper 将对象映射到我自己的类EventDTO

现在我不知道从 Web API 控制器访问这些数据的最佳实践是什么。

在我看来,当遵循干净的架构原则时,我必须EventDTOEventEntity. 但是当我这样做时,我有两个 100% 相同的对象,因为没有逻辑,EventEntity并且我做了两次相同的映射。那没有意义?!?

但是当我将直接传递EventDTO给控制器​​时,它会打破干净架构的原则吗?

事件控制器.cs

namespace Example.API.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class EventController : ControllerBase
    {
        private readonly IEventService eventService;

        public EventController (IEventService eventService)
        {
            this.eventService = eventService ?? throw new ArgumentNullException(nameof(eventService));
        }

        [HttpGet]
        public async Task<IActionResult> LoadEventsAsync()
        {

                IEnumerable<EventDTO> items = await eventService.GetEvents();
                return Ok(items);
        }

    }
}
Run Code Online (Sandbox Code Playgroud)

事件服务.cs

namespace Example.Core.Application.Services
{
    public class EventService: IEventService
    {
        private IEventRepository eventRepository;
        private IMapper mapper; 

        public EventService(IEventRepository eventRepository, IMapper mapper)
        {
            this.eventRepository= eventRepository;
            this.mapper = mapper;
        }

        public async Task<IEnumerable<EventDTO>> GetEvents()
        {
            var appointments = await eventRepository.GetAppointments();
            return mapper.Map<IEnumerable<EventDTO>>(appointments);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

事件存储库.cs

namespace Example.Infrastructure.Repository
{
    public class EventRepository : IEventRepository
    {
        private ExchangeService _exchangeService;

        public EventRepository()
        {
            _exchangeService = new ExchangeService(ExchangeVersion.Exchange2010_SP1);
            _exchangeService.Credentials = new WebCredentials("user", "passwort", "domain");
            _exchangeService.Url = new Uri("https://example.de/Exchange.asmx");
        }

        async Task<IEnumerable<Appointment>> IEventRepository.GetAppointments()
        {
            CalendarFolder calendar = await CalendarFolder.Bind(
                _exchangeService,
                new FolderId
                (
                    WellKnownFolderName.Calendar,
                    "user@example.de"
                )
            );

            return await calendar.FindAppointments(
                new CalendarView()
            );
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

事件DTO.cs

namespace Example.Core.Application.DTO
{
    public class EventDTO
    {
        public string Subject { get; set; }
        public bool Cancelled { get; set; }
        public DateTime Start { get; set; }
        public DateTime End { get; set; }
    }
}
Run Code Online (Sandbox Code Playgroud)

事件实体.cs

namespace Example.Core.Entities
{
    public class EventEntity
    {
        public string Subject { get; set; }
        public bool Cancelled { get; set; }
        public DateTime Start { get; set; }
        public DateTime End { get; set; }
    }
}
Run Code Online (Sandbox Code Playgroud)

也许有人已经实施了类似的项目。并且可以给我一个方法。

rhy*_*nix 5

这取决于项目及其复杂性。如果它是一个短期项目、原型或简单项目,请务必继续重用 DTO 来为客户端提供数据。

如果您的目标是长期可维护性,那么请始终映射到端点的不同实体,因为最终请求的数据的形状会发生变化;您可能需要组合两个或多个 DTO,您可能需要添加几个特定于 UI 的属性(例如,切换组件或存储当前过滤器/搜索词等)。此外,如果您为两个不同的端点重复使用单个 DTO,这意味着向其中一个端点添加数据将会污染另一个端点,这也意味着当一个端点不再需要某些数据时,您无法自由修改 DTO (如果每个端点都有单独的实体,则可以根据端点要求轻松更改它们)。

由于您已经在使用 AutoMapper,因此一对一映射不需要特殊配置,因此在执行其他实体映射时不会引入复杂性。

我参与过许多项目,这些项目实现了这两种方法中的一种(或两种方法的混合),其中控制器实体只是 DTO 的包装器,这比直接使用 DTO 更糟糕,因为它们增加了类的数量,但没有附加值端点仍然与 DTO 紧密耦合),最容易处理的问题是为每个端点使用单独的实体,永远不要重用 DTO,并且永远不要重用实体,即不要将实体彼此组合;每个端点都有自己的实体,如果另一个端点需要类似的数据,它会获得不同的实体。