views:

543

answers:

5

Hi,

i'm struggling a bit with repositories. I'm using C# and NHibernate. The question i have is : how much should my repository do before it calls a save or a get?

For example i have a user class which is an aggregate root. I want to call a method called "register" which will add the user and set some default values based on business rules and create some other entities which are also sub parts of the "user" root (ie address, groups, etc) . Should i call

userRepo.Register(newUser);

which would be(ignoring the obvious issues):

Regsiter(User newUser){
 newUser.SomeProp  = "Default Data";
 Group g= new Group;
 g.SomeProp2 = "Default Data";
 newUser.Groups.Add(g);
 Session.Save(g);
 Session.Save(newUser);
}

or should i have put register in a business layer and have it do:

Regsiter(User newUser){
 newUser.SomeProp  = "Default Data";
 Group g= new Group;
 g.SomeProp2 = "Default Data";
 newUser.Groups.Add(g);
 userRepo.Register(newUser, g);// this does session.save on both objects.
}

Both seems slightly wrong.

What's the correct approach?

edit -------------------------------

thanks for all the responses. I can't decide who is the most right and therefore which answer to accept.

Generally everyone is saying put the business rules in another layer. that makes sense but i'm unsure about the data calls for groups - since groups aren't an aggregate root they shouldn't have their own repository so how do i go about adding and saving them? In my project adding a group to the user's group collection doesn't automatically create the group in the db; i also need to call session.save on the object. so do i put that into the user repo as userRepo.SaveGroup(g)?

If i have a createGroup() in the other layer then it'll either need to use it's own repo or the users. or am i being thick?

+5  A: 

Personally, I keep the repository pattern as a substitute for sprocs. So my repository would have methods like getById(int id), save(), add(DomainObject obj) etc.

In a business tier, I'd have userManager.registerUser(string username, /* params, etc */). This would create the domain object. This method would just call the add() method in the data tier.

In short, the business tier is business-y, and the data tier is data-y.

Jarrett Meyer
Based on your method names, I would actually say your "repositories" actually follow the "data mapper" pattern from Fowlers PofEAA book. A Repository from Domain Driven Design is a collection-like interface rather than a class with specific methods.
jrista
A: 

What is your motivation for using the repository pattern in the first place? Usually, the repository is the abstraction of your object persistence logic. It retrieves items from (usually) a database and also handles updates, inserts and deletes.

If you were writing a test to check some logic and didn't want to hit the database what objects would you have to mock out? Usually the repository.

In scenario A) you cannot test a new user is created with the correct default data without directly hitting the database. Because of this, I would advocate keeping all business logic outside of the repositories and go with your second design

Kirschstein
A repository shouldn't handle updates. It should abstract database (or other persistence) dataaccess in a collection like interface. A normal dotnet collection like list or dictionary does not need to know about updates. Neither does a repository.
Paco
A: 

I see two potential "wrongness" issues (since you didn't really indicate what you thought was wrong with them).

  1. If the problem revolves around saving the group in the user registration method, or saving the user in the group methods, then you should separate these into different methods. (i.e. Register() would call RegisterGroup() or GetGroup()

  2. If the problem revolves around saving things before the user really is ready to conceptually save things, then keep track of what they wanted to add and wait until the overall "save" method is called before putting any of this information into storage.

John Fisher
+3  A: 

Determine where you want your Register method, maybe in a UserServices class. You can delegate object creation to a UserFactory. In a CreateNewUser() method, set up your defaults for the user and the Groups collection. Then call UserRepository.Save(newUser) to persist the data.

// UserServices class
public void Register(User newUser)
{
  // add custom registration stuff.
  _userRepository.Save(newUser);
}

// Application code
User user = UserFactory.CreateNewUser();
_userServices.Register(user);

// UserFactory
public User CreateNewUser()
{
  // all new user, group creation
}
Kevin Swiber
in this example the group creation is in the userfactory. where does the group creation db interaction happen? in a group repo or user repo?
Charlie Bear
It depends. There may be a case for a Group Repository. In practice, try to limit repositories to one per aggregate root. In such a case, use a GroupRepository to find an existing group and pass the Group as a parameter in to your UserFactory.CreateNewUser method. When the UserRepository saves the User, check your configuration to make sure you're saving the group association, too.
Kevin Swiber
Alternatively, it might make more sense to instantiate a new User object and have a UserServices.RegisterWithDefaultGroup(User user) method with the following implementation: Group defaultGroup = _groupRepository.FindByName("Default"); user.AddGroup(defaultGroup); Register(user); It really all depends on your application, the software architecture, and how strict you want to adhere to certain principles. Good luck!
Kevin Swiber
After long reflection i think kevin has the best answer, also the comment below on Jeffrey Palermo's Onion Architecture is very helpful.
Charlie Bear
+1  A: 

From your code example, I would call the Register method a service operation. As a service operation, it would belong on a service, or on a business component, rather than a repository. According to Evans (of DDD fame), a repository is a collection-like interface to entities stored in your database. The general idea is that you provide basic CRUD-like access to your entities through a repository to abstract the rest of your code away from lower-level data access details like an ORM tool.

For your example with Register...your service method would validate input, build the User object, and then call your repository to "Add" your new entity to the "repository" (which could be a database...but could also be something else...as far as your domain is concerned, it doesn't matter...its just a repository.)

jrista
Is the service operation inside a controller class or in the business layer? Or is it the same thing as far you are concerned in this case?
Charlie Bear
Well, I consider controllers to be different than services. A controller is an access point for human interaction...and generally, they should be as light weight as possible. Your controllers should do minimal orchestration of calls to other services, package up any data, and direct that data to a view, nothing more. A service can be an application service or a business service. Application services may perform purely application level work, or compose multiple business services...but a service is where the bulk of your behavior lies, including use of repositories.
jrista
@Charlie Bear: Check out Jeffrey Palermo's Onion Architecture. This is what I use for DDD-style apps. It may help explain What goes Where. Good luck! http://jeffreypalermo.com/blog/the-onion-architecture-part-1/
Kevin Swiber
@Kevin: Very nice link! Thanks. :)
jrista