System-wide concurrency handling - feedback & suggestions welcome

System-wide concurrency handling - feedback & suggestions welcome

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


skagen00 posted on Wednesday, June 28, 2006

So I know that some of you insert a class between the CSLA BusinessBase and your business objects for tweaking/enhancing the framework. 

I've been thinking about including some things to handle basic concurrency in this inserted class. Essentially, including a timestamp and userID field for BO creation and modification. In addition, since this behavior + data will be somewhat normalized, I feel like I need to tag each business object with a unique system ID (thinking Guid), which would also be a field in my inserted BusinessBase class.

It would implement GetIdValue() (with the Guid), and also contain an Update() and Insert() method. Update() and Insert() would call the stored procedure to update the timestamp & userID information, checking concurrency, and throwing an exception if it fails. I figure a failure would just cause the entire root transaction to fail and hence I've got a straightforward means to implement concurrency instead of having a timestamp field on each object and embedded with each stored procedure, etc. 

The business objects would have three additional pieces of work to do (besides inheriting from this inserted class) - just call base.Update() and base.Insert() in their respective methods before their own update and insert code, and include this "system ID" in the stored procedure for insert & update, as the main business object table would need to contain this system ID for each business object's representation in the DB. Fetch would also need to be accomodated in the same fashion.

Does anyone see any real problems with this approach (beyond the extra stored procedure call)?

 

RockfordLhotka replied on Wednesday, June 28, 2006

What happens when you have an object that works with data from 2+ tables? When using behavioral OO design this is likely to happen, and then you'd need multiple timestamp values, one for each table.

guyroch replied on Wednesday, June 28, 2006

My answer to this would be to still have the "inserted class" with all the repetitive properties/business logic but not the so call insert and update method.  I would codify this in the DataPortal_Update method because is where you actually map you business object to your persistent storage.

skagen00 replied on Thursday, June 29, 2006

RockfordLhotka:
What happens when you have an object that works with data from 2+ tables? When using behavioral OO design this is likely to happen, and then you'd need multiple timestamp values, one for each table.

I don't think that should be a problem - why would I need multiple timestamp values for each table? My goal is to ensure concurrency for business objects.

Considering an example using the PTracker demo. I have two users: Jack and Jill. They both retrieve the same project, which contains two project resources. I'm also going to say that this system assigned Guid, the created and modified datetime and staff - this is called "change information" for brevity.

I want change information to be maintained for each business object - not just the root (Project) but also for the children (ProjectResources). The maintaining of this change information will also serve as the concurrency checks.

Scenario 1: Jack changes two project fields on the main BO, and so does Jill. Jack goes to save the record. The very first thing after ensuring that the object is dirty in DataPortal_Update (if (base.IsDirty)) and opening the connection to handle the entire transaction is to call base.Update(cn). This update, located in the inserted businessbase class, will call a stored procedure to update the change information provided the modified date matches between the DB and BO. If the data doesn't match, it'll throw an exception.

Jack's update goes through just fine, he's the first one who saved it. Jill, on the other hand, when trying to save has her change information stored procedure fail, and the transaction rolls back - she doesn't get to save her object - the update process short circuits right away.

Scenario 2: Jack changes two project fields, and Jill changes a field on a project resource. Jack saves, and the change information gets updated on the project BO. Jill goes to save, and base.IsDirty (for the project) has not changed - thus it skips attempting to update the project's change info (and project data in the table itself) - but it does attempt to update the projectresource that Jill made dirty. Jill's update to the projectresource's change information saves just fine - Jack didn't dirty it and the change information for that projectresource in the DB matches what Jill has in her BO. Jill's save fully succeeds and gets committed.

The overall project has allowed changes of Jack's (to the project title and description for example) as well as Jill's change (to a project resource on the project) and each BO (root and child) knows who changed each and when.

----

