Object Design Question

Object Design Question

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


TerryHolland posted on Friday, January 12, 2007

I am developing an asp.net application and am assessing whether to use clsa.net.  The application is a quoting system.  Each Quote object will have a collection of QuoteItem objects.  Each QuoteItem object will contain a collection of StockItem objects and a collection of LabourItem objects.

Each Quote will typically contain 100 QuoteItems

Each QuoteItem object will typically contain 100 StockItem objects and 20 LabourItem objects

In addition to this there are other related colelction objects that I have not described.

 

If Im understanding CSLA correctly then the objects Ive described will map to CSLA templates as follows:

Quote: Editable Root (top level object)

QuoteItems: Editable Child Collection

QuoteItem: EditableChild (A QuoteItem only exists as part of a quotes QuoteItems collection - though the user may wish to search the entire db for a QuoteItem in order to copy the QuoteItem to a new Quote)

StockItems: Editable Child Collection  (Each QuoteItem object will contain a collection of StockItem objects)

StockItem: Editable Child

 

My concerns are that when loading a quote, Im loading a lot of related data which is not immediately necessary, that may cause a memory problem with the number of concurrent users.

When a user loads up a Quote, the UI should display a list of QuoteItems in a grid that the user can add to, remove from and edit.  A full QuoteObject conatins a large number of properties that do not need to be displayed in the list but should be visible when the user goes into the Item page.  What Im not sure about is if I load a Quote object using the stored procedure sprQuote_Select_CSLA (see below)

This procedure returns all Quote columns and all QuoteItem columns.  Should I be loading all QuoteItem columns (approx 30) at this point seeing as I only need to display 3 of the columns in the grid on the Quote page?  If I dont return all columns but return only the 3 that I wish to display, that will mean that my QuoteItems (editable child collection) object will only load its QuoteItem (editable child) objects with 3 properties.

It seems that I should have a QuoteItem_List object which contains a collection of QuoteItem_ListItem objects which contain only the properties that I would display in a list.  If I did go down this path, Im not sure how I should then convert this QuoteItem_ListItem object to a full QuoteItem object within the CSLA framework and still have the QuoteItem object a Editable Child object.

All advice welcome

 

CREATE PROCEDURE [dbo].[sprQuote_Select_CSLA]
 @quot_int_ID int
AS

SET NOCOUNT ON
SET TRANSACTION ISOLATION LEVEL READ COMMITTED

SELECT
 [quot_int_ID],
 [quot_user_int_ID],
 [quot_jobb_int_ID],
 [quot_tin_Revision],
 [quot_tin_Copy],
 [quot_int_ID_Parent],
 [quot_boo_Hire],
 [quot_txt_DuplicateReason],
 [quot_cont_int_ID]
FROM
 [dbo].


WHERE
 [quot_int_ID] = @quot_int_ID

--Load related QuoteItem records
Select * from QuoteItem where item_quot_int_ID = @quot_int_ID

 

SonOfPirate replied on Friday, January 12, 2007

Terry,

You have an advantage using a web app (IMO) because you can break the object heirarchy into more functional chunks that more emulate how the UI behaves (ergo, how you are using the objects/data).  Here is another thread that delves into this a bit: http://forums.lhotka.net/forums/thread/10599.aspx

If you think about it, what you will end up with is a page that displays the details of a Quote and a list of those LineItems. At this point you really don't need to know anything deeper than that.  So, to accomplish this, you will need your Quote editable root object and a child collection of read-only LineItemInfo objects.

To drill into the details for a particular LineItem, you will use the primary key field from the above list to identify the item to instantiate on a new page that displays the LineItem and a list of StockItems and a list of LabourItems.  This follows the same pattern as above with the LineItem being an editable root object with a child collection of read-only StockItemInfo objects and a second child collection of read-only LabourItemInfo objects.

The same approach is used to drill into the StockItems and LabourItems.

So, in the end, you'd end up with the following objects:

