24

I'm trying to understand and figure good practices for designing your app/domain models (POCOs/DTOs).

Let's say I have the following database table, Account:

UserID int
Email varchar(50)
PasswordHash varchar(250)
PasswordSalt varchar(250)

Of course, EF4 would build the entity like so:

public class Account
{
    public int UserID { get; set; }
    public string Email { get; set; }
    public string PasswordHash { get; set; }
    public string PasswordSalt { get; set; }
}

Now, let's say I have a view model for registering a new user, which may look something like so:

public class RegistrationViewModel
{
    public string Email { get; set; }
    public string Password { get; set; }
}

Lastly, I have a service which needs to register the user:

public class RegistrationService
{
    public void RegisterUser(??? registration)
    {
        // Do stuff to register user
    }
}

I'm trying to figure out what to pass into the RegisterUser method. The view model is, of course, located under my web app (presentation layer), so I do not want this getting passed to my service.

So, I'm thinking one of four possibilities:

1) Set up a service model that is similar, if not identical, to the RegistrationViewModel, and use this:

public class RegistrationServiceModel
{
    public string Email { get; set; }
    public string Password { get; set; }
}

public class RegistrationService
{
    public void RegisterUser(RegistrationServiceModel registration)
    {
        // Do stuff to register user
    }
}

2) Set up an interface of the model, and inherit this in my view model, and set up my method to accept the interface:

public interface IRegistrationModel
{
    string Email;
    string Password;
}

public class RegistrationServiceModel : IRegistrationModel
{
    public string Email { get; set; }
    public string Password { get; set; }
}

public class RegistrationService
{
    public void RegisterUser(IRegistrationModel registration)
    {
        // Do stuff to register user
    }
}

3) Pass in the Account entity, doing the RegistrationViewModel-to-Account mapping in my controller:

public class RegistrationService
{
    public void RegisterUser(Account account)
    {
        // Do stuff to register user
    }
}

4) Move my view model out of the presentation into a domain/service layer, and pass that into the service method:

public class RegistrationService
{
    public void RegisterUser(RegistrationViewModel account)
    {
        // Do stuff to register user
    }
}

None of these three scenarios seem ideal, as I see problems in each of them. So I'm wondering if there's another method I can't think of.

What are good practices for this?

Thanks in advance.

Jerad Rose
  • 15,235
  • 18
  • 82
  • 153
  • Btw, Jerad, what exactly do you think is a problem in 3rd scenario? – Vasiliy R Feb 25 '11 at 19:31
  • @Vasilio Ruzanni - My problem (and what I've heard others say) with that option is that you are tightly coupling your services/business tier with your data models (EF4 entities), which may change more frequently than a business/domain model (sorry for the mixed terminology, I still don't have a grasp on the differences in these). – Jerad Rose Feb 25 '11 at 21:38
  • well, as I see your problem, it is that you're trying to solve a problem that "**may** appear, but doesn't exist". You always have to decide on a balance between complexity and usability. Sure, your `RegistrationModel` approach abstracts everything out, but it **doesn't make your system more maintainable** (which is the over-engineering threat signal). – Vasiliy R Feb 26 '11 at 06:53
  • 1
    The main point, shortly: Don't over-engineer, your EF entities are **THE** core of your domain model! And Domain Services are part of your domain too. Don't hide domain model from domain model. If you change your entity you will **have** to change Service anyway because your entities and services are tied conceptually. Therefore, I see the `RegistrationService.RegisterUser(Account account, string password)` approach as **enough** ideal (yeah, with most accent on _enough_). – Vasiliy R Feb 26 '11 at 06:58
  • @Vasilio - Thanks, I'm slowly realizing what you mean. I've started going down this route (using AutoMapper), and it seems to make sense. Thanks for your help! – Jerad Rose Feb 26 '11 at 13:12

3 Answers3

9

You never pass a view model to the service. A service doesn't even know about the existence of a view model that you might have defined in your presentation tier. A service works with domain models.
Use Auto mapper to map between view model and domain model and vice versa.

Personally, I've never heard of service models in DDD (view models for services).

šljaker
  • 7,294
  • 14
  • 46
  • 80
  • My terminology is likely inaccurate. So, in DDD, should I not be taking this approach? Or, are you just saying in DDD, they're not called "Service Models"? – Jerad Rose Feb 25 '11 at 16:22
  • the only problem in this particular case is that the Domain object does not have a plaintext password property, so there is nothing to map it against. – Yakimych Feb 25 '11 at 16:44
  • @Jerad, yep, don't take that approach. – šljaker Feb 25 '11 at 17:56
  • 1
    @Yakimych, you map what you have... Then you call another service in order to get password hash and password salt, and then you send the modified domain object to the registration service. Auto mapper is not a must. You can do a manual mapping if that's easier in this particular case. – šljaker Feb 25 '11 at 18:04
  • Know this is old but I have a question with this solution: In our Domain Models, we apply several validations on the constructor, to ensure object state. Not always the ViewModels have all the data we need to create a whole entity so I can pass it to the service. In this case, what can we do? I always run into this problem, and until now, I'm using "ApplicationServices" because they sit on the App domain. But after some time, this is turning out to be not optimal.. it justs looks wrong. – jpgrassi Sep 01 '17 at 11:56
  • One ex: One entity Person, that has a constructor and multiple validations going on.. e.g: Gender not being null. I have then an end-point to change the Person Name only (for many reasons including logging the action). The view model only contains, of course the new name and some other stuff used for logging. From this ViewModel, there's no way I could create a new Person instance, since I lack most of the things. – jpgrassi Sep 01 '17 at 12:00
  • So how then map from entity to viewModel and return to controller ?i mean you get the reuslt of modified entoty and return it to controller and make automapper mapping in controller ? Is it ok if UI know about entity ? – So_oP Nov 04 '22 at 11:17
