3

In my attempt to keep with a good Repository/IoC Design, I am trying to figure out the best approach the current problem.

There are two objects, Member, and Character.

Basically, one Member can have many characters. Simple enough.

Member
{
 IList<Character> Characters { get; set; }
}

There is an IMemberRepository...

interface IMemberRepository
{
 MemberCreateStatus CreateMember(string email, string password);
 bool ValidateMember(string email, string password);
 bool ChangePassword(string email, string password, string newPassword);
 void RecoverPassword(string email);
}

And there is an ICharacterRepository

interface ICharacterRepository
{
 Character CreateCharacter(string name);
}

So then, my question is quite simple. Where do I want to add the logic to add a Character to a Member? Do I do this in IMemberRepository? That seems cumbersome to me. It seems beyond the scope of the IMemberRepository's tasks. Do I add it to the ICharacterRepository? That too seems a bit strange to me, because giving that knowledge of the Membership seems to be in violation of keeping them separated. What's the best approach to take, here?

If it were just this one method, it wouldn't be too big a deal - but there will be a lot of other things that have to occur between the two classes. What's the 'standards' way to approach this situation of two objects relating to one another? I am coding this in ASP.NET MVC.

Ciel
  • 17,312
  • 21
  • 104
  • 199

3 Answers3

4

I think you are maybe approaching Repositories a little differently than intended, and that may be the cause of some of your confusion.

Repositories are to provide collection semantics to a set of data. That means Repositories Get, Add, and Remove stuff. They don't validate. They don't change things about individual items. They are not a Data Access Object (DAO), though it seems many people lately name a thing Repository and then make it look like a DAO.

Your Member contains a list of Characters. Great! Want to add a Character to a Member? Do exactly that. Member.AddCharacter(...) or Member.Characters.Add(...). I usually use the former, but the latter can work as well (but requires custom collection implementations).

