When working with two data stores what's the recommended save operation?

When working with two data stores what's the recommended save operation?

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


boo posted on Monday, February 19, 2007

I don't want to get into the awful design we inherited but what we'd like to do and what we can do are apples and oranges...so here's what we have:

We are implementing BO's and the BO needs to save both to SQL Server and another data store.  Sometimes it will only save to SQL Server, sometimes it will save to both, it will never save just to the other data store.  This is simple enough to accomplish by overloading the Save method.

Now we have a set of methods to save to the other data store in seperate library, but what we send needs to know what the original BO's values were in order to know what to update.  In otherwords if nothing has changed, we don't want to update the secondary data store...and I won't get into why (bad design that we cannot alter), but we NEED to know whether the BO or it's children have changed prior to updating the second data store, if it hasn't we can't send to second data store.

Question that we're struggling with...do we make the BO smart enough to convert itself into the value objects that the the external set of methods used to update the second data store use and when completing a DataPortal update or create call the required methods in the external library or do we use a third library to control what gets called and how (ala Mediator pattern).  We like the idea of using the pattern because know the BO's don't have to know about how data is saved to a secondary data store, but it introduces a whole new set of problems (in particular circular references and now history of object is lost or the mediator becomes knowledgable of how external manager works which takes the purpose of the pattern away). 

There is also a part of me that strongly says...well if the BO's is suppose to be self containing why wouldn't it know how to convert itself and save to this secondary data store?  I'm leaning heavily towards this...but some other people's advice - in particular anyone who has dealt specifically with this...would be much appreciated.

JonM replied on Monday, February 19, 2007

The Dataportal_Update method doesn't actually get called if there are no changes to the data.  The BO keeps track of whether it is "dirty" or not.  I would just add a more database code to your dataportal methods to save the updates  in the second database rather than trying to overload methods.

ajj3085 replied on Monday, February 19, 2007

Hey boo,

First, I don't think you need to overload the Save.  Using the standard DP_I or DP_U should be just fine.

Your last paragraph is correct; the BO should know that it needs to update this second datastore.  How it does so can be encapsulated into another class... but ultimately the BO should decide whether or not to update this second datasource.

Out of curiosity, will you be using DTC to coordinate the transaction between both datasources?  Seems like a good idea to do so... but I'm not sure if the second datastore needs to know about DTC to use it (or does DTC magically know how to 'rollback' changes to whatever you're updating?).

HTH
Andy

boo replied on Monday, February 19, 2007

Thanks Andy...I misread your reply.  The reason I need to overload save is in below post.

DTC is not an option...again, one of those inherited nightmares.  I'll pass on your thoughts.  I'm actually hoping Rocky will answer too with the same results.  I'm in agreeance with you, but I might need his 'blessing' to seal the design.

boo replied on Monday, February 19, 2007

The reason why I need to overload Save is that during the workflow, which may be from hours to days, to weeks, the user will just be saving to SQL DB.  At the end of the workflow they will need to take all this information and save it to the secondary data store.  So the default Save will just save to SQL DB as normal...at the end of the workflow, and few select places during, the information will be saved to the secondary data store which may add/change information meaning I'll have to save that new/updated information to SQL DB afterwards.  In these cases an overload of Save would be called which would then let the business object know to save to the extra location along with SQL DB.

ajj3085 replied on Tuesday, February 20, 2007

Boo,

An overload of Save isn't the way to go for what you're trying to acomplish.  If you go that route, you'll have the UI deciding on a business rule (when does the secondary datasource need to be updated) when really your business object is what must make the decision.  If you were to reuse this business object in another UI, a web site or webservice or a Windows service, you'd have to duplicate that logic that decides which overload of Save to call.  Its that duplication which is actually a business rule.

It may be that you need another seperate object who's purpose is to determine if the workflow is completed and take the necessary steps updating the second datasource and such.  If not, any decision you make in Save could be made in the DP_U method, and that's where it should be.  Remember, you need to keep  your data access code in the DataPortal_ methods.  Review the book for reasons why.

HTH
Andy

boo replied on Tuesday, February 20, 2007

You hit the nail on the head, and in an ideal world where we didn't inherit a monstrosity of an application that is so tightly coupled to the GUI and so many other things that are done wrong that need to be addressed that is what we would do...and that is what we are working towards.

However it's simply not a refactoring exercise that we have the time nor the budget for, so for the time being the GUI has to know more than it should...and thus the overloaded Save method.  Believe me, it's not the ideal solution to the problem, it's a hack really until we have the time and budget to do what needs to be done.

hurcane replied on Monday, February 19, 2007

We're not dealing with multiple data stores, but we had a need to avoid saving existing objects unless a database field actually changed. The typical technique of calling MarkDirty did not work for us because changing a value from A to B, then back to A would be considered dirty, and we couldn't have that. Perhaps our solution will be helpful to you.

If you haven't already, create a "custom" business object base that will be the base object for all your business classes. Rocky recommends it, and I would strongly second that option.

We created "smart" data objects for each primary data type that we deal with, plus a few special case types (e.g. a 0-filled number/string hybrid). Each type tracks original and current values. These smart data types inherit from our base business object, so they inherit a lot of important functionality. There is no DataPortal code in these objects because their purpose is not to be saved to a database.

Our user-facing business objects maintain the "smart" data objects internally, but expose the business object properties as the primitive data types. This simplifies data binding. The DataPortal_Fetch methods instantiate a smart data object for each field that is being maintained.

Here is a typical property for us on an editable business object.

        Public Property PurchaseUOM() As String
            Get
                CanReadProperty(True)
                Return mPurchaseUOM.Value
            End Get
            Set(ByVal value As String)
                CanWriteProperty(True)
                If value <> mPurchaseUOM.Value Then
                    mPurchaseUOM.Value = value
                    PropertyHasChanged("PurchaseUOM")
                End If
            End Set
        End Property

mPurchaseUOM is a "SmartString" in this example that is declared at the object level.

Each of our business objects has to override the IsDirty function. Here's an example IsDirty function from one of our business objects:

        Public Overrides ReadOnly Property IsDirty() As Boolean
            Get
                Return MyBase.IsDirty OrElse mVPC.IsDirty OrElse mPurchaseUOM.IsDirty _
                    OrElse mPurchaseList.IsDirty OrElse mReplacementCostStartValue.IsDirty _
                    OrElse mReplacementCostDiscountMethod.IsDirty OrElse mReplacementCostDiscountValue.IsDirty _
                    OrElse mVendorItem.IsDirty
            End Get
        End Property

The SmartString's IsDirty method is simply "Return mValueOriginal <> mValueCurrent".

This allows us to detect when an object is "really" dirty. As an additional benefit for us, we use dynamically generated SQL in our business objects to update the database. To avoid updating columns that didn't change, we are able to check the smart data object's IsDirty method and use the original and current values in constructing our dynamic SQL.

Copyright (c) Marimer LLC