Should I transform Entity (Persistent) objects to DTO objects?
Asked Answered
W

2

6

My project is layered as follows:-

DAL (Entity) --> BLL (DTO) --> ApplicationComponent (ViewModel).

There will be multiple components of application (ApplicationComponent) which will access BLL. Components include windows services, web services, web API and MVC controller.

I am transforming NHibernate Entity objects to DTO objects while passing them from DAL to BLL. While passing this state to ApplicationComponent, BLL again converts it to ViewModel.

This helps me separate the concerns and how data is handled in each layer. I am not in favor of returning NHibernate Entity object to view for following reasons: -

  • Data get exposed to UI that I want to hide (or only expose if needed) like passwords, user type, permission etc.
  • On references/joins, NHibernate executes additional queries when property is accessed which nullify the use of lazy loading.
  • Unnecessary data exposed to user (of Entity) creates confusion and gap for bugs.
  • Persistence implementations leaking into BLL/UI. Entity is not designed for UI. It cannot serve UI in all cases.
  • We use attributes on DTO properties for user input validation which looks odd with Entity.

I am facing following problems with this approach: -

  • Biggest and obvious problem is redundant objects with similar members and functionality.
  • I have to write mapper methods in each layer to transform object. This could be minimized by using AutoMapper or something similar; but it does not fully resolve problem.

Questions:-

  1. Is this an over separation and should be avoided (at least minimized)?
  2. If this approach is correct, I do not see any simple way to fully bypass two problems I stated above. Please suggest.
  3. If this approach is incorrect, please suggest corrections.

References:-

  1. Link1 suggests to transfer Entity object to view which in my understanding not a good idea.
  2. Link2 suggests to map Entity with DTO that I am already doing.
  3. Link3 does not help.
  4. Link4 suggests using something like auto mapper tools which is OK. But it still does not solve the problem completely.
  5. Link5 is great post. It explains why those should be separate which I agree. It does not comment on how to minimize the overhead caused by it.
  6. Link6 is not helpful again.
  7. Link7 is an excellent answer which suggests use Entity as is in UI if possible. It still does not apply to most of my project.
  8. Linl8 is another excellent resource that suggest to go on mapping two way as I am doing now. It still does not suggest a way to minimize overhead.
Worldling answered 21/11, 2016 at 13:3 Comment(1)
... so, I've (team) invested so much time to map hundreds of objects.. and to create a domain. It seems to be working.. just remains to move it from a server to UI and back ... and so many people assure each other - the RE-MAPPING again into DTO is the way.. I do not get it. Even with automapper.. collections/references will be challenge. While: few overrides of Newtonsoft.Json (resolvers, entity and array value providers) ... and JSON serialization/de-serialization is solving that all. No DTO, No new objects...just managed JSON-ification...Bane
G
1

Have you considered creating a shared interface between the DTO and the Entity? You should not tightly couple your ORM to the rest of your application. Or in fact use anything other than interfaces between them if at all possible.

You could, in theory, have a separate project that just holds the contract/abstractions of what you expect to be passed around. To minimize mapping overhead and to leave it open for the extension you can ensure that the entity implements the interface as expected (omitting what is not needed), and in cases where you need a bespoke DTO you can create a model with mapping using the interfaces.

There is some overhead when adding extra interface projects but it will keep your code cleaner and more maintainable in the long run.

enter image description here

namespace Data
{
    public class FakeRepo : IFakeRepo
    {
        public IThisIsAnEntity GetEntity()
        {
            return new ThisIsAnEntity();
        }
    }

    public class ThisIsAnEntity : IThisIsAnEntity
    {
        public string HiddenField { get; set; }
        public long Id { get; set; }
        public string SomeField { get; set; }
        public string AnotherField { get; set; }
    }
}

namespace Data.Abstractions
{
    public interface IFakeRepo
    {
        IThisIsAnEntity GetEntity();
    }
}

namespace Abstractions
{
    public interface IThisIsAnEntity : IThisIsAnSlimmedDownEntity
    {
        string SomeField { get; set; }
    }

    public interface IThisIsAnSlimmedDownEntity
    {
        long Id { get; set; }
        string AnotherField { get; set; }
    }
}

namespace Services.Abstractions
{
    public interface ISomeBusinessLogic
    {
        IThisIsAnEntity GetEntity();
        IThisIsAnSlimmedDownEntity GetSlimmedDownEntity();
    }
}

namespace Services
{
    public class SomeBusinessLogic : ISomeBusinessLogic
    {
        private readonly IFakeRepo _repo;

        public SomeBusinessLogic(IFakeRepo repo)
        {
            _repo = repo;
        }

        public IThisIsAnEntity GetEntity()
        {
            return _repo.GetEntity();
        }

        public IThisIsAnSlimmedDownEntity GetSlimmedDownEntity()
        {
            return _repo.GetEntity();
        }
    }
}

namespace UI
{
    public class SomeUi
    {
        private readonly ISomeBusinessLogic _service;

        public SomeUi(ISomeBusinessLogic service)
        {
            _service = service;
        }

        public IThisIsAnSlimmedDownEntity GetViewModel()
        {
            return _service.GetSlimmedDownEntity();
        }

        public IComposite GetCompositeViewModel()
        {
            var dto = _service.GetSlimmedDownEntity();
            var viewModel = Mapper.Map<IThisIsAnSlimmedDownEntity, IComposite>(dto);
            viewModel.SomethingSpecial = "Something else";
            return viewModel;
        }
    }

    
    public class SomeViewModel : IComposite
    {
        public long Id { get; set; }
        public string AnotherField { get; set; }
        public string SomethingSpecial { get; set; }
    }
    
}

namespace UI.Abstractions
{
    public interface IComposite : IThisIsAnSlimmedDownEntity, ISomeExtraInfo
    {

    }

    public interface ISomeExtraInfo
    {
        string SomethingSpecial { get; set; }
    }
}
Glazed answered 10/10, 2019 at 17:7 Comment(0)
K
0

nhibernate is one of those orm`s that allow you to avoid having DAL entities and it will be better for performance to avoid extra mapping from BLL TO DAL, but if it is not critical for you, it will be better to keep it at as it is to have application layers loose coupled

Kulp answered 16/8, 2019 at 20:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.