Upgrading Azure Service Fabric

I've been writing a series of blog posts on getting started with Service Fabric. But since I started writing, because of my slow pace, Service Fabric has moved on and is no longer in preview.

That has left my original demo a bit outdated and I needed to go through an update before I can continue writing the series. This post is about the steps required to upgrade Service Fabric and a Service Fabric application from preview to the most recent version at the time of writing.

It is a boring post, but potentially useful. Who knows, here goes.

Installing the updated runtime

The first thing we need to do is go and fetch the updated runtime, SDK and tools from Microsoft Azure. You should have no problem installing it.

Upgrading your project

When you open your Service Fabric project after upgrading you should be greeted with a dialog asking you to upgrade.

Dialog to upgrade Azure Service Fabric project

Dialog to upgrade Azure Service Fabric project

This should go smooth as well, and you will end up with some slight changes to your .sfproj-file and the deploy script.

Upgrading the Service Fabric libraries

My demo application "Sheepishly" consisted of a couple of different components.

Sheepishly Azure Service Fabric architecture

  • An API implemented as a stateless service
  • A "tracker" implemented as a stateful service
  • Actors representing individual sheep

After upgrading the Service Fabric runtime it seems the application isn't working anymore when deployed the my local cluster. A closer look at the Cluster Explorer reveals an error hinting at the problem.

Unhealthy event: SourceId='System.RA', Property='ReplicaOpenStatus', HealthState='Warning', ConsiderWarningAsError=false.
Replica had multiple failures during open. Error =System.IO.FileNotFoundException (-2147024894)
Could not load file or assembly 'Microsoft.ServiceFabric.Data.Interfaces, Version=5.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35' or one of its dependencies. The system cannot find the file specified

The demo application is running on an outdated preview version of the Service Fabric libraries.

<packages>
  <package id="Microsoft.ServiceFabric" version="4.4.87-preview" targetFramework="net451" />
  <package id="Microsoft.ServiceFabric.Data" version="1.4.87-preview" targetFramework="net451" />
  <package id="Microsoft.ServiceFabric.Services" version="1.4.87-preview" targetFramework="net451" />
</packages>

Let's go ahead and update the nuget-packages to a more recent version.

<packages>
  <package id="Microsoft.ServiceFabric" version="5.0.217" targetFramework="net45" />
  <package id="Microsoft.ServiceFabric.Data" version="2.0.217" targetFramework="net45" />
  <package id="Microsoft.ServiceFabric.Services" version="2.0.217" targetFramework="net45" />
</packages>

Upgrading the application code

The thing about previews is that you can't expect them to stay the same. After upgrading from a preview version of Service Fabric the demo application code needs a bit of upgrading as well.

We will need to make a few changes to both the API, stateful service and actors.

ServiceProxy

The syntax for accessing services through the ServiceProxy has changed slightly. This code from the demo for accessing a service with a single partition.

ServiceProxy.Create<ILocationReporter>(0, LocationReporterServiceUrl);

Becomes:

ServiceProxy.Create<ILocationReporter>(LocationReporterServiceUrl, new ServicePartitionKey(0));
ActorProxy

The syntax for accessing an actor is similar and has not changed from the preview, the ActorProxy has moved however, from here.

Microsoft.ServiceFabric.Actors

To here:

Microsoft.ServiceFabric.Actors.Client
Stateful services

Stateful services no longer have parameterless constructors, we now need to take in a "service context", which provides neat information about the service instance, replication, partitioning etc.

The context seems to contain the same information that we used to have in the "ServiceInitializationParameters" property of the service.

public Tracker(StatefulServiceContext serviceContext) : base(serviceContext)

The configuration of the replica listeners has also been simplified slightly, from this.

new ServiceReplicaListener(initParams => new ServiceRemotingListener<Tracker>(initParams, this))

To this:

new ServiceReplicaListener(this.CreateServiceRemotingListener)
Registering services

