Domain vs DTO vs ViewModel - How and When to use them?
Asked Answered
S

5

32

In a Multi-layer project with Domain layer (DL)/Business (Service) Layer (BL)/Presentation Layer (PL), what is the best approach to deliver Entities to the Presentation Layer?

DO => Domain Object;
DTO = Domain Transfer Object;
VM => View Model;
V => View;

Option 1:

DL => DO => BL => DTO => PL => VM => V

This option seems to be the Best Practice but also seems heavy to mantain.

Option 2:

DL => DO => BL => DTO => PL => V

This option seems not very good practice but as DTO are almost identical to the VM, we can pass it directly to the View and it's less painfull to implement and mantain.

Is this option also reliable for multiple layouts, for example, for Mobile Devices I may need less information from the BL, so I will need a diferent VM for this particular Layout?

Stoa answered 13/10, 2012 at 15:3 Comment(0)
B
13

It's OK to pass the DTO to the view. If you need to change or enhance the DTO then create a ViewModel. A common scenario would be to add links. It's also OK for the ViewModel to reference the DTO as a complex property.

Breadth answered 13/10, 2012 at 15:10 Comment(6)
Thanks for your answer, but it would not be better to use always the same arquitecture rather than using in some situations option 1 and in others option 2?Stoa
I would say use the right technique to complete the task at hand. I wouldn't recommend creating a view model for the sake of creating a view model.Weariless
@Stoa for consistency yes. That's why I said 'OK' and not 'best practice'. My choice is to always create ViewModel and reference the DTO as a complex property.Breadth
@Max thanks, and how do you suggest that I initialize the mapping in the service layer using automapper?Stoa
I will highly recommended this video from Ayende youtube.com/watch?v=0tlMTJDKiug It will surely help to understand concept in and out and probably impact a lot of things about development and DDD pattern. It is more than few POCO objects.Emissive
This is definitely wrong. Anti pattern and anti everything in the world.Strati
P
4

If you are going to have different views that require different data from your Dto it sounds like you might benefit from having different view models for these and map your Dto to these.

One of the ideas behind this is to allow greater freedom to change your view model, knowing it will not have an impact on any other part of your application. If your Dto is being used in several views then each change to your Dto would require you to test each view.

