For those that use Paul Wilson's O/R mapper, here are some additional templates for generating a framework around that tool. While Paul Welter provides templates for this purpose, I don't particularly care for the pattern used in his generated classes (follows the 'ActiveRecord' design pattern, AFAIK). I personally don't like having business objects that 'know' anything about the concept of persistence (saving, getting, etc.). While this is a relatively common way of doing things (I did the same thing in the past), it doesn't work for me any longer simply because of my personal opinions with regards to application architecture. Granted, 'it depends' on the application - I simply don't find myself ever needing to write one with that pattern.
There is a readme.txt file that contains more than enough verbosity on what these do/don't do. However, here are some big ones:
I hope someone finds these useful.
Eric J. Smith
Founder / CEO
CodeSmith Tools, LLC
I looked at doing that before I posted, but alas I cannot find the way to post files to that section of the site. Is it that I don't have proper permissions or is it that I am so easily lost that I wouldn't find my way out of a paper bag? (maybe both)
Let me know and I will post them.
Thanks Jason - these are really cool. I definately agree with you on the manager-based persistence (Repository) being superior to the ActiveRecord pattern for all but the simplest apps. I think this will be especially apparent when you add transaction support. I was happy to learn that you are a fellow fan of Worm and that your architectual preferences are similar to mine, having seen your name around for a few years now.
I have 2 questions for you to consider.
Just a couple things to think about. I am by no means an expert in this stuff, but these are just some of the things I am wondering about.
Sorry about the file gallery. Looks like my permission settings were messed up. I have changed them now so that you should see a Upload button at the top of the file listing when you are in the folder.
> Thanks Jason - these are really cool.
Thanks, I doubt such a compliment is deserved though - you must not have looked too closely at these. By the way, I have updated the templates a tiny bit since I posted that yesterday; you might want to get the latest.
> Do you think there needs to be an EntityPersistenceManager (repository) for EVERY> domain entity? Or would it make more sense to only have these managers for the > aggregate-root types. For instance, realisticly we would never need to fetch a > collection of all the OrderItem objects, only those that are part of a root Order > - so we would just need a InstantiateNewOrderItem in the OrderManager itself, and > never need a GetAll method for the OrderItem.
I am all for making this generated framework better, and in order to do that I need feedback and to engage in an intelligent conversation about it (which would actually remove me from any such possibility); to that end, I appreciate your taking time to pose these questions. And now for the rambling that characterizes most conversations with me:
I think I understand what you are getting at, and I like the line of reasoning but it begs some questions that also need to be examined (questions that have probably been addressed by more intelligent people than myself). For example, we could say the same goes for the relationship between Customer and Order. To illustrate one of the questions I am referring to, let me paraphrase your own words, modified for this scenario:
...realistically we would never need to fetch a collection of all the Order objects, only those that are part of a root Customer - so we would just need an InstantiateNewOrder in the CustomerManager itself, and never need a GetAll method for the Order.
The question I am getting at is, what are the "aggregate-root" types? You could have an object hierarchy like Country->State->Customer->Order->OrderItem, etc. This line of questioning can go around and around, depending on how deep the object heirarchy may be. Now, this does not mean the question isn't valid, but the answer I am interested in getting is to the question about where and how we handle this idea of managing the objects, their relationships to each other, and their persistence in a way that creates and preserves semantics related to the business domain the application addresses. For example, a question that I am curious about is illustrated by this scenario:
How do we delete an instance of the OrderItem class? Let's say I have an Order that aggregates nine OrderItems, and I want to delete one of them; what would the code that I need to write look like? Here is code that does NOT persist the changed collection (i.e. it does not delete from the data store the same one we removed from the collection):
// let's retrieve an order Order previousOrder = OrderManager.GetOne(orderId); // arbitrarily, let's remove the 5th member of that collection OrderItem someOrderItem = previousOrder.OrderItems; previousOrder.OrderItems.Remove(someOrderItem); // second parameter tells WORM to use PersistDepth.ObjectGraph // when it persists the Order object OrderManager.Save(previousOrder, true);
The question is, does it make sense for this to NOT work? If we really want our relational world (the data store that we are mapping to) to reflect our OO world, I would think the data store should remove this OrderItem from its association with that Order (in the above example, it doesn't necessarily mean we want to delete it from the data store, just delete the relationship - we still have the someOrderItem object hanging around, which means it is now orphaned (and would that even be possible if we have a rule that no OrderItems can exist without an order?)).
There are so many questions, most of which, with respect to these templates and the code they produce, can be eliminated if we keep a lot of the low-level API around (I use the term "low-level" in a very relative sense - the functions of the Persistence Manager classes would be considered "low-level" in this conversation because they are relatively raw in their operations (i.e. they do basic CRUD without regard for business logic, etc.)) and force the developer to handle that each time it needs to be done. Personally I would like, as much as possible and where it makes sense, for the relational world to reflect the actions in the OO world. However, the reality is that this would greatly complicate things and doesn't always make sense.
> Would it buy us anything if instead of the repositories being static classes, we > use a singleton'd object with instance methods? With instances we can use things > like session-specific caching, isolated context, etc, which we lose the opportunity > using all static methods. So starting off with the singleon pattern, if we ever > decide we really NEED a new instance of a manager, we can just change the GetInstance > method to return a new instance instead of the static singleton. (Encapsulated > Construction).
Good idea, I would love to think more about this (have been meaning to think about it, actually) and see some examples of what you propose (only because I am short on time, as we all seem to be!). We get some caching capabilities with WORM, and where that happens I don't want to duplicate work. Also, unless it really will help things, I don't want to do work that might later be made moot by Paul adding more capabilities to WORM (not that it would be so horrible, I just want to know what the trade-offs would be and such - but it sounds like a good idea).
> Just a couple things to think about. I am by no means an expert in > this stuff, but these are just some of the things I am wondering about.
You and nearly 90% of other people are probably more expert in this than I - I just try to fake it enough to get a regular paycheck. Nah, I really dig this stuff and want to learn all that I can. Your questions are appreciated, and I hope you will entertain my ramblings (though much of them may be incoherent, ignorant or both) by continuing in this dialog.
Hey, I have done a tiny bit of reading (reading I have long wanted and needed to do) on the Repository pattern, and found something that helped me understand where it is I think you are coming from (thinking along the domain-driven design approach).
You may have already seen this, but you can download a copy of the patterns discussed in Eric Evans' Domain-Driven Design. On page 14 of that patterns document, there is a good description of the goals of using the Repository pattern which shed some light on your comments (for me). I want to read up on other patterns to get a better idea on which seems right for this set of templates; I definitely still lean to the domain-driven approach and think this is the 'right' direction for these to go.
Thanks again for your comments.
I was just about to post a link to that very site. I have not read that book yet but I plan to as soon as possible. I had read the summaries before and I guess it's where I got the notion of repositories. As to your question - what are the aggregate-roots - I guess the answer is "it depends" I am wondering now, too. In the docuemnt, the important lines relating to our question seem to be:
"For each type of object that needs global access...""Provide repositories only for aggregate roots that actually need direct access"
Still, that to me does not really answer the question and begs a real-world example. Maybe I'll order that book today...
Here are some other threads (which you have probably already seem) that might help too:http://forums.asp.net/1078887/ShowPost.aspxhttp://forums.asp.net/897523/ShowPost.aspx http://steve.emxsoftware.com/Domain+Driven+Design/How+Domain+Driven+Design+changed+my+way+of+thinking
At any rate, it seems we're not alone in this quest...
I will take a look at those posts, thanks. I have done a lot of thinking about certain aspects of these questions in the past, but because I never took the time to read prior works on the same subject, my ignorance was obvious.
As far as which ones are the root aggregates, yes - it always depends (and that is okay). If the application is being designed up front, which we all know happens all the time , these should be known when code generation needs to take place. Even if they are not, the fact that we are generating code should facilitate late changes. Ultimately, I do want these templates to facilitate a more intelligent code gen process that involves specifying domain-specific ideas as metadata. I haven't thought about all of this enough yet to really be able to quantify this or explain it, but suffice it to say that people smarter than I have done similar work (see Scott's TechEd presentation for some amazing code gen).
I too want to see a real-world example, since my question still remains - if Order is my 'chosen' aggregate root and I want to delete one of the aggregated OrderItems, how does that look? I don't want to do something like myOrder.OrderItems.Delete(); because that follows the Active Record pattern. I would imagine it might be something like OrderManager.RemoveOrderItem(itemToRemove); or OrderManager.RemoveOrderItem(myOrder.OrderItems); - that makes more sense to me. In fact, I am going to play with that idea a bit.
Of course, another good book is Patterns of Enterprise Application Architecture. I have access to that book, so I will take a look to see if it provides any helpful examples.
Thanks again for the dialogue.
I am comparing pwelter's templates with your templates. Doing that, a few probably trivial/stupid questions did come up. Please bear with me as I am a complete newbie in this kind of field.
1) You have not created any stubs for handling persistence events (IObjectNotifications). Is there a reason for this or did you simply not have time? If the later is the case would you implement it the same way pwelter did?
2) Your templates do not have any indexers. I am not missing them but I was wondering what your reason is not to include them.
3) Why did you decide to have a generic manager for persistence? Personally, I prefer one that is more specialist and also returns the actual class ("xxxBase.cs") rather then DataSet.
4) Performance wise wouldn't implementing IObjectHelper and returning xxxBase class be faster then returning DataSets due to marshaling, etc.?
Please note that is not criticism but I just try to understand the thoughts behind your approach?
> I am comparing pwelter's templates with your templates. Doing that, a few probably > trivial/stupid questions did come up. Please bear with me as I am a complete newbie > in this kind of field.
> 1) You have not created any stubs for handling persistence events (IObjectNotifications). > Is there a reason for this or did you simply not have time? If the later is the case > would you implement it the same way pwelter did?
Those are planned for future iterations of the templates (these represent the simplest, complete code I could get out for a v1 release) - currently the code generated supports the bare minimum necessary to work with WORM - these are not required so I started without them. These would be stubbed similarly to the way Paul did his, I imagine, though I don't know for certain at this very moment. Perhaps I will have those done in the next week; I do plan on updating these on a regular basis (weekly, if at all possible).
> 2) Your templates do not have any indexers. I am not missing them but I was wondering > what your reason is not to include them.
Not 100% on what you mean by this (please clarify) but I do provide the most basic indexer property on the strongly-typed collections.
> 3) Why did you decide to have a generic manager for persistence? Personally, I prefer > one that is more specialist and also returns the actual class ("xxxBase.cs") rather then DataSet.
The 'generic' manager that is included in the code that is generated is merely as a thin wrapper for providing pass-through access to some of the functionality WORM provides that is not specific to any given entity. The power, in my opinion, of WORM lies in the options it gives you. Being able to simply get an ADO.NET object such as a DataSet back is good for doing reporting or some quick prototyping. That is what this class is meant to provide, and it too is not 100% complete. I hope you also noticed all of the non-generic managers that were generated . . .
> 4) Performance wise wouldn't implementing IObjectHelper and returning xxxBase class be > faster then returning DataSets due to marshaling, etc.?
Not 100% sure what you asking/referring to here . . . having your business objects implement IObjectHelper will also be an option for future iterations of these templates, but personally I like and typically use the reflection-based route of things so that my objects, inasmuch as possible, have little code within them that is specific (read: coupled) to WORM. Granted, the very fact that I have variables of the type Wilson.ORMapper.ObjectHolder (i.e. a WORM-specific type) means these are tightly-coupled to this specific O/R mapping solution, but it still feels yucky (aren't all decisions based on such emotions?) to implement IObjectHelper. Yuck.
Since you bring up DataSets, either I don't understand how they are relevant to the rest of your question, or perhaps you misunderstand (from the generic manager class inclusion) that I am using DataSets somewhere I am not. Perhaps you should look again at everything that is generated for this framework, simply because from your question it seems as if you think I am using them for entities. > Please note that is not criticism but I just try to understand the thoughts behind your approach? No worries - I actually enjoy well-meaning and solution-providing criticism. It is the criticism that has ill-intentions or provides no alternative solutions that I find useless. The only way to make things better is to see their failings, and if that means someone tells me that something I have done makes no sense or seems to be incorrect, so be it. I appreciate your taking time to ask questions. I hope I have provided meaningful answers and look forward to your response.
I looked at the Wilson OR Mapper for the first time yesterday and thanks to your templates was up and running in a few hours.
I am working in the .net 2.0 space though and have many "suggesstions" (request?) if you're up for them?
I know you mentioned you dont support generics just yet - but boy do they simplify the code and the amount of code generated.
Let me know if you're keen.