Service Fabric: Move an enum class to different project

104 views Asked by At

Recently to resolve circular dependency we need to move an enum class to a different project under a different namespace. There are some actor and stateful services which keep the instance of this enum value in their reliable states.

Enum class is something like this:

namespace com.libA
{
    public enum Foo
    {
        None = 0,
        Foo1 = 1,
        Foo2 = 2,
    }
}

We want to move this to another project with the namespace as com.libB. These enum values are stored in reliable states inside actor and stateful services and are fetched like this:

Foo foo = await this.StateManager.GetStateAsync<Foo>("FooKey").ConfigureAwait(false);

One of the actor service which stores the value of Foo is a very long-lived actor. It can theoretically live till infinity in happy paths and if delete is never invoked from outside. We tried simple refactor > Move and tried on our non-prod environments. This started causing SerializationException in our non-prod environments. The error message says: Expecting element 'Foo' from namespace 'http://schemas.datacontract.org/2004/07/com.libB'.. Encountered 'Element' with name 'Foo', namespace 'http://schemas.datacontract.org/2004/07/com.libA'.

These exceptions are coming just before fetching the value of Foo in older actors.

My question is:

  1. How can we move Foo to namespace com.libB? Will two-phase upgrade help here?
  2. Is it even possible to do so without data loss/corruption?
2

There are 2 answers

1
Preben Huybrechts On BEST ANSWER

You can add the DataContract attribute with a namespace to your type. Since you are already running in your production environment you could use the namespace from the error to work arround the problem.

Example:

[DataContract(Name = "Foo", Namespace = "http://schemas.datacontract.org/2004/07/com.libA")]
public enum Foo
{
   // ...
}

A better approach might be to have an upgrade plan.

  1. Let the 2 types coexist
  2. When retrieving the state, have a try-mechanism, retrieve with old type, if this fails with the serialization exception try with the new type.
  3. When persisting the state, convert it to the new type in the new namespace, when it's the old type. (Add some logging so you can verify the conversion happend)
  4. Deploy to test, see if it work, if ok deploy to production
  5. Remove the old type, remove the conversion code
  6. Deploy to test, see it it works, if ok deploy to production.
0
LoekD On

One option is to create a custom serializer wrapping DataContractSerializer for all types that use Foo, that fix /ignore the namespace during deserialization.

IReliableStateManager.TryAddStateSerializer is used to register a custom serializer for the given type T. This registration should happen in the construction of the StatefulServiceBase to ensure that before recovery starts, all Reliable Collections have access to the relevant serializer to read their persisted data.

  • In IStateSerializer<OrderKey>.Read(BinaryReader reader), read the serialized data as XML
  • Change the XML namespaces where needed
  • Feed the XML to the DataContractSerializer to create an object
  • Return the object