OO Design Help Needed

OO Design Help Needed

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


dkehring posted on Thursday, May 03, 2007

I've been using CSLA since forever and have used it very successfully for years. Every now and again I come across a design problem that I struggle to resolve, usually ending up with a less-than-adequate design and one that I feel is somehow a hack. I'm having that problem now with a simple design. So, here's a quick explanation of what I'm trying to do:

I'm building a simple time tracking application. The model includes the following classes: Client, Project, Task, Work, TimeEvent. The current model has Client as the root object, Project a child of Client, Task a child of Project, Work a child of Task and TimeEvent a child of Work.

Client --> Project --> Task --> Work --> TimeEvent

The most common usage scenario in the application is via Work. A list of "active" Work objects is displayed and the user can "start", "stop" or "pause" a work item. These actions create TimeEvent objects within the TimeEvents collection in Work. I want to persist the TimeEvent object each time one of these actions is performed. Since TimeEvent is a child of Work, I have to save Work, but it's a child of Task and so on leaving me to have to save Client each time. So I figure, let's make Work a Switchable object (something that always gives me pause). So, I can load a Client object and get all of its descendants all the way down to TimeEvent. I can also load a Work object given its WorkId. But then I come to this. I want to create a class called WorkCollection that would load all of the Work objects from the database that are currently "active" (started). I want this to be a collection of root Work objects (to allow me to save them easily when I "start", "stop" or "pause" work) so I need a method like GetWork(dr) passing in a SafeDataReader, but then I have one already to support the child version of Work (when loaded from Task). Okay, I can then also have a GetWorkChild(dr). Now things are getting messy.

Here's my dilemma. I want to preserve the relationship between these objects but I also want to be able to act on a Work object independently. Of course this does not allow me to add a new Work object because I need to know the TaskId, but I'm not concerned about adding (I do that through a wizard that requires the user to select the Client, Project and Task). I also want to be able to load a collection of Work objects as root objects so that I can call their Save method. Do I make Work a root object only and then if I want to create a new Work object, I have to set the TaskId (rather than it being passed in via the normal internal Insert and Update methods)? Work is such a central class in the application that it stands out as the key class. By now you can see that I'm probably confusing myself so any advice is welcome.

As an aside, I find that the ProjectTracker sample application does not provide us with sufficient examples of the key elements of the CSLA design. For example, there is now demonstration of grandchild or greatgrandchild objects. No demo of a Switchable object (which I believe should be rare anyway). For example, what if ProjectTracker had a Task class? And what if the application allowed for subtasks such that a Task can have a collection of Task objects and so on? Now that would be a more interesting sample application.

Any and all help much appreciated. This probably sounds like (and is) a very simple problem but I'm looking for some sound advice that could solve this dilemma for me now and in the future.

Regards,

Dave

Patrick.Roeper replied on Thursday, May 03, 2007

Dave,

I have a similar setup in my system: Project -> Service -> Work Order -> Timecard. In order to interact with the Work Order object independently it was made into a switchable object. Do you have a copy of Rocky's business object book? I read through it and its not too bad to implement. Codesmith with the CSLA templates makes it extremely easy as well.

I dont know how specific of an answer you need; sounds like you just need some guidance on how to model this situation. Switchable is definately the path to go.

SonOfPirate replied on Thursday, May 03, 2007

Dave,

The most common response you will get is that your answers are in your use cases and you should detail them a bit more than you have here.  What that means is, as Rocky has said, if you are looking at creating a switchable object, you are probably combining use cases.

What you need to consider is HOW you are going to perform the various tasks required by the application.  For instance, you say that the most common usage scenario is at the Work level and that a list of active Work objects is displayed.  How?  Does the user simply click a button and a list of active Work object shows up?  Or is this a result of a drill-down process? Is there one list of Work items or does it vary by user or some other criteria?

In a similar application I developed a while ago, access to our work items was either via that Client and Project or through an assignment to the current user (e.g. My Work Items).

A typical interface allowed the user to view a collection of clients via a menu item, link, etc.  The list was bound to our ClientList root business collection containing read-only ClientInfo objects.  When an item in the list is opened (via double-click, for instance), we open the Client Editor form which is bound to our editable Client object.  The Client object has a child collection of Projects - our ClientProjectCollection child business collection of read-only ClientProject objects.

When an item in the Client's Projects list is opened, we display the Project Editor form which is similarly bound to our Project object.  Continuing down the object heirarchy, the Project object contains a Tasks property - a ProjectTaskCollecton child business collection or read-only ProjectTask objects.

This pattern is repeated down the heirarchy.

In addition, we expose a root ProjectCollection which contains ProjectInfo objects.  This allows users to view a list of projects in lieu of drilling into the project from the client.  The difference is that the ProjectCollection contains projects for all clients whereas the ClientProjectCollection contains projects for a specific client.

