1
\$\begingroup\$

After what I've felt is a sufficient amount of research, I have made my first attempt at a domain driven design MVC project. I would love for you guys to tear it apart and give me pointers to make it better.

The actual "data tier" has 2 projects, MyProject.Data and MyProjects.Model. I wont go into detail on the Model project, it's quite literally just POCOs for Entity Framework Code First and the DBContext. For this example I'll be using my user_account entity.

MyProject.Data is effectively a repository layer, I have a base repository and interface that will expose commonly used EF operations:

public interface IBaseRepository<T> : IDisposable where T : class
{

    IQueryable<T> GetAll();
    IQueryable<T> FindBy(Expression<Func<T, bool>> predicate);
    void Add(T entity);
    void Delete(T entity);
    void Edit(T entity);
    void Save();
}

public abstract class BaseRepository<C, T> :
    IBaseRepository<T> where T : class where C : DbContext, new()
{

    private C _entities = new C();
    protected C Context
    {

        get { return _entities; }
        set { _entities = value; }
    }

    public virtual IQueryable<T> GetAll()
    {

        IQueryable<T> query = _entities.Set<T>();
        return query;
    }

    public IQueryable<T> FindBy(System.Linq.Expressions.Expression<Func<T, bool>> predicate)
    {

        IQueryable<T> query = _entities.Set<T>().Where(predicate);
        return query;
    }

    public virtual void Add(T entity)
    {

        _entities.Set<T>().Add(entity);
    }

    public virtual void Save()
    {

        _entities.SaveChanges();
    }
    etc....
}

And then I have a concrete class/interface combo specifically for each entity. Here's UserRepository.cs:

public interface IUserRepository : IBaseRepository<user_account>
{
    void CreatePasswordReset();
}

public class UserRepository : BaseRepository<DBContext, user_account>, IUserRepository
{
    public void CreatePasswordReset();
}

This allows the Data project to expose an interface for each entity and will by default have basic CRUD operations available. More operations for the repository can be added on using its specific repository, like CreatePasswordReset.

Next up is MyProject.Domain, aka my service layer. I feel that this layer could definitely use some pointers and refining. It feels repetitive. This layer contains a Models folder that is client friendly and will act as an intermediary between any clients and the repository. Here's an example of MyProject.Domain.Models.User.cs:

public class User
{
    public int Id { get; set; }
    [MaxLength(20)]
    [Required]
    [DisplayName("First Name")]
    public string FirstName { get; set; }
    [MaxLength(30)]
    [Required]
    [DisplayName("Last Name")]
    public string LastName { get; set; }
    [MaxLength(50)]
    [DisplayName("Address Line 1")]
    public string Address1 { get; set; }
    public string FullName => $"{FirstName} {LastName}";
}

The second functionality of the Domain layer is a little more in depth, but similar to the repository layer. Each Model gets it's own Interface/Concrete class that will perform operations on it's respective repository and return any data in the friendly, ready to be consumed format that the Model offers. Here's the MyProject.Domain.Services.(I)UserService.cs example:

public interface IUserService
{
    IEnumerable<User> GetAll();
    User GetOne(int id);
}

public class UserService : IUserService
{
    private IUserRepository _userRepository { get; set; }

    public UserService(IUserRepository userRepository)
    {
        if (userRepository == null) throw new ArgumentNullException(nameof(userRepository));
        _userRepository = userRepository;
    }

    public IEnumerable<User> GetAll()
    {
        var users = new List<User>();
        var usersList = _userRepository.GetAll();

        foreach(var user in usersList)
        {
            users.Add(Convert(user));
        }

        return users;
    }

    public User GetOne(int id)
    {
        return Convert(_userRepository.GetOne(id));
    }

    #region Conversion
    private User Convert(user_account userAccount)
    {
        if (userAccount == null) return new user_account();
        return new User
        {
            FirstName = userAccount.first_name,
            LastName = userAccount.last_name,
            Id = userAccount.user_id
        };
    }
    #endregion
}

The idea here is kind of the same as the repository layer, we expose the interface and do the conversions and operations in the concrete class. Most articles/blogs/whatever I've read recommend using an interface for your service/domain layer, but I'm having a very hard time understanding what benefits are gained here with the interfaces versus just creating a new instance of the UserServiceand running with it.

This has gotten so long and tiring that I wont go into great depth of the MVC project unless it's requested. Basically Unity dependency injection is used to bind the interfaces to the concrete classes, and the services are used in the controllers exactly like the repositories are used in the services. The controllers know absolutely nothing about the repository layer, it is 100% decoupled. The ViewModels on the MVC project are still used, but they're created specific for each view, they may contain a reference to the Domain.Model classes, and some select SelectListItem shenanigans.

This whole project has been a huge learning curve for me, can't wait to get some feedback.

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

Though overall structure of the project is good & which is been used by many others in their project, but I have some concerns about the Model layer having the user class.

  1. Since you have already mentioned that there are view models in place which interact between the controller & views, I doubt if you really need the User class properties to be decorated with the Data Annotation attributes. This layer is working more like a DTO object layer (Data Transfer Objects). If this is intended only for this then probably you can get rid of those & have these attributes decorated on your view Models.

  2. The Services layer have a function called Convert() which is converting the data base entity to the Model class. You can certainly use the Automapper here. This would provide more flexible way.

\$\endgroup\$
1
  • \$\begingroup\$ Thanks for the pointers! Glad to hear I'm not far off. The Models on the Domain layer will also be consumed by an API as well as an MVC project, which is why they're there but you make a good point none the less. I didn't even know about automapper, but I just looked into it briefly and it's beautiful, it;s going in thanks! \$\endgroup\$
    – Halter
    Apr 6, 2017 at 12:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.