5

A C# .NET application talks to an external component by calling a known API and marshalling interop structures from the component's response.

This is already implemented and working well. However, once versioning comes into the equation, things get a bit more complicated: as the component will evolve over time thus requiring interop structures be kept in-sync. The application must still be able to talk to components on older versions, so that it has to pick the right interop structs at run-time for a given version of the component.

I've been throwing this problem around in my head for a couple days and haven't come up with anything I'm particularly happy about. Google hasn't been much help, either, so I thought I'd try writing this up and posting it here in an effort to get some feedback. This is what I have so far.

Package all versions of the structs in a library, have a registry and decide at runtime which structs to use

Here, every new version of the structs is compiled into the new version of the .NET application, and some kind of registry is responsible for mapping API versions to interop struct versions in the library.

Class/struct naming becomes an issue, since Class1 would now have to be called Class1_v1_1 or some other way to disambiguate based on the name (either that, or use the same name and put them in a separate namespace).

Load versions of the interop structures from a versioned DLL

Here, every version of the interop structs would be compiled into an individual DLL and loaded dynamically based on the API version. A mapping would still need to be created between API version and DLL name.

The naming problem disappears, but certainly there are a lot more moving pieces for deployment and consequently things that can go wrong at runtime.


As I said, I'm not happy with either one and feel that there's a much obvious/cleaner/elegant solution which is escaping me. I can't imagine that I'm the first person who's had to support changing versions of a dependent component, so is there some known pattern which can provide some guidance for scenarios such as this?

Thanks in advance for any feedback &/or possible alternatives!

2 Answers2

1

The number one priority should be to minimize the changes to the interop API, if you have any control over the "known API". Good API design minimizes changes over time.

Both solutions would work and the versioned dll solution might be cleaner, but more bulky, since you probably have much of the API that does not change between versions. You will have to manage the code reuse in some way, perhaps by using a common library.

I would detect the version using a call to the known API, if you can, instead of using a registry. That way you are not forced to use the same version across the whole machine.

0

If possible, introducing a version number to each structure is one approach, which is particularly popular with REST APIs and file formats (both binary and plain text). Your application then has the ability to upgrade each version to the next. That means you only need to worry about one upgrade path, too, and can just chain upgrades together as necessary. If desired, it's possible to add other upgrade paths to speed things up and you can even have downgrade paths (these are often visible in programs where multiple versions are maintained at the same time and there's usually a cost preventing many customers from upgrading immediately).

So for example, maybe you have an Employee struct. Perhaps version one is really simple:

{
    '_version': '1.0'
    'first_name': 'Amelia',
    'last_name': 'Bedelia',
    'employee_id': 123,
    'salary': 65000
}

But then later on, suppose we really wanna combine that name field. Let's just make that change cause presumably there's a good business reason for it! We have a new version to our structure:

{
    '_version': '1.1'
    'name': 'Amelia Bedelia',
    'employee_id': 123,
    'salary': 65000
}

Now, we could just have our codebase branch on the version number, but it's much more future proof and often cleaner to simply update the older structure to something compatible with the new one. This might involve filling in reasonable defaults, reading in additional information from elsewhere to fill in blanks, asking users for that information, or whatever. The idea, however, is to ensure that your codebase only needs to deal with the most recent version of the structure.

So we'd have some upgrade function that has some branch like:

if employee['_version'] == '1.0':
    # Let's just make an assumption about names and combine it blindly
    employee = {
        '_version': '1.1',
        'name': employee['first_name'] + ' ' + employee['last_name'],
        'employee_id': employee['employee_id'],
        'salary': employee['salary']
    }
# And so on for other version upgrades, returning the final object
# at the end.

And then we'd just keep doing this until we have the current version (which could then be converted to a stronger type). This pattern also works well for database updates (it's how Android's recommended handling for their SQLite databases works).

Now, this answer has been pretty high level so far. But it's not unique to where the data structure can be represented as a generic dictionary (although it certainly is a versatile and adaptable data structure for exchange of information across system boundaries). If data can be stored in a raw binary format and we always have the version in a certain position, we can easily choose which low level structure is used to interpret the data (and then apply updating from there). In which case we'd still pretty much need to store said structures somewhere, though (but the value comes from not having to have many code paths for these different versions of structures).

Kat
  • 330
  • 2
  • 10