Developing a Service Fabric service using another host

Service Fabric provides you with ways to setup high quality scalable microservices, but also comes with some challenges. One of which is setting up your local development environment. With this, you may very well run into some issues.

One of the problems I’ve run into, as well as some colleagues, is that a system running a lot of Service Fabric services can become slow. This is because those services require quite some resources. Another is that the feedback loop can be very long, because the deployment of an update to your service is time consuming.

So wouldn’t it be nice if you could develop Service Fabric services, without the necessity to have Service Fabric running on your system? Would such a setup be possible? Luckily the answer to that is yes. In this post I’ll take you through the setup, as well as some challenges you may run into and how to tackle them.

Setting it up

Let’s start with the basics. After you’ve created a new Service Fabric project with service, your solution will look something like this:

Service Fabric Solution
It contains a Service Fabric project, as well as a project with the service itself. After some time the solution will probably grow and have more projects, but that doesn’t change the approach we’re going to take.

As it stands, when you run the Service Fabric service, it hosts your service on your local Service Fabric installation. You could change this to just host it some other way, but I’ll show you how you can leave the choice to the developer. After all, there will always be cases where you do want to use Service Fabric locally, so it doesn’t make sense to cut out that possibility. The goal of this setup is to be able to select your service project (MyService in the example above) as startup project in order to host it without Service Fabric.

To accomplish this, we need to look at the project’s Program.cs file. We’re going to add some code to the main method to choose the host:

var appName = Environment.GetEnvironmentVariable("Fabric_ApplicationName");

if (appName == null)
{
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup<Startup>())
        .Build()
        .Run();
}

This extra bit of code makes use of the environment variable Fabric_ApplicationName that Service Fabric sets. This is our way to see which startup project the developer has selected, as the variable will only have a value if that’s the Service Fabric project (MyServiceFabricApp in the example). So if the variable is null, we implement our WebHost. This WebHost is just one possibility; you can also implement another form of hosting here if you want. Our complete main method should now look something like this:

private static void Main(string[] args)
{
    try
    {
        var appName = Environment.GetEnvironmentVariable("Fabric_ApplicationName");

        if (appName == null)
        {
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup<Startup>())
                .Build()
                .Run();
        }
        else
        {
            // The ServiceManifest.XML file defines one or more service type names.
            // Registering a service maps a service type name to a .NET type.
            // When Service Fabric creates an instance of this service type,
            // an instance of the class is created in this host process.

            ServiceRuntime.RegisterServiceAsync("MyServiceType",
                context => new MyService(context)).GetAwaiter().GetResult();

            ServiceEventSource.Current.ServiceTypeRegistered(Process.GetCurrentProcess().Id, typeof(MyService).Name);

            // Prevents this host process from terminating so services keeps running. 
            Thread.Sleep(Timeout.Infinite);
        }
    }
    catch (Exception e)
    {
        ServiceEventSource.Current.ServiceHostInitializationFailed(e.ToString());
        throw;
    }
}

This is really all you need to do to be able to develop the service without running Service Fabric on your system. But although this will work without problems, there’s still some challenges regarding integration.

Dealing with application parameters

The most common question that pops up after you’ve set this up is: “What about you application parameters?”. With Service Fabric those parameters are usually provided through the Service Fabric project, but as we’re now running with the service itself as startup project, that will no longer work.

One way to solve this is to make use of environment parameters, but it’s probably better to stay a little bit closer to what developers are used to. You can use your appsettings.json file to provide your application settings. This way we’re not using the Service Fabric application parameters anymore, and use this alternative for all or our environments. Out-of-the-box this solution comes with the exact opposite problem: it works with the service itself as startup, but not when running with Service Fabric. That’s something we can solve though.

To accomplish this, we’re first going to create the following extension method in our MyService project:

public static class WebHostBuilderExtensions
{
    public static IWebHostBuilder ConfigureAppSettings(this IWebHostBuilder builder)
    {
        builder.ConfigureAppConfiguration((hostingContext, configuration) =>
        {
            var environment = hostingContext.HostingEnvironment;

            configuration.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
            .AddJsonFile($" appsettings.{environment.EnvironmentName}.json", optional: true, reloadOnChange: true);

            if (environment.IsDevelopment())
            {
                var appAssembly = Assembly.Load(new AssemblyName(environment.ApplicationName));
                if (appAssembly != null)
                {
                    configuration.AddUserSecrets(appAssembly, optional: true);
                }
            }

            configuration.AddEnvironmentVariables();
        });

        return builder;
    }
}

This code makes sure the appsettings.json file will be used, but also does some other things:

  • Taking the environment you’re running on into consideration and loading the corresponding *.json file
  • Adding user secrets, to make it possible to use developer specific settings (think of things like a personal password for a local resource)
  • Adding environment variables, so we can continue using them where needed

We’re almost done now. The only thing left to do is actually calling the extension method. For that, we’ll add a line to our MyService.cs file in the CreateServiceInstanceListeners method:

protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
{
    return new ServiceInstanceListener[]
    {
                new ServiceInstanceListener(serviceContext =>
                    new KestrelCommunicationListener(serviceContext, "ServiceEndpoint", (url, listener) =>
                    {
                        ServiceEventSource.Current.ServiceMessage(serviceContext, $"Starting Kestrel on {url}");

                        return new WebHostBuilder()
                                    .UseKestrel()
                                    .ConfigureAppSettings()
                                    .ConfigureServices(
                                        services => services
                                            .AddSingleton<StatelessServiceContext>(serviceContext))
                                    .UseContentRoot(Directory.GetCurrentDirectory())
                                    .UseStartup<Startup>()
                                    .UseServiceFabricIntegration(listener, ServiceFabricIntegrationOptions.None)
                                    .UseUrls(url)
                                    .Build();
                    }))
    };
}

The only thing we did here is calling our ConfigureAppSettings method we’ve just created. And that’s all you need to do. From this moment on you can use your appsettings parameters, no matter from which context you’re running your application.

A fully working example solution which has all of the above implemented, as well as some examples in working with configuration parameters, can be found on GitHub: timvandelockand/ServiceFabricWebHost (github.com)

Some other challenges

Once you’ve got this all working, you may start to wonder about the bigger picture. How to connect your frontend if there’s two ways of hosting the backend? or How to deal with multiple Service Fabric services from different solutions?

Those will be things I’ll dive into in my next post, as well as answering some common questions I may get in the meantime.