Pensile answered 13/10, 2012 at 15:10 Comment(10)
Thanks for your answer, so you are saying that each Dto should have its own VM in a mantainance point of view?Stoa
You might use the Dto directly in some admin views or when it is only used in one place. Creating the view models once you see a need to use dto in more than one place. On the other hand if you are going with the Domain, Dto, Viewmodel approach it doesn't take that long to create the extra objects (with automapper between) and saves any time trying to decide which to use. Makes it clearer for anyone else on the project to know what to do. IMHO, this might save more time in the long run and make your code a little maintainable. Having only viewmodels in views helps in several ways...Pensile
as always it depends, on your domain, team and size of project.Pensile
And how would you create the mapping using Automapper at the service level since there is no Global.Asax in this level? I use the App_start folder to put everystarting stuff and call then from Global.asax in the presentation layer, but where should I create them in the service project? maybe with the DTO definition?Stoa
You could store the Automapper profile near the Dto definition but you would still configure on application start like the others. up to youPensile
I would prefer to keep DTO configuration in the service layer, beacause if tomorrow I need a Win32 aplication to "talk" to the service layer, I would not be able to put the mapping up and running, correct? I don't know is where do I can create the profiles because I only know to do it in the Global.asaxStoa
you can have the profile in the service layer, where the details of the mapping is stored. However, you will have to also call the automapper configuration within the windows service, a one liner or not much more.Pensile
What do you mean about "Windows service", I have build the profiles and mapping but for now and based and my knowledge I have to put the AutoMapperConfiguration.Configure(); on the global.asaxStoa
that's fine. now you must put the AutoMapperConfiguration.Configure() into the start of your Win32 application (I had assumed this was a service).Pensile
Width 2 Mapper.Initialize(x => { x.AddProfile, one for Service layer, onde for Web layer, automapper raise an erro. Aprently it only accepts onde initialize, and it seems that it has to be in the global.asax. Another option is to create directly the mapping without profiles, what do you think?Stoa
A
3

To me this decision is based on where I put my validation logic.

Scenario #1: Adding a data annotation in viewmodel (in UI layer) greatly simplifies programming. Mostly there will be one on one mapping between DTO and view model. In such cases Option 1 is good. DL => DO => BL => DTO => PL => VM => V

Scenario #2) If the validation is not attached to ViewModels then DTO is replaced with ViewModel and ViewModel resides in Business layer. DL => DO => BL => VM => PL => V

Scenario #2 can be perfect in situations where validation logic resides in Domain Models. And these models are used by many UI applications. UI just lists the errors in a list, as given by the business layer (not very user friendly though). As the application undergoes business rules change we only change the domain model. Again database related validations can be auto generated through EF(if using .net), even less scope for change.

Airbrush answered 5/12, 2013 at 21:32 Comment(1)
So sorry for the delay, but Thank You for your answer.Stoa
B
2

I have created several middle size applications with the following approach and sleep very well at night ;-)

Entity <=> Service <=> Model

Please note, those 3 layers must be separated by Packages/Modules/Assemblies and be a backend only.

Entity: Domain Layer, Domain Object, Repository, Database Model, Data Object Layer (DAL)

Service: Business Logic Layer (BLL), Business Service Layer

Model: Data Transfer Object (DTO), Presentation Layer, View Model (please remove presentation/view from your dictionary)

View is a separate project (PWA, Progressive Web Application) which uses API and does not require any backend code support. It has its own View Models and Business Logics inside.

Example:

MyProject.Entities (DAL):

[Table("customers")]
public class Customer
{
    // Used to instantiate the proxy by DbContext
    protected Customer() {  }
    
    // Used by developers
    public Customer(string name) : this()
    {
        Id = IdGenerator.Next();
        SetName(name);
    }
    
    [Key]
    public virtual string Id { get; protected set; }

    public virtual string Name { get; protected set; }
    
    [Column("summary")]
    public virtual string Description { get; set; }

    public void SetName(string name)
    {
        if (string.IsNullOrEmpty())
            throw new ArgumentNullException(nameof(name));

        Name = name;
    }
}

MyProject.Models (DTO):

public class IdModel
{
    [Required]
    public string Id { get; set; }
}

public class CreateCustomerModel
{
    [Required]
    [MaxLength(50)]
    public string Name { get; set; }
}

public class EditCustomerModel : IdModel
{
    [Required]
    [MaxLength(50)]
    public string Name { get; set; }

    public string Description { get; set; }
}

MyProject.Services (BLL):

public class CustomerService
{
    private readonly DbContext _db;
    private readonly IMapper _mapper;

    public CustomerService(DbContext db, IMapper mapper)
    {
        _db = db;
        _mapper = mapper;
    }

    public async Task<EditCustomerModel> Get(IdModel model)
    {
        var customer = await _db.Get<Customer>(model.Id);

        return _mapper.Map<EditCustomerModel>(customer);
    }

    public async Task<IdModel> Create(CreateCustomerModel model)
    {
        var customer = new Customer(model.Name);

        await _db.SaveOrUpdate(customer);

        return _mapper.Map<IdModel>(customer);
    }

    public async Task Edit(EditCustomerModel model)
    {
        var customer = await _db.Get<Customer>(model.Id);

        customer.SetName(model.Name);
        customer.Description = model.Description;

        await _db.SaveOrUpdate(customer);
    }

    public async Task Delete(IdModel model)
    {
        await _db.Remove<Customer>(model.Id);
    }
}

The above example is just a simple CRUD service. But in the same way you can implement any Domain Services eg. CustomerInvoiceService:

[Description("Creates a new customer and invoices it. Returns invoice Id.")]
public async Task<IdModel> CreateCustomerAndInvoiceIt(CreateCustomerInvoiceModel model)
{
    model.InvoiceForm.CustomerId = await _customerService.Create(model.CustomerForm);
    
    var invoiceId = await _invoiceService.Create(model.InvoiceForm);

    return invoiceId;
}

Just expose API/services and let frontend devs freely do their job completely separated from the backend. If it's a desktop app or the front end is made using backend language like c#/java/Go (which is very bad), they must build their own Frontend Logic Layers and Models and never allow them to mix Domain/Business Layers with Presentation Layers together in any situation. Frontend is a completely separated thing and, as someone mentioned Views above, I would suggest to remove that word from the BLL dictionary at all.

IMHO

Besse answered 16/6, 2022 at 13:30 Comment(5)
Hi, Thank You for your answer. Can you just help me to identify the DTO in your example? Thanks.Stoa
MyProject.Models are DTOs. Their purpose is to transfer data from one service into another (eg. CreateCustomerModel). MyProject.Entities are Domain objects. Their purpose is to keep/persist the state of the business logic.Besse
And in the controller, what model do we send to the View?Stoa
Sorry but there are no controllers, controllers are in MVC. If you creating an api for your needs you can use controllers, but they are not necessery. Using minimal api approach (learn.microsoft.com/en-us/aspnet/core/tutorials/min-web-api) you can register your public services without controllers. In my opinion controllers are redundant.Besse
But if you still think about the controllers (even as I said they add an extra level of complexity), then MyProject.Models (DTOs) are used to transfer your data from view into BLL and back. public async Task<MyResultModel> MyControllerAction(MyFormModel model) { return _businessLogic.Process(model); }.Besse
C
1

See here my reply : https://mcmap.net/q/102965/-dto-viewmodel

You say : This option seems to be the Best Practice but also seems heavy to mantain.

heavy to implement perhaps, juste few lines of code to duplicate most the time, but to maintain surely not.

Chantry answered 27/12, 2012 at 18:24 Comment(1)
So sorry for the delay, but Thank You for your answer.Stoa

© 2022 - 2024 — McMap. All rights reserved.