9

Use 3rd option, for sure. As šljaker said, Service should be unaware of presentation part of application (which your ViewModel is a part of).

Sure, as well, don't overcomplicate things around by including tons of transition models like RegistrationServiceModel or - even worse - IRegistrationModel (last one will lead to "interface explosion" one day).

So:

  1. Have a Domain entity (POCO entity that is persisted with Entity Framework or NHibernate or NoRM or whatever).
  2. Have a ViewModel that represents your domain model in given context. Don't hesitate to make a ViewModel per Controller Action if necessary. The side-effect benefit of strict ViewModels (those which are 1:1 with your View) is complete absence of over-posting and under-posting problems. It depends on your concrete situation/taste though.
  3. Use DataAnnotation attributes with your ViewModels to provide basic validation (remember to validate business rules too but it should sit behind the wire - inside Services/Repositories layer).
  4. Don't let App Service ever know about ViewModels. Create a domain entity instance and feed it to the Service instead (to validate/persist).
  5. Use AutoMapper as an option to quicky map from your domain entities to ViewModels.
  6. Map from incoming ViewModel or FormCollection to your entity in either Controller action or custom IModelBinder.
  7. (Optionally) I'd recommend to follow the Thunderdome Principle. It's a really really convenient usage of ViewModels.
Vasiliy R
  • 941
  • 8
  • 18
  • @Vasilio - Same comment as above. In this particular case the entity does not contain a plaintext password, so there is nothing for AutoMapper (or whatever tool or manual code) to map against. – Yakimych Feb 25 '11 at 16:46
  • 1
    @Yakimych, I didn't see it until I have written my comment. About AutoMapper - you can make custom projections with AutoMapper. Also, as I understood the question - it is essentially about "best practices" of using ViewModels in common. So `Account` and `UserService` are just an example. And in most cases AutoMapper **is** the thing to avoid boilerplate code. – Vasiliy R Feb 25 '11 at 16:55
  • @Vasilio - I mostly agree with you, and yes you can tweak automapper to project onto a different property. But again **in this particular case**, there is no property for a plaintext password **at all**. You actually need to run business logic in order to generate salt and hash the password, so you need to make space for the plaintext password somewhere at the service layer. And IMO in this case a DTO works best. Regarding the rest, as I mentioned, I mostly agree. – Yakimych Feb 25 '11 at 17:11
  • @Yakimych, sure that definitely **is** approach too, with usage of special Dto for trasfer across the wire and I have to state that it is very dependent on context and service isolation requirements. In MVC I'd prefer to feed domain entities directly to app service but I like the DTO approach too (except the fact that it is another object to maintain along with already existing domain entity and a couple of ViewModels/EditModels). – Vasiliy R Feb 25 '11 at 17:28
  • @Vasilio - Ok, so how would you solve this particular issue without a DTO? I mean exactly how would you get the plaintext password to the service layer? – Yakimych Feb 25 '11 at 17:32
  • 1
    @Yakimych, `MembershipService.RegisterUser(user, password)` – Vasiliy R Feb 25 '11 at 19:16
  • @Vasilio - Yes, that's probably a better solution in this case. Just as a note, this won't be extendable for cases when you have more parameters that need to be passed along (such as password in this case). – Yakimych Feb 26 '11 at 12:00
3

In this case it makes perfect sense to use a DTO (Data Transfer Object). You can create an AccountDto class at the service layer and use it to pass the registration data down to the service. It might be similar to the ViewModel in some cases, but generally you can show much more in your View than is required to create a user. To further illustrate the point, your ViewModel will probably at least look something like this:

public class RegistrationViewModel
{
    [Required]
    public string Email { get; set; }

    [Required]
    public string Password { get; set; } 

    [Required]
    [Compare("Password")]
    public string RepeatPassword { get; set; } 
}

While your DTO will only require the Email and Password properties.

public class AccountDto
{
    public string Email { get; set; }
    public string Password { get; set; }
}

So as you see, the ViewModel only contains data needed for the View. The email validation and password comparison logic happens in your Web layer. You use the DTO to get get only email and password to the Service. And then at the service layer you hash the password, populate your Entity object and persist the values to the database.

Yakimych
  • 17,612
  • 7
  • 52
  • 69
  • 2
    Is AccountDto really necessary? Usually, after registration, user is redirected to the profile page. This means you would have to include the UserID in AccountDto and to do the following: RegistrationViewModel -> AcountDto -> Acount -> persist values to the db -> Acount -> AcountDto -> Back to the controller. Seams like an overkill. – šljaker Feb 25 '11 at 18:30
  • 1
    @šljaker - It's not necessary, but it is perfectly reasonable in cases when business logic should be run to populate part of the domain object from the ViewModel. Regarding the other statement, I don't see a reason to use the Dto to get the Account back. `Account account = accountService.RegisterAccount(accountDto);` - and you get your domain object back. It contains all you need. – Yakimych Feb 26 '11 at 12:07
  • 1
    @Yakimych So you need to transform the Registration view mode to AccountDTO ? And in the service layer, you must transform account DTO into Account domain model ? is that correct ? – Adi Sembiring Aug 25 '11 at 13:20