So, to extend this to your object model, we would have a TaskWorkCollection with TaskWork objects that represent the Work items for a specific Task.  The WorkCollection of Work objects contains all of the work items for all tasks.

Hope that helps.

 

dkehring replied on Friday, May 04, 2007

SonOfPirate,

Thank you for your detailed response. I agree that switchable objects typically point to a problem with the object model. I have always tried to avoid using them.

Let me give you a bit more detail on this application. First, the application's primary user is a programmer who needs to track his/her time spent on various Work assigned to him/her. Work can be added manually or imported from other sources. When you add Work, you have to select the client, project and task (or add them if they don't exist). The primary use case however is that a programmer will run this program and be presented with a list of "active" Work items assigned to them. The user can select an item and start, stop or pause it. These actions essentially create TimeEvent objects within Work, keeping track of the time spent on a Work item. So, I need to have a collection-type object that, when loaded, contains a collection of Work items that are active. These Work items need to be root objects because I will need to persist the Work object each time I add or modify a TimeEvent object within the Work object. Also, I need to ensure that only one Work item is active at any one time. If the user starts a Work item, the currently "active" work item (if it exists) needs to be paused automatically. Therefore, I need to be able to iterate over an entire collection of Work objects.

Finally, the model also needs to support the alternative view and that is that Work is a child of Task is a child of Project is a child of Client. For reporting purposes I need to generate statistics that show time spent by the programmer broken down by Client, Project and Task.

Referring to your post, I understand what you're doing with ProjectCollection / ProjectInfo (I do this as well) but ProjectInfo is a read-only object, not a persistable business object. If we were to extend this concept to create a WorkCollection, the collection needs to contain Work business objects, not WorkInfo read-only objects. This is where my dilemma lies. Work needs to act like a child object in relation to Task but it also needs to act like a root object in relation to WorkCollection. Further ideas?

Thanks,

Dave

SonOfPirate replied on Friday, May 04, 2007

Dave,

What I see are a number of use cases:

  1. The need to edit an individual Work object
  2. The need to display a list of Work items assigned to the current user.
  3. The need to display a list of Work items that belong to a specific Task.
  4. The need to generate a report of all Work items by Client, Project and Task.

To support #1, I would have an editable root object, WorkItem.  This would be bound to your data entry form.

To support #2, I would have a read-only root collection, MyWorkItemCollection.  This would contain read-only WorkItemInfo objects.  I don't see the merits in having a specific read-only object to handle "MyWorkItemInfo" unless there is information needed for this list that is not present in the other lists.  The list would be displayed in some kind of list or grid control and the unique identifier used to reference the target object when an item is clicked, double-clicked or whatever.

For #3, I would have a read-only child collection, TaskWorkItemCollection.  This would also contain the read-only WorkItemInfo objects - unless, again, there is a use case that makes this different from the other lists' items.  Here again, double-clicking would open the same WorkItem object or editing per #1.

For #4, I would consider having a read-only root WorkReport object that encapsulates the report data.  It's internal collection would contain read-only WorkReportItem objects that "flatten" the object heirarchy so that each property matches a column in your report.  Then, using whatever group-by function your reporting tool allows, you can group the results by Client, Project and Task.

Hopefully you see how defining the use cases leads itself to the object model.  Yes, you will have multiple objects that have overlapping DATA but they each have a different responsibility in the application:

Keep in mind that the difference between WorkItemInfo and WorkReportItem is that the latter flattens the heirarchy - i.e. the query that retrieves the records resolves all foeign key references whereas the former leaves them intact.

Also, if there are differences needed for MyWorkItem(Info) and TaskWorkItem(Info), then you can add those into the mix.

HTH

 

dkehring replied on Friday, May 04, 2007

HTH,

Thanks. A couple of observations:

1. If WorkItem will be a root object, I'll have to have a setter on TaskId to allow the relationship with Task to be maintained (or some sort of Assign method) along with a validation rule to require the TaskId.

2. MyWorkItemCollection and TaskWorkItemCollection are very similar. In fact, I would likely parameterize the Get method so that I could load the WorkItemInfo collection by passing in a TaskId or a UserId and another parameter to indicate if I want active, inactive or all WorkItem objects.

3. Reporting I'm not too concerned with since those objects are not traditional business objects and are constructed to accommodate the data I need.

4. Having Task load a read-only collection is something I did not consider. My mental breakdown was with the relationship between Task and WorkItem. It means I have to load the actual WorkItem business object on demand. A potential performance issue but I think the way this object will be used it won't be a problem.

I'll have to see how this works out in practice. Thanks again and I'll post back when I've made some progress.

Dave

Copyright (c) Marimer LLC