Deep & wide parent-child-grandchild implementation ?'s

Deep & wide parent-child-grandchild implementation ?'s

Old forum URL: forums.lhotka.net/forums/t/2009.aspx


SonOfPirate posted on Tuesday, December 19, 2006

Okay, I'm looking for some quick advice from all the guru's out there.  I find myself in the typical position of trying to rationalize behaviorally versus my instinctive desire to think data-centrically and am doing nothing but pulling my hair out re-reading the book and pouring through forum posts.

I have an application that is both wide and deep in its hierarchy.  This may be because I have been working on this app for 15+ hours a day for the last two weeks getting it spec'd, designed and documented and am at the point where I'd like to be getting more coding done than wrestling with design issues still.  But I am trying to sort this out and make sure that my implementation is consistent with a behavioral-based approach and it seems like things just keep getting more and more complicated the more I try to simplify!

Anyway, the application is web-based, multi-user and divisional in nature.  What I mean by the last one is that the application must support multiple divisions within the company with no sharing of data except for those higher up.  That isn't where the problem stands - at least not at this point - as the data model has been designed with a pseudo-domain/organizational unit structure.  In other words, everything is tied to an OU including the users so that they can access only the data within their OU.  Some users have "domain" access which allows them to run aggregate queries, etc. for reporting and so on.  This is all accomplished by passing the current user's ID value along to every query to the database so the result set is filtered accordingly.

My problem starts at the level just below the OU - with a Customer.  Going down the hierarchy, a Customer can have multiple Projects created for him/her.  Each Project can have multiple Quotes generated and each Quote can have multiple Orders placed against it with multiple Packing Slips for each of those then we have Invoices, Payments and so on down the line.

That's the depth.

Each object has a standard set of child collections that go along with it.  For instance, the Customer has Contacts, Attachments, Notes and History (Events) and Resources - in addition to its Projects.  So, my dilemma is trying to figure out the right implementation to accomodate this.

In reading the book and reviewing the sample app, etc. I see that the queries for all root objects return a result set for their child objects as well.  This concerns me because I would have to handle seven (7) result sets in my DataPortal_Fetch method for the Customer class.  Although, my initial concern about how to carry this to the next level was tempered when I looked at things from the UI perspective (something I was trained NOT to do!).

The UI will consist of a multi-tab page with the Customer's direct properties on one page and lists/grids displaying the child collections on the other seven (7) tabs (one per).  So, that tells me that all I really need for my Customer object is to have read-only lists for the child collections.  To drill into a Project, the Project's ID value can be pulled from the list/grid when the user double-clicks the row and passed to the new page that is opened displaying the Project's properties and child objects/collections in the same manner.

So, my old-school, data-centric way of wanting to be able to say ThisCustomer.Projects[0].Quotes[0].Orders[0].Status is just that, obsolete - right?  Am I correct that this is not how it is supposed to work?  (Can someone give me an example of how I would perform this type of drill-down with Csla?)

Limiting the discussion to just the Customer at this point, here is what I think I need:

I will have similar classes for the Project, etc.  Would it make sense to have interfaces common between classes like CustomerResource and ProjectResource (IResource)?  Or a common base class?  This gets confusing because we will want a way to list all of the Customers, Projects, etc. that a Resource has been assigned to - but, thankfully, this parallels what Rocky did in the book.

To add a new Contact to a Customer, for instance, I would need to display a separate form that is bound to a Contact object via a CslaDataSource.  After the InsertObject handler saves the new Contact, we would use the ID value to add the Contact to the Customer and refresh that page.

I guess this is where I am the most fuzzy because the book & example only demonstrate how to "assign" an existing child object to the parent.  In my case this will work fine for the CustomerResources but the other child collections allow for new items to be created that are in a many-to-many relationship with the customer (only Notes and Events have true parent-child relationships with Customer).  My guess is that I will need to implement this as a two-step process where the new record is saved to the database then the relationship established, as I described above.

Okay, that's probably enough info to get ya going, eh?  I'm anxious for your feedback to evaluate where I am in this process and if I'm anywhere near ready to start coding!

Thanks in advance.

 

SonOfPirate replied on Friday, December 22, 2006

No one has anything to contribute/answers to my queries?

ajj3085 replied on Friday, December 22, 2006