Quote editable root
QuoteListItemCollection editable collection (?)
QuoteLineItem read-only child
LineItem editable root
LineItemStockItemCollection editable collection (?)
LineItemStockItem read-only child
LineItemLabourItemCollection editable collection (?)
LineItemLabourItem read-only child
StockItem editable root
LabourItem editable root

If you have had a chance to read Rocky's book and review the sample project, you'll find where he goes over how to add new child items, etc. to finish off the needed behavior. (Thus the question marks next to the child collections. It's up to you how to implement these to match the behavior you require from the app.)

This also means that your queries can be limited to retrieving the specific record and one level down the heirarchy rather than the full graph for the select object.

So, to answer your initial question, yes, Csla is perfectly suited for your application and is, in fact, aimed at doing just that.

HTH

TerryHolland replied on Friday, January 12, 2007

Thanks for response.

 

So let me see if I understand what you are suggesting.

 

  • Quote is a full EditableRoot object
  • A Quote object will contain a collection of QuoteLineItem objects that will be exposed through the QuoteListItemCollection property. 
  • A QuoteLineItem is a ‘small’ readonly object containing only the properties that will be displayed in the QuoteItems list on the Quote page.
  • My stored procedure sprQuote_Select_CSLA should look something like procedure below
  • My Quote object will load this collection via data_portal as in DataPortal_Fetch below
  • The LineItem class is a full QuoteItem editable root object containing all 30 properties
  • The user will click a link on the QuoteItems (QuoteListItemCollection ) grid on the Quote page and the ID from this row will be used to load a QuoteItem (LineItem) object and display this on the QuoteItem page
  • The QuoteItem page will contain a grid bound to the LineItemStockItemCollection property of a QuoteItem (LineItem) object
  • The user will click a link on the StockItems (LineItemStockItemCollection) grid on the QuoteItem (LineItem) page and the ID from this row will be used to load a StockItem object and display this on the QuoteItemStock page

 

And so on…

 

If I have understood what you are suggesting correctly then there are a few problems

 

  1. Having QuoteListItemCollection as a editable child collection and QuoteLineItem as a readonly child causes a problem because QuoteListItemCollection inherits from Csla.BusinessListBase as follows

 

Inherits Csla.BusinessListBase(Of QuoteListItemCollection, QuoteLineItem)

 

Because

 

‘QuoteLineItem’ does not inherit from or implement the constraint type 'Csla.Core.IEditableBusinessObject'.

 

  1. How do I Add a QuoteLineItem child object to the QuoteListItemCollection? I ask this question because a QuoteLineItem  does not equate to a valid QuoteItem  object (all 30 properties need to be populated, not just the ones displaying in the list)
  2. If you are suggesting I add a new LineItem object (this is a root object), then, in the scenario where I am creating a new quote and Im adding Items to the quote, the Item could be saved to the db before the quote – which I might subsequently decide to cancel
  3. By splitting these objects into root objects, won’t we lose the ability to wrap the saving of a Quote and its Items and stock into a single transaction

 

I look forward to your response

 

Terry

 

 

=======================

Code Snippets

=======================

Create PROCEDURE [dbo].[sprQuote_Select_CSLA]

      @quot_int_ID int

AS

 

SET NOCOUNT ON

SET TRANSACTION ISOLATION LEVEL READ COMMITTED

 

SELECT

      [quot_int_ID],

      [quot_user_int_ID],

      [quot_jobb_int_ID],

      [quot_tin_Revision],

      [quot_tin_Copy],

      [quot_int_ID_Parent],

      [quot_boo_Hire],

      [quot_txt_DuplicateReason],

      [quot_cont_int_ID]

FROM

      [dbo].

WHERE

      [quot_int_ID] = @quot_int_ID

 

--Load related QuoteLineItem records

Select

      [item_int_ID],

      [item_int_Column1],

      [item_int_Column2],

      [item_int_Column3]     

from Item

where item_quot_int_id = @quot_int_ID

 

 

 

Private Overloads Sub DataPortal_Fetch(ByVal objCriteria As Criteria)

Dim tr As SqlTransaction

Using cn As SqlConnection = New SqlConnection(Database.Contest03UKConnection)

cn.Open()

tr = cn.BeginTransaction()

Try

Using cm As SqlCommand = tr.Connection.CreateCommand()

cm.Transaction = tr

cm.CommandType = CommandType.StoredProcedure

cm.CommandText = "sprQuote_Select_CSLA"

cm.Parameters.AddWithValue("@quot_int_ID", objCriteria.ID)

 

Using dr As SafeDataReader = New SafeDataReader(cm.ExecuteReader())

dr.Read()

‘load object from dr

 

ValidationRules.CheckRules()

 

'load child object(s)

dr.NextResult()

m_objItem_List = clsQuoteListItemCollection.QuoteListItemCollection(dr)

 

End Using

End Using

tr.Commit()

Catch

tr.Rollback()

Throw

End Try

End Using

End Sub

 

RockfordLhotka replied on Friday, January 12, 2007

TerryHolland:

My concerns are that when loading a quote, Im loading a lot of related data which is not immediately necessary, that may cause a memory problem with the number of concurrent users.

Your objects should be designed around your use cases, not around your data. What you've described thus far are a set of objects that map 1:1 to your tables (pretty much anyway), but it sounds like that is a mismatch when compared to your application's usage.

I say this because of your concern. You seem to be indicating that there are tasks the user is performing that don't require all the data in that huge object graph. Those tasks are, effectively, use cases. It is most certainly true that each use case should have its own object model: designed specifically to accomodate that use case.

So it is highly unlikely, especially in a web setting (which it sounds like where you are) that you'd have such a comprehensive object graph. That might make sense in a WinForms setting, where you really could have a form that displays and edits all that data at once. But in the web that's just not likely.

Odds are that your object model is not 1:1 with the database. Rather, it may be 3:1 - 3 objects per table, with each object filling a different role based on a different use case. The focus isn't on the data though, but rather on the behavior required by the use case.

You have a use case where the user brings up a quote, with a list of read-only line items. That makes sense, and you should have an object model FOR THIS USE CASE that reflects this need. Probably

Quote<-ReadOnlyRoot
ItemList<-ReadOnlyChildList
Item<-ReadOnlyChild

Then it sounds like you have a case where the user wants to edit one of the line items. That's a use case, with its own object(s), probably:

QuoteItem<-EditableRoot

Again, in a WinForms setting the object model could be factored at a higher level. And for many web scenarios you don't need this level of breakdown either. But if you are building a high volume web site where you have hundreds of concurrent users hitting your web site, you probably do need to do a design at this level of granularity (where each page (more or less) is considered a use case.

I don't know if you really do have that kind of scalability requirement or not - most people don't.

When thinking about server memory consumption it is important to be clear about what's going on. Objects are only as big (in memory) as the size of their instance fields. That's it.

However, you are talking about ~12000 objects in your graph. So for fun, let's say they average 150 bytes of data per object - that's nearly 2 meg per graph (per user). If you only have 10 or 20 users, that should be a non-issue. 200 users becomes an issue Smile [:)]

