Optimistic Concurrency with SOAP in SSDS

In my last post I described how ETag's can be used in the upcoming sprint 3 version of SSDS.  In this post I'd like to talk about how this same functionality is exposed through our SOAP service head.

In many ways designing this particular feature for the REST head was much easier than for the SOAP service head.  This is because the Http specification describes, in a fair bit of detail, what the semantics are around things like ETag's.  The SOAP specification provides no such guidance and as a result we've rolled our own implementation here to support this feature in a way that makes sense for our service. 

VersionMatch

As I mentioned earlier, since there is no ETag concept natively in SOAP we've introduced a new object called, "VersionMatch".  We've augmented the Scope struct in the system to now include this so that version is now also considered a qualifier for operations that take place via the SOAP service head.

The VersionMatch object, as shown in the class diagram below, captures two things.  The first, is the version of the entity in question.  This could be the version of the entity we've just created, the version that I, as a caller, want to retrieve, or finally the one I want to update or delete. 

The second concept is something we refer to as the, "MatchType" or VersionMatchType.  This is used to determine how the service should compare the version when executing the requested operation.  This could mean that we only perform the operation if the version matches the version in the store (the VersionMatchType.Match case), or only perform the operation if it doesn't match (the VersionMatchType.NotMatch case) or finally to ignore the version completely when performing the operation.  NOTE: By default we use the Ignore semantic unless otherwise instructed.

Class Diagram of the modified Scope object along with the new VersionMatch object and VersionMatchType enum.

image

 

Now, that we've covered a bit of the changes to how we address things in the system let's see how we can apply these new features to some common scenarios.

Conditional Get

Conditional retrieval is an interesting case if a mid-tier or client application is maintaining a cache.  This shows up many times in the web browser case where large files are cached until they've been either refreshed or updated on the service side. 

We can now perform a similar operation now using the SOAP service head.  I've created a sample below which illustrates how a caller can now conditionally retrieve an entity only if the version has changed.

             using (SitkaSoapServiceClient client = new SitkaSoapServiceClient("SitkaSoapEndpoint"))
            {
                try
                {
                    // Create a reference to some entity that we know exists and one that
                    // we have a reference to in our cache (the version we have is 2000).
                    Scope entityScope = new Scope();
                    entityScope.AuthorityId = "some_authority";
                    entityScope.ContainerId = "some_container";
                    entityScope.EntityId = "123";
                    entityScope.VersionMatch = new VersionMatch();
                    entityScope.VersionMatch.Version = 2000;
                    entityScope.VersionMatch.MatchType = VersionMatchType.NotMatch;

                    // Then, retrieve the entity *only* if the version doesn't match the
                    // one that I already know about.
                    Entity theEntity = client.Get(entityScope);

                }
                catch (FaultException<Error> ex)
                {
                    if (ex.Detail.StatusCode == ErrorCodes.EntityNotModified)
                    {
                        // there is no later version.
                    }
                }
            }

Conditional Update and Delete

The next two cases are really very similar to one another.  In both of these cases we really only want to update (or delete) the entity if we know that there has been no other modifications to the entity in question.  This way we can be certain that we've not overwritten someone else's changes (in the update case) or perhaps deleted an entity which now (since the update) may not have needed to be deleted. 

The code listing below illustrates both of these cases.

Conditional Update in SOAP

             using(SitkaSoapServiceClient client = new SitkaSoapServiceClient("SitkaSoapEndpoint"))
            {
                try
                {
                    // Create a reference to some entity that we know exists.
                    Scope entityScope = new Scope();
                    entityScope.AuthorityId = "some_authority";
                    entityScope.ContainerId = "some_container";
                    entityScope.EntityId = "123";

                    // Then, retrieve the entity (any version will do).
                    Entity theEntity = client.Get(entityScope);

                    // Update some properties on the Entity.
                    theEntity.Properties["FavoriteStorageService"] = "SSDS";

                    // Next, update the version match properties on the scope we'll be using to update
                    // the entity with.
                    entityScope.VersionMatch = new VersionMatch();
                    entityScope.VersionMatch.Version = theEntity.Version;
                    entityScope.VersionMatch.MatchType = VersionMatchType.Match;

                    // Finally, update the entity if the version check clears.
                    client.Update(entityScope, theEntity);

                }catch(FaultException<Error> ex)
                {
                    if(ex.Detail.StatusCode == ErrorCodes.PreconditionFailed)
                    {
                        // Our version check failed so we don't have the latest and greatest.
                    }
                }
            }

Conditional Delete in SOAP

             using(SitkaSoapServiceClient client = new SitkaSoapServiceClient("SitkaSoapEndpoint"))
            {
                try
                {
                    // Create a reference to some entity that we know exists.
                    Scope entityScope = new Scope();
                    entityScope.AuthorityId = "some_authority";
                    entityScope.ContainerId = "some_container";
                    entityScope.EntityId = "123";

                    // Next, set the version match properties on the scope we'll be using to remove
                    // the entity with.
                    entityScope.VersionMatch = new VersionMatch();
                    entityScope.VersionMatch.Version = 2000; // the version of the entity we know about.
                    entityScope.VersionMatch.MatchType = VersionMatchType.Match;

                    // Finally, remove the entity if the version check clears.
                    client.Delete(entityScope);

                }catch(FaultException<Error> ex)
                {
                    if(ex.Detail.StatusCode == ErrorCodes.PreconditionFailed)
                    {
                        // Our version check failed so we don't have the latest and greatest.
                    }
                }
            }
  

That covers the optimistic concurrency examples for the Sprint 3 bits of SSDS.  I'm certain that there are other scenarios here that I'm not covering and I'd really like to hear other scenarios if you have them to share. 

Next time, I'll cover perhaps the biggest feature of the sprint (and one of our most requested features of the service that we have now completed for sprint 3....).  Look for more shortly :-)