This is a continuation of my previous post Refactoring ASP MVC 2 to use Dependecy Injection (part1)
The main layers I would like in my application are the following
Layer | Contains | Depends on/using |
GuiLayer | (partial) views, user controls, js | PresentationLayer, (DomainLayer) |
PresentationLayer | viewmodels, controllers, MVC plumbing | DomainLayer |
DomainLayer | domain objects, business logic | nothing |
DataLayer | LINQ generated classes, repository handlers ... | DomainLayer |
When we look at the dependencies it is worth noting that the DomainLayer does not depend on anything. The DomainLayer contains all our domain logic and relevant objects and we want to be able to swap the GUILayer but also the DataLayer (see more
Domain-driven design @ Wikipedia)
Let us have a look at the domain model I have defined.
For clarity I have omitted several other classes. Since both the domain model and the database was created from the actual scenario we are trying to solve here they are remarkably similar, however this is not a requirement (it just makes the DB-OO mapping easier).
Since the PresentationLayer contains view models that are relevant to the way we present our data we might find that we have several view models for one domain model or one view model that is composed by more than one domain model. We do not want the domain models to know about the view models since they might change depending on a rich client GUI that allows more data to be shown, but then again they might not, the key point is that we do not know and neither does the DomainLayer.
To this effect I have added an interface that is used in the PresentationLayer (view models).
Please see sample implementations for the 3 view models.
using System;
using System.Collections.Generic;
namespace ReverseAuction.Models
{
interface IDomainModelToViewModelConverter<T, U>
{
U ConvertDomainModelToViewModel(T domainEntity);
List<U> ConvertDomainModelToViewModel(List<T> domainEntities);
}
}
The reason for letting the ViewModels handle the conversion from DomainModel to ViewModel (this also goes for the data layer - the DataModels handle the conversion from DataModel to DomainModel) is that the domain model is the interface all other layer know about.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Appinux.ReverseAuction.Domain;
namespace Appinux.ReverseAuction.Models
{
public class SimpleUserViewModel : IDomainModelToViewModelConverter<User, SimpleUserViewModel>
{
public string Username { get; set; }
public Guid ID { get; set; }
public SimpleUserViewModel ConvertDomainModelToViewModel(User domainEntity)
{
this.ID = domainEntity.ID;
this.Username = domainEntity.Username;
return this;
}
public List<SimpleUserViewModel> ConvertDomainModelToViewModel(List<User> domainEntities)
{
List<SimpleUserViewModel> viewModelList = new List<SimpleUserViewModel>();
foreach (User domainEntity in domainEntities)
{
viewModelList.Add(new SimpleUserViewModel().ConvertDomainModelToViewModel(domainEntity));
}
return viewModelList;
}
}
}
More of the same here
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Appinux.ReverseAuction.Domain;
namespace Appinux.ReverseAuction.Models
{
public class ProjectBidViewModel : IDomainModelToViewModelConverter<ProjectBid, ProjectBidViewModel>
{
public SimpleUserViewModel Bidder { get; set; }
public Double BidAmount { get; set; }
public DateTime BidDate { get; set; }
public ProjectBidViewModel ConvertDomainModelToViewModel(ProjectBid domainEntity)
{
this.BidAmount = domainEntity.Amount;
this.BidDate = domainEntity.PostedDate;
this.Bidder = new SimpleUserViewModel().ConvertDomainModelToViewModel(domainEntity.PostedBy);
return this;
}
public List<ProjectBidViewModel> ConvertDomainModelToViewModel(List<ProjectBid> domainEntities)
{
List<ProjectBidViewModel> viewModelList = new List<ProjectBidViewModel>();
foreach (ProjectBid domainEntity in domainEntities)
{
viewModelList.Add(new ProjectBidViewModel().ConvertDomainModelToViewModel(domainEntity));
}
return viewModelList;
}
}
}
and the last class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Appinux.ReverseAuction.Domain;
namespace Appinux.ReverseAuction.Models
{
public class ProjectViewModel : IDomainModelToViewModelConverter<Project, ProjectViewModel>
{
public string Name { get; set; }
public string Description { get; set; }
public SimpleUserViewModel Owner { get; set; }
public DateTime BiddingStartDate { get; set; }
public DateTime BiddingEndDate { get; set; }
public DateTime ProjectLauchDate { get; set; }
public DateTime ProjectDeadline { get; set; }
public double MaxAllowedBid { get; set; }
public string Status { get; set; }
public List<projectbidviewmodel> ProjectBids { get; set; }
public ProjectViewModel ConvertDomainModelToViewModel(Project domainEntity)
{
this.Name = domainEntity.Name;
this.Description = domainEntity.Description;
this.Owner = new SimpleUserViewModel().ConvertDomainModelToViewModel(domainEntity.Owner);
this.BiddingStartDate = domainEntity.BiddingStartDate;
this.BiddingEndDate = domainEntity.BiddingEndDate;
this.ProjectLauchDate = domainEntity.StartDate;
this.ProjectDeadline = domainEntity.Deadline;
this.MaxAllowedBid = domainEntity.MaxBidAmount;
this.Status = domainEntity.Status.Name;
this.ProjectBids = new ProjectBidViewModel().ConvertDomainModelToViewModel(domainEntity.Bids);
return this;
}
public List<ProjectViewModel> ConvertDomainModelToViewModel(List<Project> domainEntities)
{
List<ProjectViewModel> viewModelList = new List<ProjectViewModel>();
foreach (Project domainEntity in domainEntities)
{
viewModelList.Add(new ProjectViewModel().ConvertDomainModelToViewModel(domainEntity));
}
return viewModelList;
}
}
}
I have on purpose done a non optimal structuring of the code above. Before continuing have a look at
Single responsibility principle.
The problem:
According to the
Single responsibility principle a class should have one responsibility. This is always up to interpretation what is the specific class's responsibility. However since ViewModels per definition are responsible for carrying data from the controller to the View in ASP.NET MVC, they should not know about conversion between DomainModels and themselves.
Depending on the complexity of the mapping between ViewModels and DomainModels & DomainModels and DataModels Factory classes could improve the modularity and adherence to the
Single responsibility principle.
So lets fix this.
We are going to split each of the ViewModels into a ViewModel containing the
DTO /
POCO and a static
Factory class.
Now the classes look like the following:
using System;
namespace Appinux.ReverseAuction.Models
{
public class SimpleUserViewModel
{
public string Username { get; set; }
public Guid ID { get; set; }
}
}
using System;
using System.Collections.Generic;
using Appinux.ReverseAuction.Domain;
namespace Appinux.ReverseAuction.Models
{
public static class SimpleUserViewModelFactory
{
public static SimpleUserViewModel Create(User domainEntity)
{
SimpleUserViewModel viewModel = new SimpleUserViewModel();
viewModel.ID = domainEntity.ID;
viewModel.Username = domainEntity.Username;
return viewModel;
}
public static List<SimpleUserViewModel> Create(List<User> domainEntities)
{
List<SimpleUserViewModel> viewModelList = new List<SimpleUserViewModel>();
foreach (User domainEntity in domainEntities)
{
viewModelList.Add(SimpleUserViewModelFactory.Create(domainEntity));
}
return viewModelList;
}
}
}
using System;
namespace Appinux.ReverseAuction.Models
{
public class ProjectBidViewModel
{
public SimpleUserViewModel Bidder { get; set; }
public Double BidAmount { get; set; }
public DateTime BidDate { get; set; }
}
}
using System;
using System.Collections.Generic;
using Appinux.ReverseAuction.Domain;
namespace Appinux.ReverseAuction.Models
{
public static class ProjectBidViewModelFactory
{
public static ProjectBidViewModel Create(ProjectBid domainEntity)
{
ProjectBidViewModel viewModel = new ProjectBidViewModel();
viewModel.BidAmount = domainEntity.Amount;
viewModel.BidDate = domainEntity.PostedDate;
viewModel.Bidder = SimpleUserViewModelFactory.Create(domainEntity.PostedBy);
return viewModel;
}
public static List<ProjectBidViewModel> Create(List<ProjectBid> domainEntities)
{
List<ProjectBidViewModel> viewModelList = new List<ProjectBidViewModel>();
foreach (ProjectBid domainEntity in domainEntities)
{
viewModelList.Add(ProjectBidViewModelFactory.Create(domainEntity));
}
return viewModelList;
}
}
}
using System;
using System.Collections.Generic;
namespace Appinux.ReverseAuction.Models
{
public class ProjectViewModel
{
public string Name { get; set; }
public string Description { get; set; }
public SimpleUserViewModel Owner { get; set; }
public DateTime BiddingStartDate { get; set; }
public DateTime BiddingEndDate { get; set; }
public DateTime ProjectLauchDate { get; set; }
public DateTime ProjectDeadline { get; set; }
public double MaxAllowedBid { get; set; }
public string Status { get; set; }
public List<ProjectBidViewModel> ProjectBids { get; set; }
}
}
using System;
using System.Collections.Generic;
using Appinux.ReverseAuction.Domain;
namespace Appinux.ReverseAuction.Models
{
public static class ProjectViewModelFactory
{
public static ProjectViewModel Create(Project domainEntity)
{
ProjectViewModel viewModel = new ProjectViewModel();
viewModel.Name = domainEntity.Name;
viewModel.Description = domainEntity.Description;
viewModel.Owner = SimpleUserViewModelFactory.Create(domainEntity.Owner);
viewModel.BiddingStartDate = domainEntity.BiddingStartDate;
viewModel.BiddingEndDate = domainEntity.BiddingEndDate;
viewModel.ProjectLauchDate = domainEntity.StartDate;
viewModel.ProjectDeadline = domainEntity.Deadline;
viewModel.MaxAllowedBid = domainEntity.MaxBidAmount;
viewModel.Status = domainEntity.Status.Name;
viewModel.ProjectBids = ProjectBidViewModelFactory.Create(domainEntity.Bids);
return viewModel;
}
public static List<ProjectViewModel> Create(List<Project> domainEntities)
{
List<ProjectViewModel> viewModelList = new List<ProjectViewModel>();
foreach (Project domainEntity in domainEntities)
{
viewModelList.Add(ProjectViewModelFactory.Create(domainEntity));
}
return viewModelList;
}
}
}
Let us look at how the traditional layering of tiered applications often look like.
Now lets look at how the same application usually looks like after a few iterations of feature additions based on new requirements from the business.
The
technical debt increases, and a normal consequence is that the number of dependencies increase.
My proposal to let the entire stack of layers revolve around the domain layer means that the presentation layer does not have any indirect dependecy on anything else than the DomainLayer. The same goes for the DataLayer. This is actually how it works in most businesses, the business requirements are the driving factor for development and IMHO from a development standpoint the application should have the DomainLayer as the driver (since the DomainLayer should be the software equivalent of the buisiness requirements) (I know this is not the full story since there
often also are UI requirements etc ... but let us for now, arrogantly :), ignore these.
The picture below shows the dependencies in the approach I am suggesting here.
The whole point about interface based design is that you can swap classes with other classes that implement the same interface.
However if you new up specific classes in methods then you are adding a hard dependecy on the class. The code below show how a lot of code looks. This is a contrived example in real life applications it is never one single new statement but many all over the place. The classes needed to implement functionality often resides in several assemblies / namespaces. This means that you very quicly get into dependecy hell.
And on a side note it makes
Test Driven Development much more difficult since you cannot mock the dependecies.
using System;
using System.Web.Mvc;
using SomeApplication.Domain;
namespace SomeApplication.Domain
{
public interface ISomeInterface
{
string DoSomething();
}
public class SomeClass : ISomeInterface
{
public string DoSomething()
{
throw new NotImplementedException();
}
}
}
namespace SomeApplication.Presentation
{
public class HomeController : Controller
{
public HomeController()
{
}
public ActionResult Index()
{
// Bad Bad Bad - this is adding a dependency directly to the SomeApplication.Domain.SomeClass
ISomeInterface someInteface = new SomeClass();
return View();
}
}
}
Let us try to take away the knowledge about what specific class that is being used in the controller in the sample above.
using System;
using System.Web.Mvc;
using SomeApplication.Domain;
namespace SomeApplication.Domain
{
public interface ISomeInterface
{
string DoSomething();
}
public class SomeClass : ISomeInterface
{
public string DoSomething()
{
throw new NotImplementedException();
}
}
}
namespace SomeApplication.Presentation
{
public class HomeController : Controller
{
private ISomeInterface someInterface;
public HomeController(ISomeInterface someInterface)
{
this.someInterface = someInterface;
}
public ActionResult Index()
{
// Now this class knows nothing about the concrete class, this knowledge is pushed higher up the call stack
someIterface.DoSomething();
return View();
}
}
}
The above way of structuring code is called constructor injection. In addition there are property injection and method injection. This is not really anything new. Since the advent of OO interface based design this approach have been recommended, but now this specific way of coding OO have a name
Dependency injection.
The highest point in the call stack with regards to
Dependency Injection is normally called the Composition Root. Composition Root is normally located as close to the application start as possible, so for an ASP.NET application this would normally be the Global.asax.
That the subject of the next post in this series. How to manually implement Dependency Injection in ASP.NET MVC.