I have a repository data access pattern like so:
IRepository<T>
{
.. query members ..
void Add(T item);
void Remove(T item);
void SaveChanges();
}
Imagine the scenario where I have a repository of users, users have a user name which is unique, if I create a new user with a username that exists (imagine I have a dumb ui layer that doesn't check), when I add it to the repository, all is fine.. when I hit SaveChanges, my repository attempts to save the item to the database, my database is enforcing these rules luckily and throws me back an aborted exception due to a unique key violation.
It seems to me that, generally this validation is done at the layer ABOVE the repository, the layers that call it know they should ensure this rule, and will pre-check and execute (hopefully in some kind of transaction scope to avoid races, but doesn't always seem possible with the medium ignorance that exists).
Shouldn't my repository be enforcing these rules? what happens if my medium is dumb, such as a flat database without any integrity checks?
And if the repository is validating these kind of things, how would they inform callers about the violation in a way that the caller can accurately identify what went wrong, exceptions seem like a poor way to handle this because their relatively expensive and are hard to specialize down to a specific violation..
I've been playing around with a 'Can' pattern, for example.. CanAdd with Add, add will call CanAdd and throw an invalid operation exception if can returns a violation.. CanAdd also returns a list of violations about what went wrong.. this way I can start to stack these routines through the stack.. for example, the service layer above would also have a 'Can' method that would return the repositories report + any additional violations it wanted to check (such as more complicated business rules, such as which users can invoke specific actions).
Validation of data is such a fundamental yet I feel there is no real guidance for how to handle more advanced validation requirements reliably.
Edit, additionally in this case, how do you handle validation of entities that are in the repository and are updated via change tracking.. for example:
using (var repo = ...)
{
var user = repo.GetUser('user_b');
user.Username = 'user_a';
repo.SaveChanges(); // boom!
}
As you could imagine, this will cause an exception.. going deeper down the rabbit hole, imagine I've got a validation system in place when I add the user, and I do something like this:
using (var repo = ...)
{
var newUser = new User('user_c');
repo.Add(newUser); // so far so good.
var otherUser = repo.GetUser('user_b');
otherUser.Username = 'user_c';
repo.SaveChanges(); // boom!
}
In this case, validating when adding the user was pointless, as 'downstream' actions could screw us up anyway, the add validation rule would need to check the actual persistence storage AND any items queued up to persist.
This still doesn't stop the previous change tracking problem.. so now do I start to validate the save changes call? it seems like there would be a huge amount of violations that could happen from aparently unrelated actions.
Perhaps I'm asking for an unrealistic, perfect safety net?
Thanks in advance, Stephen.