I think all those read only lists should be root objects in their own rights.  They are read only, so don't need to exist within the Customer object because there's no need to track changes for them at all.  You could have navigational methods off the Customer class to get the appropriate readonly list.

An IResource would only make sense if there are some common behaviors between a CustomerResource and ProjectResource, and it doesn't matter which one you have when invoking those behaviors.

The CustomerContact editable object should be a root in its own.  You can have a New factory method that requires a Customer object though, to force the client code to tie the contact to a customer.

Does this help at all with some key points?  You posted quite a bit of info, and I'm not sure I digested it all correctly..

Andy

SonOfPirate replied on Friday, December 22, 2006

Yea, I'm with you on all of it except the lists being root objects as well.  I don't see how that makes sense without them existing within the context of a Customer.  Or are you saying that the static Get...() method should require the id of the Customer?

I was looking at some of the ADO.NET classes - just for reference and example of approach.  From that I took the following:

1. I've added a CreateProject() method to my Customer class.  This will ensure that the returned object has the associated Customer pre-set.  I'd considered automatically adding the object to the Customer's Projects collection but that is not consistent with what I've seen and would lead to more hassle dealing with "cancelled" objects.  But, I did add validation code to ensure that any Project added to a Customer's Projects collection has either no Customer defined or is matching the Customer it is being added to.

2. I allow a Project to be created on its own, via NewProject().  But, I don't allow it to be saved without a valid Customer set.  Then, on saving the object, I verify that it exists in the Customer's Projects collection, adding it if not.

Does that make sense?

Back to the "dependant" lists, I used the ProjectTracker app as a basis for my thinking.  In that app, the ProjectResources collection is not publicly creatable.  So, I guess this is where my confusion lies with regard to making them root objects.  Can you elaborate?

Thx.

ajj3085 replied on Friday, December 22, 2006

Pirate,

Yes, the Get method for the lists would only accept a customer object (or customer id, depending on how you want to implement it).

Your methods sounds just fine too, I do this thing quite a bit.

You can have editable collections as root objects as well; you just define new or get as always requiring a customer id so that the customer tie in is always there.

Andy

SonOfPirate replied on Friday, December 22, 2006

Alright, let me give it some more thought and see what I can come up with.  This is the one area that I have found a bit difficult to wrap my head around.  Mostly because I am so used to thinking hierarchically (e.g. Customer.Projects[0].Quotes[0].Orders[0].Status) - which is very data-centric (and coupled!!!).  So breaking that train of thought, especially when it comes to this type of dependancy, has taken a bit more effort to grasp.  In my previous implementations, I've skirted the issue by following the more "dot-oriented" structure.  But, I am trying very hard this time to fully adapt/adopt the behavioral approach as well as a better OO design.

The problem my brain is having is the question "where does the customer id come from if not being passed from parent to child?"  Again, this is very data-centric, I know.

I'm also suffering from the SOA mentallity that you design with the UI out of mind and am somewhat glad that this has gone in the direction that it has because that was always a sore point with me.  After all, how can you design behavior if you aren't looking at how it is to be used?

Do you find that the approach you've described facilitates or hinders representing your objects in a tree-view structure?  Obviously the dot-oriented approach makes it easy to crawl an object model and populate a tree.  But, I'm thinking that the approach you are describing might actually be better because it supports lazy-loading and even AJAX-supported implementations rather than traversing the entire object model and pre-populating the tree - or re-instantiating the object so you can climb into the next branch.

It seems that this approach will allow a simple callback using the id value of the parent to get an instance of the child collection (as if it were a root object) to populate the branch.

This is something on the application's to-do list and thinking this way is helping me wrap my head around the concepts.  Am I on the right page?

 

SonOfPirate replied on Friday, December 22, 2006

One more thing...  In the ProjectTracker example, Rocky binds the ProjectResources list using the Project.Resources property.  Are you suggesting that using ProjectResources.GetProjectResources(ProjectID) is a better way to accomplish this?

 

JoeFallon1 replied on Friday, December 22, 2006

Hi,

Some comments.

1. The Customer BO should NOT carry around all of those collections all of the time. Sometimes a Root BO will always have a contained child collection - think Invoice and InvoiceLines. But this one may not always need these child objects.