On the other hand, rationally thinking about it, can a user actually interact with 12000 objects in any meaningful sense? That seems quite unlikely - even in a WinForms setting.

So far the idea of this mega-object-graph is not passing any sort of sniff test (for me anyway) - web or Windows aside.

So perhaps you consider that at a broader level you have these use cases:

  1. List quotes
  2. Display a quote
  3. Edit a quote
  4. Edit a line item
    1. Edit stock items
    2. Edit labor items

Odds are this set of use cases is consistent across web or Windows. And each one of these use cases will have its own set of objects. DON'T TRY TO REUSE THEM in your initial designs. Just design each use case independantly (use Chapter 6 as a guide). When you are done, THEN you can see if reuse is possible or desirable.

Certainly any consistent business rules (rule methods) should be candidates for reuse. But DO NOT reuse just because there are common properties. Property code is meaningless - template/snippet stuff, not worthy of reuse. But algorithmic logic or validation logic - that is good for reuse.

And you might have certain name-value lists or other read-only data objects that appear in multiple use cases - that's quite common.

But I wouldn't expect to see your mainline business objects span these use cases. Each one of these use cases addresses a different need, and will almost certainly have its own set of objects.

TerryHolland replied on Saturday, January 13, 2007

RockfordLhotka:

So it is highly unlikely, especially in a web setting (which it sounds like where you are) that you'd have such a comprehensive object graph. That might make sense in a WinForms setting, where you really could have a form that displays and edits all that data at once. But in the web that's just not likely.

