Versioning in HealthVault

Download the sample code from MSDN Code Gallery.

[Note that EncounterOld has been renamed to EncounterV1 in newer versions of the SDK]

“Versioning” is an interesting feature of the HealthVault platform that allows us to evolve our data types (aka “thing types”) without forcing applications to be rewritten. Consider the following scenario:

An application is written using a specific version of the Encounter type. To retrieve instances of this type, you write the following:

HealthRecordSearcher searcher = PersonInfo.SelectedRecord.CreateSearcher();

HealthRecordFilter filter = new HealthRecordFilter(Encounter.TypeId);
searcher.Filters.Add(filter);

HealthRecordItemCollection items = searcher.GetMatchingItems()[0];

foreach (Encounter encounter in items)
{

}

That returns all instances of the type with the Encounter.TypeId type id. When the XML data comes back into the .NET SDK, it creates instances of the Encounter wrapper type from them, and that’s what’s in the items array.

Time passes, cheese and wine age, clothes go in and out of style. The HealthVault data type team decides to revise the Encounter type, and the change is a breaking one in which the new schema is incompatible with the existing schema. We want to deploy that new version out so that people can use it, but because it’s a breaking change, it will (surprise surprise) break existing applications if we release it.

Looking at our options, we come up with 3:

  1. Replace the existing type, break applications, and force everybody to update their applications.
  2. Leave the existing type in the system and release a new type (which I’ll call EncounterV2). New applications must deal with two types, and existing applications don’t see the instances of the new type.
  3. Update all existing instances in the database to the new type.

#3 looks like a nice option, were it not for the fact that some instances are digitally signed and we have no way to re-sign the updated items.

#1 is an obvious non-starter.

Which leaves us with #2. We ask ouselves, “selves, is there a way to make the existence of two versions of a type easier for applications to deal with?”

And the answer to that question is “yes, and let’s call it versioning”…

Versioning

Very simply, the platform knows that the old and new versions of a type are related to each other, and how to do the best possible conversion between the versions (more on “best possible” in a minute…). It uses this information to let applications pretend that there aren’t multiple versions of a type.

Down Versioning

The first scenario is existing applications that were written using the original version of the Encounter type (which we’ll call EncounterV1 for clarity), and what happens when the come across a health record that also has EncounterV2 instances in it. Here’s a graphical indication of what is going on:

image

This application is doing queries with the EncounterV1 type id. When a query is executed, the platform knows that the encounter type has multiple versions, and converts the query for the V1 type to a query for all encounter types (both EncounterV1 and EncounterV2).

The platform finds all the instances of those two types, and looks at each one. If it’s an EncounterV1 instance, it just ships it off to the application.

But, if it’s an EncounterV2 instance, the platform knows (by looking at the application configuration) that this application doesn’t know what to do with an EncounterV2. It therefore takes the EncounterV2 instance, runs a transform on the EncounterV2 xml to convert it into EncounterV1 xml, and ships that across to the application. The data is “down versioned” to the earlier version.

The application is therefore insulated from the updated version – it sees the instances using the type that the application was coded against.

Down version conversions are typically lossy – there are often fields in the V2 version that are missing in the V1 version.  The platform therefore prevents updating instances that are down versioned, and will throw an exception if you try. You can look at the IsDownVersioned property on an instance to check whether updating is possible to avoid the exception.

Up Versioning

The second scenario is an application written to use the new EncounterV2 type:

image

This time, the V1 data is transformed to the V2 version. An application can check the aptly-named IsUpVersioned property to tell whether an instance is up versioned.

Higher versions typically contain a superset of the data in the old version, and the platform therefore allows the instance to be “upgraded” (ie updated to the new version).

However, doing so will prevent an application using the V1 version from being able to update that instance, which may break some scenarios. The application should therefore ask the user for confirmation before upgrading any instances.

This would be a good time to download the sample applications and run the EncounterV1 and EncounterV2 projects. Because this behavior is controlled by the application configuration, each application has its own certificate, which will need to be imported before the application can be run.

Add some instances from both V1 and V2, and see how they are displayed in each application. Note that EncounterV1 displays both the instances it created and the down-versioned instances of the EncounterV2 type, and that EncounterV2 displays the instances it created and up-versioned instances of the EncounterV1 type.

Version-aware applications….

In the previous scenarios, the application was configured to only use a single version of a type.

In some cases, an application may want to deal with both versions of a data type simultaneously, and this is known as a “version-aware” application.

We expect such applications to be relatively uncommon, but there are some cases where versioning doesn’t do everything you want.

One such case is our upcoming redesign to the aerobic session type. The AerobicSession type contains both summary information and sample information (such as the current heart rate collected every 5 seconds). In the redesign, this information will be split between the new Exercise and ExerciseSamples type. AerobicSession and Exercise will be versioned, but there will be no way to see the samples on AerobicSession through Exercise, nor will you be able to see ExerciseSamples through AerobicSession (there are a number of technical reasons why this is very difficult – let me know if you want more details). Therefore, applications that care about sample data will need to be version-aware.

Writing a version-aware application adds one level of complexity to querying for data. Revisiting our original code, this time for an application that is configured to access both EncounterV1 and EncounterV2:

HealthRecordSearcher searcher = PersonInfo.SelectedRecord.CreateSearcher();

HealthRecordFilter filter = new HealthRecordFilter(Encounter.TypeId);
searcher.Filters.Add(filter);

HealthRecordItemCollection items = searcher.GetMatchingItems()[0];

foreach (Encounter encounter in items)
{

}

When we execute this, items contains a series of Encounter items. The version of those items is constrained to the versions that the application is configured (in the Application Configuration Center) to access. If the version in the health record is a version that the application is configured to access, no conversion is performed, *even if* the version is not the version specified by the filter.

That means that the items collection may contain instances of different versions of the type – in this case, either Encounter or EncounterOld instances. Any code that assume that instances are only of the Encounter type won’t work correctly in this situation. You may have run into this if you are using the HelloWorld, as it has access to all versions of all types.

Instead, the code needs to look at the instances and determine their types:

foreach (HealthRecordItem item in items)
{
Encounter encounter = item as Encounter;
if (encounter != null)
{
}

EncounterOld encounterOld = item as EncounterOld;
if (encounterOld != null)
{
}
}

This is a good reason to create your own application configuration rather than just writing code against HelloWorld.

Controlling versioning

The platform provides a way for the application to override that application configuration and specify the exact version (or versions) that the application can support. For example, if my application wants to deal with the new type of Encounter all the time, it can do the following:

filter.View.TypeVersionFormat.Add(Encounter.TypeId);

which will always return all versions of encounter instances as the Encounter type. 

EncounterBoth is a version-aware application in the sample code that handles the Encounter and EncounterV1 types. It will also let you set the TypeVersionFormat.

Asking the platform about versioned types…

If you ask nicely, the platform will be happy to tell you about the relationship between types. If you get fetch the HealthRecordItemTypeDefinition for a type, you can check the Versions property to see if there are other versions of the type.

The VersionedTypeList project in the sample code shows how to do this.

Naming Patterns

 

 

 

 

There are a couple of different naming patterns in use. The types that were modified before we had versioning use the “Old” suffix (MedicationOld and EncounterOld) to denote the original types. For future types, we will be adopting version suffix on the previous type (so, if we versioned HeartRate, the new type would be named HeartRate, and the old one HeartRateV1). We are also planning to rename MedicationOld and EncounterOld to follow this convention.

We will also be modifying the tools (application configuration center and the type schema browser) to show the version information. Until we get around to that, the best way is to ask the platform as outlined in the previous section.