The alternate mechanism is to perhaps have the root BO updating its change information (but not its actual data) with IsDirty rather than base.IsDirty, and in Scenario 2, Jill would have had her changes fail. (I could see this if one needs to protect the business object's validity - let's say the project as a whole needs to have at least one project manager if it's checked as a "critical project". Under the initial mechanism, this could be violated. Jack and Jill retrieve - Jack marks the project as a critical project and saves (it's ok because there is a project manager) but Jill changes the role away from project manager and saves (her BO isn't marked as a critical project so this is valid).

----

For UI display, one might override the modified user & timestamp properties to display for the project the maximum of it and its child BOs modified info - so the user might only see that the last changer of the project was Jill, rather than Jack. (Depending on the UI wishes)

---

Am I fundamentally missing something? It really seems like this would work and would allow me to abstract most of this behavior and tracking into a base class for all my business objects.

Thanks!

RockfordLhotka replied on Thursday, June 29, 2006

You pre-suppose that each object will only interact with a single table. What I am saying, is that this is not the case, if you do behavioral/responsibility-driven OO design. Yes, it is the case in the PTracker example, because that is not a real application and I had to have a small enough example to fit in a single chapter. But in real applications, in the real world, it is the case that proper OO design will often cause you to have objects that contain data from multiple tables.

skagen00 replied on Thursday, June 29, 2006

Hmm.. I'm not sure I fully understand what you are saying.

Let's say I have a class called Individual, and it ends up utilizing a few tables - Profile and Individual (profile containing generalized information for perhaps organizations as well. i.e. Profiles = Individuals + Organizations).

I will be storing all of the "businessbase" related fields (created/modified user & timestamp) keyed by Guid in a particular table for this common BO related data. So it seems that whichever BO "advances" the modified timestamp first gets to do all of its table writes, to however many tables it needs to - it's passed the concurrency check and has essentially made sure that anyone who has older copies of the BO won't be able to save. These old records that attempt saving will fail at "advancing" this modified timestamp and they won't be able to do any of their writes.

I read that you're busy up in Montreal, but if you (or anyone) has a second and could provide a concrete example of where this might fail I'm certainly interested. You seem to be identifying something I've yet to grasp.

Harry_Seldon replied on Wednesday, May 14, 2008

Hi skagen00, how did this work out for you? I'm trying to deal with concurrency at the moment and I seem to be missing somthing fundamental.

skagen00 replied on Wednesday, May 14, 2008

The way we went with this is that each of our entity objects end up having a created & modified user and datetime. By default when updating the entity object, the original datetime when read is passed as a parameter for the update stored procedure (as part of one of our intermediate framework classes).

At the top of every update stored procedure we make a call to a stored procedure that checks that a given table record has the matching modified datetime to when we read it. The way it was done opens up a slight window (during the update itself), but it was minimal enough for me to not be too concerned about it in terms of the consequences of any missed concurrency violations.

I would have rather seen the implementation update the modified datetime where the datetime matched when the record was read -- that way, the first "updater" would get rights to completing the operation (any others would fail). It's something that could be remedied but it's been low on my priority list to make it an issue.

Chris

smiley riley replied on Thursday, May 15, 2008

Why not create a set of underlying values for each obejct and pass these in a where clause, only updating where the same. You can then handle when no rows are updated. You can use datetime but you have fixed granularity. A guid is NOT garanteed to be unique. What about DateTimeStamp???

 

skagen00 replied on Thursday, May 15, 2008

"You can use datetime but you have fixed granularity"

I think I understand where you're going but in some cases you don't want certain portions of an object to change independently and successfully with the originally read object because you can invalidate rules. I even consider a change to the child to be a change to the root object for concurrency purposes.

It depends on what your concurrency needs are.

One might be that an invoice status can be set to "ready to pay" if all invoice lines are "approved". Well, if you let a non-ready-to-pay invoice with two approved lines get retrieved twice and on the first you set it to "ready to pay" and on the second you unapprove one of the lines and you let both changes to succeed because "they don't overlap", well, you've gotten yourself a ready to pay invoice with an unapproved line.

The above isn't a direct example of what we're doing but I think it outlines a sample case that I want to avoid and avoid in a very general way. That is, I don't need to deal with any specific logic to handle these edge cases if I use the route I use.

For us, the likelihood of the same object being manipulated at the same time is relatively low - so the frequency of concurrency violation will be low. This makes the approach we're taking good for us. Now if you had some deal where you had a lot of people using the same records and had a higher frequency of concurrency violations under that scenario (making work a PITA for people as they're constantly losing their work) then you be fancier about concurrency and try not to reject changes as aptly as we're willing to do.

Your concurrency approach is really tied to the flavor of your application IMO.

smiley riley replied on Thursday, May 15, 2008

Your right, absolutely. But.....

Your example above is trying to solve a workflow issue by putting in data consitancy checks and balances. Surley above you would just join in the Invoice and update the status as part of the where clause to prevent the line getting set after the invoice status changed to "Ready To Pay". You then need to allow a workflow pattern for moving the invoice from ready to not ready to change the invoice lines back if the line was indead not ready or had been rejected late on?? Maybe I'm  missing something!!

Why not look at SQL Server Notifications?

 

Harry_Seldon replied on Thursday, May 15, 2008

Hi Chris,

Thanks for the reply. By "Entity object" do you mean buisness object or database table? I think we're gonna go down the timestamp column on all(well most) of our tables route. I want to use NHibernate but that looks like it is going to be rejected, due to time constraints.

HS

skagen00 replied on Thursday, May 15, 2008

By Entity Object I'm referring to a business object. In the event an entity object itself (not including children) span multiple tables, I just keep datetimes in the "base" table.

For example, Profiles can be Individuals or Organizations. My datetime is in the profile table.

 

 

Copyright (c) Marimer LLC