So does this imply that you would use a different set of business objects for a web app to those that you would use for a win app?  Some of my application will be used in a win application as well as web - though 80% of app will be a web app

RockfordLhotka:

Quote<-ReadOnlyRoot
ItemList<-ReadOnlyChildList
Item<-ReadOnlyChild

Then it sounds like you have a case where the user wants to edit one of the line items. That's a use case, with its own object(s), probably:

QuoteItem<-EditableRoot

It seems from what you are saying, that there is not much need  for EditableChildList or EditableChild classes in a web environment.  That being the case would it not be better to use ReadOnlyRootList and ReadOnlyRoot classes for my list of QuoteItems that will display on my Quote page.  This way, if when I added an Item for example, in a pop-up window, I could easily refresh the list of QuoteItems without having to reload the Quote object when pop-up is closed and focus is back on the Quote page if I use Ajax technology on my QuoteItems list

RockfordLhotka:

On the other hand, rationally thinking about it, can a user actually interact with 12000 objects in any meaningful sense? That seems quite unlikely - even in a WinForms setting.

You're absolutely right - this is not necessary.

Thanks again for your input

SonOfPirate replied on Saturday, January 13, 2007

Fact is that Win apps will support the more complex object models but that doesn't mean that you have to use them.  I have found that following the more web-oriented approach still works well for Win apps.  In fact, it allows you to decouple your forms from each other which, IMO, is a better and more flexible design.

In my experience, although I don't use it much, EditableChildList and EditableChild can come into play in web apps when you want to have an editable grid (i.e. inline editing).

As for your question about adding items to the child collections, it sounds like you are on the right track.  Since the child object (QuoteItem) is being edited in another form, it is persisted to the data store from that form so all you have to do is refresh the original to reflect the changes.  As far as the underlying mechanics since the QuoteItems collection is derived from ReadOnlyChildList, well...  You never really have to add it to the collection, do you?  That's the trick.

By persisting the child object to the data store itself then refreshing the QuoteItems collection from the same data store, you have the same affect without having to code the addition.  Make sense?  This is divergent from the way we are used to thinking and is something I struggled to wrap my head around originally, but it works and simplifies things greatly.

This is different, of course, to many-to-many relationships where you do have to establish that relationship somewhere.  Rocky illustrates this with the Project->ProjectResource->Resource, etc. design in his ProjectTracker sample project.

Good luck.  HTH

 

TerryHolland replied on Saturday, January 13, 2007

SonOfPirate:

By persisting the child object to the data store itself then refreshing the QuoteItems collection from the same data store, you have the same affect without having to code the addition.  Make sense?  This is divergent from the way we are used to thinking and is something I struggled to wrap my head around originally, but it works and simplifies things greatly.

Do I lose any undo capability by working this way.  ie if my from my Quote page I add a QuoteItem, it seems that the advice is to create a new QuoteItem EditableRoot object and save this to the DB then navigate back to the Quote page and refresh the grid displaying QuoteItems.  Could I 'undo' at this point to remove the new QuoteItem?

Terry

Copyright (c) Marimer LLC