2. For each screen (or UseCase) you will need the Customer root BO and 1 or more of the ROCs. This means that each ROC must be able to get the correct data for the current customer. So you need code like this:

Public Shared Function GetCustomerContactList(customerID As Integer) As CustomerContactList

3. For each screen/UseCase create a higher level BO which contains Customer and the ROCs needed *just for that screen*. I called this higher level BO a Unit Of Work (UOW). Others called it Use Case Controller. The second name may be better but I already have many UOWs so...

This BO is fairly simple. It is also a Root BO and has its own BrokenRules. The key point is that when you navigate to the screen and get an instance of this BO you pass it the CustomerID to fetch the CustomerBo and the ROCs. All data on the screen passes through the UOW to the underlying BOs. You can Save the UOW (which saves Customer and any other data.) You can also check if it IsValid and list broken rules across multiple BOs.

Joe

pelinville replied on Saturday, December 23, 2006

 

There is a lot of stuff in that post.

 

I believe, first of all, that hierarchies should not be avoided.  Even with "good OO", natural hierarchies exist. Some problem domains have very deep ones that simply cannot be avoided. If you have one of those, well, you do.  As long as you are not imposing a hierarchy upon it because of the nature of the persistence mechanism, no harm done.

 

That being said.  Looking at your customer object I don't see that much trouble if you set up each of the contained classes as roots.  When I say roots I mean objects that are responsible for building themselves or getting their own data given a set of criteria. By doing this you can avoid the long dot chain you mention.  For example the OrdersCollection could take a CustomerID (or even a customer) to present the orders for that customer.  The OrdersCollection could have another factory method that would take a customer and a Quote and return the order for that criteria pair.  Each order could have the Quote(s) that it is associated with as well.  Whether you expose Orders through the dot chain or not is up to you.

 

So for example you could Have Customer.OrderStatus(q as Quote) as OrderStatus.  Of course this type of thing could also be done with the Read only lists and what not.  The benefit of using roots, especially in a web app, is that you can limit the data retrieved for a method call.

 

As for the necessary ID creation the best solution to this is have the objects themselves produce the ID.  That way you don't have to go back to the DB to get it.  GUIDs are the easiest (especially in web apps) way but there are other options.  The GUID doesn't have to be the primary key as long as the field in the DB is unique.  It just makes things soooo much easier when the objects control the data.

 

Another option is to use the DataPortal_Create that will generate the ID of the new object while also saving the ID of the parent at the same time.  Does require some over riding of the base classes but does work.

 

As far as wether you should have interfaces or base classes.  I think many times that is not even necessary.  You mentioned CustomerResource and ProjectResource.  Is it possible that there isn't a real distinction between the two, that they are simply a Resource?  I have found that often that is the case with my classes.  For example I had a StudentAttendance and a InstructorAttendance. Really the behavior was no different between the two.  Both simply recorded weather the individual was at the appointed place and time. Another object ended up deciding the significance of that fact. I guess I cheated a bit when I let the some other object determine if the AttendanceType of the Attendance was valid.  But maybe not.  Who cares, I simplified my object model and completed the goal.  But I will say that I think that having an interface is often useful but only if it is in another dll.  The only drawback is if you have to change the interface.  It can result in a whole bunch of work if that interface is used in a bunch of places. 

 

I like what Joe describes but for me it is way to much work. I am very lazy.  I do have what I call entryPoints.  They retrieve date (or fail) based upon the role of the logged in user and what part of the application they are logged into.  Examples are InstructorEntryPoint, GaurdianEntryPoint, StudentEntryPoint etc.  So a studentEntryPoint will only retrieve the data relevant to that student and nothing more. This is easy because everything is a root so I don't have to create special classes to support it.  Outside of that it behaves much as Joe describes. It is a root BO and it tracks all it's contained object and has it's own broken rules and so on.

 

Maybe this helps? 

SonOfPirate replied on Saturday, December 23, 2006

Joe: So I'm guessing you are in favor of a more lazy-load approach to the child collections?  I'm thinking we'd have properties like:

public class Customer
{
    :
    :
    public CustomerNoteList Notes
    {
        if (_notes == null)
            _notes = CustomerNoteList.GetCustomerNoteList(this.UniqueID);
   
        return _notes;
    }
    :
    :
}

where GetCustomerNoteList triggers the DataPortal_Fetch method to retrieve the collection's contents from the database???

