1

I build an asp.net core web api with clean architecture, my layers are:

  • Web API
  • Service layer
  • Domain layer
  • Data layer

In my Web API I'm loading data from an external Exchange Webservices. i would like to provide in my own Web API for my clients.

For this I implemented a repository at my data layer which gets the data from the external webservice as objects from Class Appointment.

At the service layer I mapped the objects to my own Class EventDTO with AutoMapper.

Now I don't know what's the best practice to get access to this data from the web api controller.

In my opinion, when following clean architecture principles, I have to map the EventDTO to EventEntity. But when I done this, I have two 100% identically Objects, because there is no logic on the EventEntity and I do the same mapping two times. That does not make sense?!?

But when I pass the EventDTO direct to the controller, it will break the principle of clean architecture, or not?

EventController.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);
        }

    }
}

EventService.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);
        }
    }
}

EventRepository.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()
            );
        }
    }
}

EventDTO.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; }
    }
}

EventEntity.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; }
    }
}

Maybe someone has implemented a similar project. And can give me an approach.

Riedi
  • 76
  • 1
  • 5

1 Answers1

5

It depends on the project and its complexity. If it's a short-lived project, a prototype, or a simple project, sure go ahead and reuse the DTOs for serving data to the clients.

If your goal is long-term maintainability, then always map to different entities for the endpoints because eventually the shape of the requested data changes; you may need to combine two or more DTOs, you might need to add a couple of properties that are UI specific (e.g. to toggle a component or to store current filter/search terms etc.). Also, if you reuse a single DTO for two different endpoints, that means adding data to either of the endpoints will pollute the other endpoint, it also means that you can't freely modify the DTOs when some data is no longer necessary for one endpoint (while if you had separate entities for each endpoint, you can easily change them based on the endpoint requirements).

Since you're already using AutoMapper, 1-to-1 mappings won't require special configurations, so you won't be introducing complexity when doing the additional entity mapping.

I have worked on many projects that implemented one of the two approaches (or a mixture of both where the controller entities are just wrappers around the DTOs which is even worse than directly using the DTOs because they increase the number of classes with no added value as the endpoint is still tightly coupled to the DTO), and the least painful to deal with has been using separate entities for each endpoint, to never reuse the DTOs, and to never reuse the entities i.e. don't compose entities from each other; each endpoint gets its own entity, and if another endpoint needs similar data, it gets a different entity.

rhytonix
  • 968
  • 6
  • 14
  • Thank you for your detailed and very helpful comment :-). I believe that is quite right and I will implement the additional mapping. – Riedi Aug 05 '19 at 07:41