We no longer use the old FabricRuntime to register services, instead we use a separate ServiceRuntime. Changing our code slightly from this.

fabricRuntime.RegisterServiceType("TrackerType", typeof(Tracker));

To this:

ServiceRuntime.RegisterServiceAsync("TrackerType", context => new Tracker(context)).GetAwaiter().GetResult();
ServiceEventSource

The demo had some auto-generated code for reporting diagnostics events. This code needs updating to use the new service context. Example:

service.ServiceInitializationParameters.CodePackageActivationContext.ApplicationTypeName,

To:

service.Context.CodePackageActivationContext.ApplicationTypeName,
Actors

When it comes to actors, things start to come a bit more interesting. For one it seems Microsoft has gotten rid of the "stateless actor" concept. I never really understood why you would need an actor without state, so I am perfectly OK with that. It also means that we now only have Actor, not StatelessActor or StatefulActor.

State has also changes a bit for actors. An actor no longer has a state object of a given type, but instead has a dictionary containing whatever named state objects you wish.

So when declarating our Sheep-actor, we no longer do this:

internal class Sheep : StatefulActor<Sheep.ActorState>, ISheep

But simply this:

internal class Sheep : Actor, ISheep

Also notice that the Actor-class now resides in using Microsoft.ServiceFabric.Actors.Runtime.

Actor state

I mentioned that actor state is handled a bit different now than in the preview version. Up until now our sheep actor could access state through the "State"-property. Like this code for initializing state.

if (State == null)
    State = new ActorState { LocationHistory = new List<LocationAtTime>() };

To achieve the same now we need to access the state from the state dictionary.

var state = await StateManager.TryGetStateAsync<ActorState>("State");
if (!state.HasValue)
    await StateManager.AddStateAsync("State", new ActorState { LocationHistory = new List<LocationAtTime>() });

My initial thought is that the old approach of providing the type of the state as a type argument for the actor and accessing it through the State-property, is more elegant. I also think this is more in tune with the idea of actors.

On the other hand, the new dictionary-based approach is more versatile, and writing your own abstraction on top of it (if you are fine with the single "State"-property) is dead easy.

Registering actors

Rather than registering actors using the fabric runtime we now used a separate actor runtime. Which means rather than doing this.

fabricRuntime.RegisterActor<Sheep>();

We now do this:

ActorRuntime.RegisterActorAsync<Sheep>((context, information) => new ActorService(context, information, () => new Sheep()));

Notice that we are registering an "ActorService", which takes as the last argument a factory function for spawning new actors.

Stateless service WebAPI endpoint

To allow the Sheepishly system to communicate with the world we have a WebAPI configured to run as a stateless service. My initial introduction had a post about setting up the WebAPI endpoint which was based on excellent posts from the Azure documentation (which has been updated to reflect the changes I am talking about here).

After upgrading we need to do a few changes to the setup to make sure it still works. First of all the OwinCommunicationListener needs to support the new StatelessContext, rather than the old InitilizationParameters, changing this:

public OwinCommunicationListener(string appRoot, IOwinAppBuilder startup, ServiceInitializationParameters serviceInitializationParameters)

To this:

public OwinCommunicationListener(string appRoot, IOwinAppBuilder startup, StatelessServiceContext context)

Next we need to update CreateServiceInstanceListeners to support the change. From this:

return new[] {
    new ServiceInstanceListener(initParams => new OwinCommunicationListener("api", new Startup(), initParams))
};

To something like this:

return Context.CodePackageActivationContext.GetEndpoints()
    .Where(endpoint => endpoint.Protocol.Equals(EndpointProtocol.Http) || endpoint.Protocol.Equals(EndpointProtocol.Https))
    .Select(endpoint => new ServiceInstanceListener(serviceContext => new OwinCommunicationListener("api", new Startup(), serviceContext)));

That's it

With the changes above the application is out of preview, and running using the most recent libraries!

View Comments