Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

IdentityUser in Data layer

I am struggling with my design (or over-design) of a web project I am doing.

I have a MyProject.Data, MyProject.Business, and MyProject.Web DLL's.

My data layer (EF6 based) contains my entities, db context.

My business layer contains my repositories (yeah maybe not a true repo pattern).

I have added IdentityFramwork to the Web Project, and of cource it creates ApplicationUser. I already have a User POCO in my Data layer. I would like to move the Application user into the Data layer, so I can use it in conjunction with other entities.

One way to do this is have my Data.User extend IdentityUser, and also have my Data.MyContext extend IdentityDbContext. This results in the Data layer being strongly coupled to the asp.net.identity framework which doesn't quite feel right.

What is the best practise here?

like image 836
sheamus Avatar asked Mar 14 '14 18:03

sheamus


1 Answers

I would not recommend making User extends ApplicationUser. I consider ApplicationUser just a way an user can access your application (through login with email and pass, through facebook, google). Your User entity belongs to your domain, thus you don't need password there (as an example). Actually, a single User could have many ApplicationUsers associated, why couldn't the same user login with differente accounts?

It's ok to make your Data Layer references Identity's dlls, how often are you going to change this in your project? I sincerely have never seen this happens, once you put a project to run with a defined framework, you rarely change it, of course your application might grow, but you rarely need to change things like this. This way, your Data Layer is not coupled to a web project, but to a library (like sunil mentioned). AspNetUser's tables will be there, but not necessarilly a WPF project need to use it as a login method

Furthermore, I suggest you to keep your entities in the Business Layer (I also encourage not to use DataAnnotations there), then make your Data Layer map them with Entity Framework using Fluent API, and encapsulate EF inside your Concrete Repositories, which should be in Data Layer just implementing Repositories interfaces from Business Layer.

Make your DBContext extends IdentityDbContext, but don't make your User extends ApplicationUser, you could make an ApplicationUser has an User instead. Whenever a new User logs in your application, where you would normally create a new ApplicationUser, you could also create an User and associate with it. Once logged, this user could associate another external logins to his account (which means many ApplicationUsers to the same User).

Code Sample below I didn't get for any existing source, I've just invented for analysis

Business Layer - should not know about other layers, it's the simplest possible, should express your real business and be part of the Ubiquitous Language

public class User: ITrackable
{
    public int ID { get; protected set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public UserStatus Status { get; set; }
    public DateTime? Birthday { get; set; }
    public virtual ICollection<Address> Addresses { get; protected set; }

    public string LastKnownPlace { get; set; }

    public virtual bool IsPremium()
    {
        // some logic
    }

    public string GetTrackingIdentification()
    {
        // gets some unique key representing this object. Shouldn't be ID, since I might track other objects, soon IDs would dup for different objects...
    }

}

public interface ITrackable
{

    string GetTrackingIdentification();
    string LastKnownPlace { get; set; }

}

public interface ITrackingService<T> where T: ITrackable
{

    void Track(IEnumerable<T> source);

}

public interface IUserTrackingService: ITrackingService<User>
{

    IEnumerable<User> GetDeadbetUsersWithTracking();

}

public interface IUserRepository
{
    IEnumerable<User> GetPremiumUsers();
    IEnumerable<User> GetDeadbets();
}

Infrastructure Layer - should deal with how actions requested from applications should be executed. It could be using database persistence by implementing repositories, either by using Entity Framework or any other provider, or writting to text files, queuing tasks, sending e-mails, logging, maybe writting to registry, these can be known as Infra Services. Here you should reference Business layer and you could reference Identity's dlls as well. To keep example simple, I'm just implementing repositories:

public class YourContext : IdentityDbContext<ApplicationUser>
{

    DbSet<User> DomainUsers { get; set; }
    DbSet<Address> Addresses { get; set; }

    public YourContext(): base("DefaultConnection", throwIfV1Schema: false) { }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<IdentityRole>().Property(r => r.Name).HasMaxLength(200);
        modelBuilder.Entity<ApplicationUser>().Property(a => a.UserName).HasMaxLength(200);
        modelBuilder.Entity<User>().Property(u => u.Name).IsRequired().HasMaxLength(50);
    }

}

Service Layer - Will reference Businness and maybe Infra layers, implementing Business Services. Service is an ambiguous term here. There are Application Services, Domain Services and Infra Services. A Domain Service is a complex/generic operation which is part of the business logic, but does not belong to specific entities. Application Service is a way to expose actions to external worlds via APIs, WCF, etc. Infra services I explained before. I understand a Service Layer as a Layer which implements Business Services and exposes to Applications. Since Business does not know how services are implemented, it's ok to delegate to external APIs, for example. That way, business is still decoupled from everything.

public class UserTrackingService : IUserTrackingService
{

    private IUserRepository repo;

    // Injects an UserRepository to the service...
    public UserTrackingService(IUserRepository repository)
    {
        this.repo = repository;
    }

    public IEnumerable<User> GetDeadbetUsersWithTracking()
    {
        var users = this.repo.GetDeadbets();
        this.Track(users);
        return users
    }

    public void Track(IEnumerable<User> source)
    {
        // This would use each ITrackable's (in this case each User) Identification to request some external API, get last known place and set to ITrackable's LastKnownPlace property...
        api.Track(source);
    }

}

Application Layer - might reference Business, Infra/Data and Service Layers. Here you should express/defined what actions an app can do, but not how. Apps just receive requests, authenticates the requester and guarantees they are authorized to perform said action. Once everything is ok, it delegates either to a service/business/infra layers. Here is strongly recommended to use DTOs (or ViewModels) to receive and return data to external world. It might receives domain data from service/business/infra layers, translates to DTOs and return. This can be interpreted as the Controllers.

public class TrackedUserDTO
{
    public string Name { get; set; }
    public string MainAddress { get; set; }
    public string LastKnownPlace { get; set; }
    public decimal TotalDebt { get; set; }
    public bool ShouldBeContacted { get; set; }

}

public class UserController : Controller
{

    private IUserTrackingService service;
    private IUserRepository repository;

    // Injected - IoC
    public UserController(IUserTrackingService service, IUserRepository repository)
    {
        this.service = service;
        this.repository = repository;
    }

    public ActionResult Index()
    {
        return View();
    }

    [Authorize(Roles="Manager,Admin", ErrorMessage="You aren't allowed do see this content...")]
    public ActionResult GetDeadbetUsersWithTracking()
    {
        IEnumerable<User> users = this.service.GetDeadbetUsersWithTracking();
        IEnumerable<TrackedUserDTO> dtoUsers = Mapper.Map<IEnumerable<TrackedUserDTO>>(users);
        return View(dtoUsers);
    }

I strongly recommend reading these:

Creating Domain Services by Philip Brown

Services in Domain-Driven Design by Jimmy Bogard

Why not use DTOs inside business layer? To avoid Anemic Domain Model (by Martin Fowler)

like image 175
Alisson Reinaldo Silva Avatar answered Nov 02 '22 21:11

Alisson Reinaldo Silva