posted on 08/28/08 at 06:13:35 pm by Joel Ross
Earlier today, there was a great discussion in an IRC channel about entities and whether they should have behaviors or not, and that eventually moved onto Repositories. It's a good discussion and after it was over, several of us felt it would be a good reference for later. Since I have a log of the conversation, I figured I'd repost it for future reference. Hopefully, you'll find it a good read as well.
For a bit of reference, this conversation took place in ##twittertribe on irc.freenode.net (yes, a double #). The main players are (in order of appearance) Michael Eaton (mjeaton), Eric Ridgeway (Ang3lFir3), Nate Kohari (nkohari), Matt Davis (mattsonlyattack), James Avery (averyj), Michael Letterle (TheProkrammer), me (RossCode), James Bender (JamesBender), Jay Wren (evarlast), and Nick Quaranto (qrush).
mjeaton: Ok. Entities - behaviors: yes or no?
Ang3lFir3: mjeaton: yes?
mjeaton: Ang3lFir3: serious question. what do you think?
nkohari: mjeaton: if you mean behaviors as in business logic, yes
Ang3lFir3: mjeaton: i've heard yes.. and I think its reasonable.... but i also think it could get out of hand
mjeaton: Ok. Customer class. Does it have a .Save()?
Ang3lFir3: i see visions of Entities with 10 properties and 15 "Actions"
averyj: mjeaton: I would say yes
TheProkrammer: mjeaton: Ideally, no.
mjeaton: I LOVE IRC!
TheProkrammer: awesome. :)
Ang3lFir3: mjeaton: it has a Save if you are using active record
averyj: but if you want to be all IOC you could have the Customer class take an ICustomerRepository
mjeaton: you guys rock. guaranteed that any question I ask will result in a 50/50 split
averyj: and it uses that to save
RossCode: mjeaton: I can see both ways, but for the most part, yes
nkohari: averyj: false
nkohari: you want the ICustomerRepository to take a Customer, not the other way around
mattsonlyattack: the Customer class wouldn't take a repository
mattsonlyattack: SRP bitches
nkohari: that's too activerecordy
JamesBender: Didn't we have this discussion already?
averyj: dont build an aenemic domain model for PIs sake
mjeaton: how many of you think PI is important?
nkohari: it's a nice-to-have
nkohari: it helps during development
RossCode: mjeaton: I don't, really
nkohari: but i don't buy the "oh i might switch dbms's" argument
mjeaton: nkohari: how does it help?
Ang3lFir3: PI is pretty.... but i would decide based on project scale
JamesBender: It makes some things easier
evarlast: averyj: the customer class wouldn't take an ICustomerRepository.
evarlast: Customer should have no deps other than its object relations.
nkohari: mjeaton: you can design against your domain model without worrying about where the entities are stored
nkohari: and you can create an in-memory backing store for testing
Ang3lFir3: scale and complexity of the domain... simple domain then i prolly don't need PI ... and go straight to AR
mjeaton: nkohari: /devils_advocate: explain domain model
nkohari: so you don't need to have a database during testing
evarlast: Repositories know about entities, but Entities need not and shouldn't know about their repositories. NH FTW, Entity Framework FTL
nkohari: the term "domain model" sucks, but to me it means the entities that represent your business... ie. customer, order, invoice
RossCode: even with activerecord, you don't necessarily have to have a database to test
Ang3lFir3: nkohari: true
mjeaton: Ok. So UI talks to controller. Controller talks to ??
nkohari: ICustomerRepository, for example
Ang3lFir3: mjeaton: controller uses repo and sends data to View (UI)
mjeaton: Controller talks to repository and returns instances/collections of Entities?
mattsonlyattack: or an application service
RossCode: nkohari: do you have one repository or multiple repositories?
mjeaton: mattsonlyattack: explain.
Ang3lFir3: technically the returned instances don't HAVE to be entities
nkohari: RossCode: lately i've been using one
evarlast: the nice thing about repo model is when you scale you can scale out and instead of talking direct to DB you can make your Repo talk to a web service or maybe something simple like ASP Dynamic Data and your entities and repository interfaces dont' change.
nkohari: but you can only get away with that in some cases
RossCode: nkohari: how do you handle custom queries? Pass in ICriteria?
Ang3lFir3: isn't the multiple repo thing about when you get into Aggregate roots etc?
mattsonlyattack: for behaviors that don't directly map to a single entity some times services are used to control the collaboration, and they'll use respositories as well
nkohari: yeah, in the past i have, but lately i've moved to linq for nh
nkohari: so it's all Expression<Func<T, bool>>s
Ang3lFir3: linq for nh FTW
RossCode: nkohari: but then aren't your queries and how you query data in your controllers now?
nkohari: RossCode: touche ;)
nkohari: chadmyers was just talking about that
nkohari: he suggests creating query objects
nkohari: which is actually a very good idea
evarlast: if you look at the whole NH Criteria API, its mostly duplicate of what is now available with Expression<> in 3.5
nkohari: evarlast: that's the idea
mjeaton: someone raise d a good question: one repository or multiple?
nkohari: it's a linq provider...
RossCode: nkohari: I'm not saying it's bad. I think I would rather encapsulate how I get my data in a repository and have hte controller call the repository
nkohari: RossCode: you can definitely do that
nkohari: the other argument is that you have one 'data context' that provides low-level access to the IQueryable<T>s
nkohari: and then you build repositories on top of that
mattsonlyattack: one repository per aggregate root is the standard response
nkohari: bradwilson was advocating that on twitter
mjeaton: mattsonlyattack: don't want standard response. i want to know what you guys are doing.
nkohari: mattsonlyattack: yeah, there's more flexibility there for sure
Ang3lFir3: mattsonlyattack: define aggregate root.... cuz no one has done that very well yet
RossCode: nkohari: that makes sense - so public repositories all talk to one lower level repository?
RossCode: Ang3lFir3: have you watched the DDD chalk talk by bsimser?
nkohari: RossCode: yeah, the data context is just an object with a bunch of IQueryable<T> properties
mattsonlyattack: watching that greg young chalk talk is good for this question
Ang3lFir3: RossCode: yup... he didn't make it clear enough
nkohari: and then you can create a mock one by implementing each with a List<T> (i think?)
mattsonlyattack: it seems he redrew boundries for multiple aggregate roots with the same sets of entities
mjeaton: see, when i hear nkohari go off like that, I think "wow, complexity. is it needed?"
nkohari: mjeaton: nope, it's not
mjeaton: of course, I'm the dumbest guy in this channel right now...
Ang3lFir3: mattsonlyattack: that is supposed to be ok... some aggregate roots are allowed to have the same entities in them... they are supposed to be about concepts i think
JamesBender: mjeaton No you're not.
mattsonlyattack: mjeaton: not the dumbest by a long shot
averyj: If I was building bank sofware, I would do all of this
nkohari: it's not important in all cases
RossCode: mjeaton: depends on how big the project is, I would say
averyj: but I am not, I am building simple software
averyj: so I just need simple solutions
Ang3lFir3: mjeaton: no you aren't
nkohari: if you code against repositories though you get some benefits
nkohari: like for example
averyj: ActiveRecord is simple
mattsonlyattack: Ang3lFir3: agreed, the aggregates help w/logical paths to other objects
nkohari: ideavine reads info from xml files right now
nkohari: but eventually we might want to move it to a db
nkohari: that's ultra-simple with a repository-based approach
averyj: use tests and re-factor
nkohari: i'm not disagreeing with that
averyj: we might never move it though, and now we have un-needed complexity
nkohari: but this approach lets you refactor less
nkohari: not really
mjeaton: all of you need to go to summer camp dammit.
nkohari: what else would i have done?
averyj: I agree we need to design for change, but isn't that what my unit tests are?
nkohari: private methods that would read the xml?
nkohari: that's not any easier than what i did
averyj: yeah, I can see that
Ang3lFir3: my issue in our domain is that while I have entities of the same types...the definition of those entities is different for all implementations
averyj: the repository for getting the XML makes sense because you had to have something to do that
nkohari: i guarantee, once you grok DI this stuff doesn't sound nearly as complex
averyj: and that is a good simple pattern
averyj: Its not the complexity that bothers me as much as the obfuscation of the code
nkohari: i just naturally smash this stuff out now
nkohari: averyj: that's true
nkohari: that's why i favor [Inject] actually
averyj: It is way harder for me to grok an application like that, especially when there are levels and levels of stuff being injected
RossCode: averyj; I agree- that's one of the problems I have with interfaces for everything
After a short lull (not fit to be reprinted here), James Avery got the conversation back on track.
averyj: ok, repository question for the room. I actually did a 2 year project using the pattern we are talking about but we used a generic Repository, what the reason to have a separate CustomerRepository?
nkohari: averyj: you can wrap queries inside the repository
nkohari: er, customerrepository
mjeaton: so, IRepository<T> vs. IFooRepository?
nkohari: queries with business logic in them, like GetAllLoyalCustomers()
averyj: we actually had just one Repository and it had stuff like Save<>
averyj: its the one Dave made open source
averyj: NHibernateRepository I think
mjeaton: averyj: yea, we used that on a project.
evarlast: Save<> ? that is horrible
averyj: actually based on Bellwares code from like 4 years ago
RossCode: averyj: where do you put code for things like GetCustomersByFirstName?
averyj: on this project we had separate data objects when we needed them
TheProkrammer: Customers.Find( c => c.FirstName = "value")
TheProkrammer: err ==
averyj: but I could see that, use CustomerRepository for the custom queries
RossCode: TheProkrammer: in your controllers?
averyj: so yo have a base repostiory it all inherits form?
TheProkrammer: bah, I'm working here, stop making me think.
RossCode: averyj: I laid out what I did in a blog post just last night
RossCode: I used an interface and extension methods
mjeaton: RossCode: link?
RossCode: Note: I've been playing with NHibernate for 1 week, so i could be completely off base. I probably am
averyj: intereting, why not use a base class?
RossCode: testing, mainly. Since it's interface based, I can use Moq to mock the repositories
averyj: you can still have an interface
averyj: just thinking instead of the extension methods you could haev a base class
averyj: I could be missing something though
RossCode: so have CustomerRepository: IRepository<T>, BaseRepository?
mattsonlyattack: qrush: http://code.google.com/p/moq/
RossCode: I think I tried that, but then BaseRepository has no knowldege of T
mjeaton: Moq is cool, but people tell me Rhino caught up with the features.
RossCode: I guess I could have made it BaseRepository<T>
qrush: but seriously. lots of new libs using 3.5
qrush: i need to try it someday :/
RossCode: mjeaton: I saw the new syntax for Rhino recently - very similar to Moq's syntax
RossCode: I picked Moq before I saw the new way Rhino works. The old way looked too complicated for me
mjeaton: wishes he was sitting with rosscode, averyj and a laptop right now.
mjeaton: RossCode, agreed. The record/playback stuff was crap.
mattsonlyattack: which was still in moq, just implied by the syntax
mattsonlyattack: moq is still sexy
RossCode: You guys are ruining my day. I'm supposed to be working. How am I supposed to prepare for the fantasy draft tonight?
This is the power of the tribe - impromptu discussions on just about any topic. It's guaranteed to generate a conversation. Want to be part of the next one? Stop by - we'll be there!