Still lost on Repositories and Decoupling, ASP.NET MVC
I'm still on my eternal quest to build (and understand) modern programming convention of decoupling, IoC, DI, etc. I'm to the part where I am trying to figure out how to build a repository. I've examined the post at Database abstraction layer design - Using IRepository the right way? which was very helpful, but I've still got some problems that are just befuddling me all the way.
I have my program in now 4 layers...
Web (Project | ASP.NET MVC Application) - References Models.dll and Persistence.dll
Models (Domain Objects)
Persistence (Fluent nHibernate Mapping of Domain Objects)
Utilities (Providers, Repositories)
Now then, I'm trying to write up a simple Membership Repository. My first task... ?
Check to see if an email address exists when someone tries to register. That seemed all well and good - so I go to try and figure out where to place this.
At first though, I would just place it inside of the MembershipProvider
class CreateUser
method. This, however, resides in the Utilities project. So far, Utilities has no knowledge of nHibernate. Only the Persistence Project has any knowledge of nHibernate.
So then, my CreateUser
method needs to query my database. So what's the best practice here? Do I create a UserRepository
in the Persistence
project, and just make an entire method called CheckEmail
? Or do I simply add the nHibernate .dll's to my Utilities
project, and write session lookup within the Provider?
It seems like more work to make repositories in my Persistence Project that do specific actions than it is to make the providers. Why a开发者_Python百科m I even making the Providers if I have to make Repositories for them? Isn't the purpose of all of these new methods to stop code repetition? But it feels like to keep things 'separate' I have to write the same code 2 or 3 times. What is the best practice here?
Your repositories should really be implemented in your Persistence assembly. Assuming you are unit testing them, you would define the interface for each repository in your Domain assembly.
Your CreateUser
method shouldn't be directly querying the database to determine if the email address already exists, instead create a separate method in your DoesEmailExist
which is responsible for doing that check. Each method should have a single responsiblity.
In response to jfar's doubts:
That's right, the domain defines what can be done, defining interfaces such as Domain.IUserRepository.Create(User user)
. The domain does not however define any implementation.
Let's say you start out using Entity Framework, you might create a Persistence
assembly which implements the interfaces defined in the domain. So following on from the domain interface above we implement the interface:
namespace Persistence
{
public class UserRepository : Domain.IUserRepository
{
public void Create(User user)
{
// use Entity Framework to persist a user
}
}
}
Let's say for example that your customer later tells you to implement an NHibernate persistence layer. Luckily our domain is separate to the existing persistence layer - it's in the domain. So you can easily implement the existing domain interfaces without needing to change any code in your MVC application - as all that knows about is the interface you defined, not the Entity Framework implementation.
Your IoC container can then be configured to resolve IUserRepository
as either the Entity Framework or NHibernate implementation, your MVC application doesn't care either way.
In terms of assembly references, the Persistence
assembly has a reference to the Domain
, and the Domain
quite rightly does not have a reference to Persistence
.
This leads to a decoupled design which is easy to test, and change going forwards, resulting in easier maintenance.
I hope that helped.
The Repositories Interface should be placed on the Domain Layer, the implementation of this Repositories are going to be NHibernate classes on the Infrastructure Layer. Your UI Layer knows about the infrastructure, but will only depend on the repository interface which will be injected by hand or via DI container.
I use the following convention to help me decide for functionality like you described above:
Case: Check if email address already exists when a user registers
1 - Validation Type: input validation
Description: is the email in the valid format, length, characters etc.
Done by: The User object when the property is set, else it will throw an exception
2 - Validation Type: business rule validation
Description: does a user with the same email already exist or any such business rule?
Done by: The UserRepository as it will be able to query across all the users to find this information before the Commit is called
Based on this, my code for the user would look like:
// Method - can be called by the Controller layer to register a user
// Can be a private method of the controller itself or can be abstracted by a
// IAccountManager implementation with this method
void RegisterUser(string emailId){
var user = new User();
user.Email = emailId;
if(!user.IsValid){
// Some input data is not correct
// Handle the case & exit this method
}
var repository = serviceLocator.GetInstance<IUserRepository>();
repository.Save(user);
if(!repository.IsValid){
// Some business rule error
// Handle the case & exit this method
}
else{
repository.Commit();
}
}
So, while I understand you are having trouble looking at all the layers, I would categorize them as:
Web Project - is the View part of your application & will have only databing logic to output a view
Models - a POCO's with logic to validate themselves
Repository - calls the persistance layer if the object is valid & all the business rules are valid
Persistance layer - persists the data to the store
IoC/DI - will resolve instances & give you the correct instance to work with
HTH
Stacey, Take a look at this video. The video was prepared using ASP.NET MVC1, and I think that it is extremely high quality. Watch the video and study it. MVC2 is basically the same with an improved Html helper.
link Nerd Dinner
I agree with Spolto: MVC encourages the separation of concern and therefore produce high quality software which can be tested.
精彩评论