quentin-starin
  • 26,121
  • 7
  • 68
  • 86
  • my repositories only do the 1 validateMember because it requires databse acess. It isn't tied to my models. Why would using the Add(T) method require custom collection implementation? – Ciel Dec 16 '10 at 18:09
  • Okay, I kind of get what you're saying - but this has me even more confused now. Where am I supposed to put more complicated logic that has to get data out of the data store? I thought the Repository was where I accessed data, so that the Business and UI layers didn't have any knowledge of the data layer. – Ciel Dec 16 '10 at 19:15
  • Your repository wouldn't validate a member itself, what it would do is retrieve the `Member` data from the database and return it to your business layer. Your business layer would then validate that the password is correct (or make sure that a record was returned by the repository). – KallDrexx Dec 16 '10 at 19:21
  • @Stacey: Are you are doing a Domain/Object Model or a Transaction Script/Services style? If you're creating a domain or object model, then you really shouldn't have much logic that needs to get data out of the data store. You get one object - the root object, or aggregate - and with it should come everything needed to perform logic on it (in most cases). Alternatively, if you are doing a traditional Service/Transaction script (often one class per table), then your "repositories" will be simple gateways to the database and the methods you wonder about belong in Services or Commands. – quentin-starin Dec 16 '10 at 19:45
  • @qstarin: ...I'm sorry, I have no idea what you just said. – Ciel Dec 16 '10 at 20:01
  • If I put the AddCharacter method in the `Member` class, then I don't have access to the `ISessionFactory` that is needed to save the data. I've tried it. I don't comprehend how I am supposed to get something added to the list if I can't touch the object it has to be added to, or something that can touch it. – Ciel Dec 16 '10 at 20:28
  • I've tried just doing it without using the Repositories and it doesn't work. I can't get the `IList` without turning off the Lazy Loading - and I can't eager load the collection in the root of the Repository. The concept you are proposing doesn't make any sense to me. You're saying 'Your domain layer can handle everything' - but that simply isn't possible with concepts like Lazy Loading and Sessions/Transactions. I don't understand how you're expecting me to be able to add to a collection that can't be loaded unless I make a `Load` method for every property on every object in the repository – Ciel Dec 16 '10 at 21:22
  • Member shouldn't have anything to do with persistence. The repository or a UnitOfWork abstraction should handle persisting changes. If using a mature ORM, and that collection is mapped, then entities added to a child collection should automatically get persisted with the parent entity. Who has the ISessionFactory to persist your Member? If Characters are under Member, that's who persists them. I still say you need to understand and decide between the two styles I mentioned (google the terms). If you're doing Transaction Script, that Characters list in Member probably shouldn't even exist. – quentin-starin Dec 16 '10 at 21:24
  • The Repository has the `ISessionFactory`. The Domain Objects have no concept of the ORM. I am using nHibernate. The Repository **does** use a UnitOfWork abstraction to handle changes. That's the problem. What you're telling me to do isn't possible the way you're telling me to do it. The Domain Objects *do not know* what the ORM is. They have no concept of it. At all. But nHibernate uses proxies and lazy loading. I cannot `RetrieveMember`, and then use its methods freely. I have to do it within the UnitOfWork in the Repository --> – Ciel Dec 16 '10 at 21:34
  • And because of that, I am led back to my original question - I have to make Repository Methods for adding something to it. What you're telling me is completely backwards from everything I've spent the last 7 days reading on IoC and DI. I'm not trying to be rude or hostile, I just have no idea what you're saying. – Ciel Dec 16 '10 at 21:35
  • If I do `GetMember(id)`, I'll get a `Member` object from the Repository, with some nHibernate proxies for the Lazy Loaded `IList`. So then, calling `member.Characters.Add(character)` throws an error - because using *UnitOfWork* pattern means I have to dispose of the `Session` after it runs its query. To do anything else would mean I have to do this business logic at the Controller level, which is completely against everything I've been learning. If I didn't have to deal with an ORM,I see where you are coming from - but I do have to deal with an ORM, so I don't have that option. – Ciel Dec 16 '10 at 21:40
  • But then, on the same token, you just told me I'm not using a Repository correctly. So now I'm thrice confused. If the Repository is supposed to be where I do my Data Access, and the Domain Layer is where my Domain Objects are, and never the two shall meet - I don't comprehend how I can do adding/removing logic when things like Lazy Loading have to be taken into account. *Please, Please* understand again I'm not being hostile or defensive - I am just *very* confused and lost now. I _finally_ thought I was starting to understand this, and now ...another complete curveball... – Ciel Dec 16 '10 at 21:43
  • So do I not use Repositories for anything but Adding/Deleting? Where do I use Data Access Logic for more complicated tasks, then? – Ciel Dec 16 '10 at 22:08
  • 1
    This is getting into an extended discussion not well suited for here, and I think we are mis-communicating a tad, though I think I understand your situation better now. I'm also terribly busy at the moment. But yes, I find ORM lazy loading introduces exactly these complications and I therefore avoid it. Perhaps that isn't possible in your scenario. Events could be used to solve this potentially. Calling the add method *doesn't* actually add the item, but raises an event signaling a Character has been added, and a handler takes care of pulling the aggregate and adding. – quentin-starin Dec 16 '10 at 22:11
  • Alright. I understand what you are saying. I've spent some hours abstracting and I have posed the question a bit differently based on some new information at http://stackoverflow.com/questions/4473713/implementing-unitofwork-with-castle-windsor – Ciel Dec 17 '10 at 19:06
1

I feel it is responsibility of IMemberRepository to populate Characters of a Member. There are a few reasons:

  • Since IMemberRepository is Member type's Repository, it knows what Member is composed of. And that is why it knows about Character type.
  • It is certainly not ICharacterRepository's responsibility. Character type can be used to compose other types in future. That will affect Character's Repository whenever you add new type.
  • IMemberRepository will be using ICharacterRepository to populate Character objects in Member.
decyclone
  • 30,394
  • 6
  • 63
  • 80
0

The most natural approach for me is to put that logic inside the Member class as the Member object is responsible of tracking its own Characters.

What do you think about that?

Ignacio Soler Garcia
  • 21,122
  • 31
  • 128
  • 207
  • SoMos - in my opinion, that's not the most flexible way of doing things. I can see the path of least resistance leading to that approach. however, you sould have an interface that you base your member properties against, as well as a generic T repository for the basic saving/retrieval. you would then have a generically typed 'service or task' class that would do anything outwith the norm for a given class entity. that's my boiled down approach anyway... – jim tollan Dec 16 '10 at 17:37