I guess I can wrap my head around that idea.  Seems like the best of both worlds.  Wasn't there a reason Rocky shyed away from lazy-loading?  I'm assuming it is because of the number of round-trips to the DB.

In my case, it may make more sense to NOT lazy load, though, when looking at how the UI works.  I am using a tab control with the first containing a FormView bound to the Customer's (root object's) properties.  The other tabs correspond to each of the child collections.  So, for instance, there is a Notes tab that has a GridView bound to the Customer's Notes property - just as Rocky does it in the ProjectEdit.aspx page.  Because of this, I know that these child collections will always be used so it makes more sense to go with Rocky's approach and have a single query return the multiple result sets and have my Customer object populate all of the child collections.  BUT, I do see the merits in the approach you described and will keep it mind elsewhere in the app.

pelinville: lots of good stuff.  Part of what I like about the approach Joe described is that it would support a full hierarchical view of the object model.  Because Customer would have a Projects property that uses ProjectList.GetProjectList(customerID) to instantiate the collection and a Project would have a Quotes property that uses QuoteList.GetQuoteList(projectID), I could walk the heirarchy like: Customer.Projects[0].Quotes[0].Status.  Again, best of both worlds.  Except, as we all know, nothing works all the time and I think the way my UI is setup drives the need to reduce the number of round-trips by eliminating the lazy load.  However, I can still limit this to only one level deep, I guess.

I am thinking I would end up with two objects: ProjectList and CustomerProjectList.  The former being a root object responsible for its own data routines and the latter being a child object loaded from the parent Customer record.  Same for QuoteList and ProjectQuoteList and so on.  Then I can start at any level or walk the hierarchy.  Every other level in the heirarchy would be a root object so my CustomerProject object would call QuoteList.GetQuoteList(projectID) if that branch was traversed.

Make sense?  Or, are you suggesting a single object, such as ProjectList that can exist as either a root object or child object (i.e. switchable) based on the criteria passed through overloaded factory methods??

As for the interface/base class question, I have several use cases in mind.  First, the CustomerResource/ProjectResource example is exactly like what Rocky has in the book where I do have a root object, Resource, that represents just that.  CustomerResource and ProjectResource represent the assignment of that resource to the Customer or Project.  So, my intent is to follow the same model presented by Rocky with a central Assignment class that handles the common behaviors.  But, I am also looking to have an IResource interface shared by the object that establishes the contract that is espected in the implementing classes.  Why?  Because I can then have a single Web Server Control to render the list/object by programming it against the IResource/IResourceList interfaces.  Then, it will work whether I am displaying CustomerResources or ProjectResources.

Second is the case where I don't have a "free-standing" object but a truly dependant child object, such as CustomerNote.  Every "external" object (by that I mean Customer, Project, Quote, etc) has notes attached.  A "note" is defined the same no matter the object: a time/date stamp, the user that created it and the content of the note.  While a note cannot exist without a parent, I can have a Note object that represents the note itself and have CustomerNote, ProjectNote, etc. represent the relationship.  Again, by having a common base class for these objects, I can code my UI control against the base class and gain reuse of the control in my UI.  And, in this case, I am leaning towards an abstract base class as being more appropriate than an interface to gain code reuse of the common properties that are shared by all xxxNote objects.

The other use would be in what I described before where I have a Project class and a CustomerProject class with the former being a root object and the latter a child object.  If they share interface IProject, then other code can make use of the objects without distinction - and using interfaces helps to ensure that both classes are compliant with the "contract".  My UI controls and other classes can code against the IProject interface and not be concerned with object is being used.

Make sense?

 

JoeFallon1 replied on Monday, December 25, 2006

Pirate,

The lazy loading approach is fine  For the given use case since you need all the ROCs as part of the Customer because you plan on using them all on the same screen. You can even fetch them all directly (once you have the correct CustID value).

The one thing I would do differently is make a separate UOW BO and place the Customer in it and then place each ROC in it. So you are working with the UOW for this use case and have access to each ROC as:  MyUOW.MyROC.

Using this pattern the Customer BO is not "polluted" with all the ROCs. So you can fetch it on other screens and not worry about bringing back all those ROCs which are not needed on other screens.

Joe

 

 

 

 

 

Copyright (c) Marimer LLC