In this post, I’m going to introduce several design patterns when we write Azure Functions codes in C#, by considering full testabilities.

Sample codes used in this post can be found here.

User Story

  • As a DevOps engineer,
  • I want to search ARM templates using Azure Functions
  • So that I can browse the search result.

Basic Function Code

We assume that we have a basic structure that Azure Functions can use. Here’s a basic function code might look like:

public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, ILogger log)
{
var settings = new FunctionAppSettings();
var client = new HttpClient();
var service = new GitHubService(settings, client);
var query = GetQuery(req);
var models = await service.GetArmTemplateDirectoriesAsync(query)
.ConfigureAwait(false);
return req.CreateResponse(HttpStatusCode.OK, models);
}

As you can see, all dependencies are instantiated and used within the function method. This works perfectly fine so there’s no problem at all. However, from a test point of view, this smells A LOT. How can we refactor this code for testing?
I’m going to introduce several design patterns. They can’t be overlooked, if you’ve been working with OO programming. I’m not going to dive into them too much in this post, by the way.

Service Locator Pattern

As you can see the code above, each and every Azure Functions method has the static modifier. Because of this restriction, we can’t use a normal way of dependency injections but the Service Locator Pattern. With this, the code above can be refactored like this:

public static class GetArmTemplateDirectoriesHttpTriggerWithServiceLocator
{
// Declare a static IServiceLocator property.
public static IServiceLocator ServiceLocator { get; set; } = new ServiceLocatorBuilder().RegisterModule<AppModule>().Build();
public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, ILogger log)
{
// Resolve the service instance through the service locator property.
var service = ServiceLocator.GetInstance<IGitHubService>();
var query = GetQuery(req);
var models = await service.GetArmTemplateDirectoriesAsync(query)
.ConfigureAwait(false);
return req.CreateResponse(HttpStatusCode.OK, models);
}
}

First of all, the function code declares the IServiceLocator property with the static modifier. Then, the Run() method calls the service locator to resolve an instance (or dependency). Now, the method is fully testable.

Here’s a simple unit test code for it:

[Theory]
[InlineData(HttpStatusCode.OK)]
public async void Given_Instance_Run_ShouldReturn_Response(HttpStatusCode statusCode)
{
var models = new[] { new ContentModel() }.ToList();
var service = new Mock<IGitHubService>();
service.Setup(p => p.GetArmTemplateDirectoriesAsync(It.IsAny<string>())).ReturnsAsync(models);
// Create a mocked IServiceLocator instance.
var locator = new Mock<IServiceLocator>();
locator.Setup(p => p.GetInstance<IGitHubService>()).Returns(service.Object);
// Inject the mocked IServiceLocator instance.
GetArmTemplateDirectoriesHttpTriggerWithServiceLocator.ServiceLocator = locator.Object;
var config = new HttpConfiguration() { Formatters = { new JsonMediaTypeFormatter() } };
var context = new HttpRequestContext() { Configuration = config };
var req = new HttpRequestMessage() { Properties = { { HttpPropertyKeys.RequestContextKey, context } } };
var log = new Mock<ILogger>();
// Run the test.
var res = await GetArmTemplateDirectoriesHttpTriggerWithServiceLocator.Run(req, log.Object).ConfigureAwait(false);
res.StatusCode.Should().Be(statusCode);
}

The Mock instance is created and injected to the static property. This is the basic way of dependency injection for Azure Functions. However, there’s still an issue on the service locator pattern.
Mark Seemann argues on his blog post that the service locator pattern is an anti pattern because we can’t guarantee whether dependencies that we want to use have already been registered or not. Let’s have a look at the refactored code again:

public static class GetArmTemplateDirectoriesHttpTriggerWithServiceLocator
{
// Declare a static IServiceLocator property.
public static IServiceLocator ServiceLocator { get; set; } = new ServiceLocatorBuilder().RegisterModule<AppModule>().Build();
public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, ILogger log)
{
// Resolve the service instance through the service locator property.
var service = ServiceLocator.GetInstance<IGitHubService>();
var query = GetQuery(req);
var models = await service.GetArmTemplateDirectoriesAsync(query)
.ConfigureAwait(false);
return req.CreateResponse(HttpStatusCode.OK, models);
}
}

Within the Run() method, the IGitHubService instance is resolved by this line:
var service = ServiceLocator.GetInstance();
When the amount of codes is relatively small, that wouldn’t be an issue. However, if the application grows up quickly and has become a fairly large one, can we ensure if the service variable is always NOT null? I don’t think so. We have to use the service locator pattern, but at the same time, try not to use it. That doesn’t sound quite right, though. How can we achieve this goal?

Strategy Pattern

Strategy Pattern is basically one of the most popular design patterns and can be applied to all Functions codes. Let’s start from the IFunction interface.

public interface IFunction : IDisposable
{
IServiceLocator ServiceLocator { get; set; }
Task<HttpResponseMessage> InvokeAsync<TOptions>(HttpRequestMessage req, TOptions options = default(TOptions));
}

Every function now implements the IFunction interface and all logics written in a trigger function MUST move into the InvokeAsync() method. Now, the FunctionBase abstract class implements the interface.

public abstract class FunctionBase : IFunction
{
public IServiceLocator ServiceLocator { get; set; }
public virtual Task<HttpResponseMessage> InvokeAsync<TOptions>(HttpRequestMessage req, TOptions options = default(TOptions))
{
return null;
}
}

While implementing the IFunction interface, the FunctionBase class add the virtual modifier to the InvokeAsync() method so that other functions class inheriting this base class will override the method. Let’s put the real logic.

public interface IGetArmTemplateDirectoriesFunction : IFunction
{
}
public class GetArmTemplateDirectoriesFunction : FunctionBase, IGetArmTemplateDirectoriesFunction
{
private readonly IGitHubService _gitHubService;
public GetArmTemplateDirectoriesFunction(IGitHubService gitHubService)
{
this._gitHubService = gitHubService.ThrowIfNullOrDefault();
}
public override async Task<HttpResponseMessage> InvokeAsync<TOptions>(HttpRequestMessage req, TOptions options = default(TOptions))
{
var @params = options as GetArmTemplateDirectoriesFunctionParameterOptions;
var directories = await this._gitHubService.GetArmTemplateDirectoriesAsync(@params.Query).ConfigureAwait(false);
return this.CreateOkResponse(req, directories);
}
}

The GetArmTemplateDirectoriesFunction class overrides the InvokeAsync() method and processes everything within the method. Dead simple. By the way, what is the generic type of TOptions? This is the Options Pattern that we deal with right after this section.

Options Pattern

Many API endpoints have route variables. For example, if an HTTP trigger’s endpoint URL is /api/products/{productId}, the productId value keeps changing and the value can be handled by the function method like Run(HttpRequestMessage req, int productId, ILogger log). In other words, route variables are passed through parameters of the Run() method. Of course each function has different number of route variables. If this is the case, we can pass those route variables, productId for example, through an options instance. Code will be much simpler.

public abstract class FunctionParameterOptions
{
}
public class GetArmTemplateDirectoriesFunctionParameterOptions : FunctionParameterOptions
{
public string Query { get; set; }
}

There is an abstract class of FunctionParameterOptions. The GetArmTemplateDirectoriesFunctionParameterOptions class inherits it, and contains a property of Query. This property can be used to store a querystring value for now. The InvokeAsync() method now only cares for the options instance, instead of individual route variables.

Builder Pattern

Let’s have a look at the function code again. The IGitHubService instance is injected through a constructor.

public interface IGetArmTemplateDirectoriesFunction : IFunction
{
}
public class GetArmTemplateDirectoriesFunction : FunctionBase, IGetArmTemplateDirectoriesFunction
{
private readonly IGitHubService _gitHubService;
public GetArmTemplateDirectoriesFunction(IGitHubService gitHubService)
{
this._gitHubService = gitHubService.ThrowIfNullOrDefault();
}
public override async Task<HttpResponseMessage> InvokeAsync<TOptions>(HttpRequestMessage req, TOptions options = default(TOptions))
{
var @params = options as GetArmTemplateDirectoriesFunctionParameterOptions;
var directories = await this._gitHubService.GetArmTemplateDirectoriesAsync(@params.Query).ConfigureAwait(false);
return this.CreateOkResponse(req, directories);
}
}

Those dependencies should be managed somewhere else, like an IoC container. Autofac and/or other IoC container libraries are good examples and they need to be combined with a service locator. If we introduce a Builder Pattern, it would be easily accomplished. Here’s a sample code:

public interface IServiceLocatorBuilder : IDisposable
{
IServiceLocator Build();
IServiceLocatorBuilder RegisterModule<TModule>(RegistrationHandler handler = null) where TModule : IModule, new();
}
public class ServiceLocatorBuilder : IServiceLocatorBuilder
{
private ContainerBuilder _containerBuilder;
public ServiceLocatorBuilder()
{
this._containerBuilder = new ContainerBuilder();
}
public IServiceLocator Build()
{
var container = this._containerBuilder.Build();
return new AutofacServiceLocator(container);
}
public IServiceLocatorBuilder RegisterModule<TModule>(RegistrationHandler handler = null) where TModule : IModule, new()
{
this._containerBuilder.RegisterModule<TModule>();
if (handler.IsNullOrDefault())
{
return this;
}
if (handler.RegisterTypeAsInstancePerDependency.IsNullOrDefault())
{
return this;
}
handler.RegisterTypeAsInstancePerDependency.Invoke(this._containerBuilder);
return this;
}
}

With Autofac, all dependencies are registered within the RegisterModule() method and the container is wrapped with AutofacServiceLocator and exported to IServiceLocator within the Build() method.

Factory Method Pattern

All good now. Let’s combine altogether, using the Factory Method Pattern.

public class FunctionFactory
{
public FunctionFactory()
: this(null)
{
}
public FunctionFactory(RegistrationHandler handler = null)
{
this.ServiceLocator = new ServiceLocatorBuilder().RegisterModule<AppModule>(handler).Build();
}
public virtual IServiceLocator ServiceLocator { get; set; }
public virtual TFunction Create<TFunction>(ILogger log)
where TFunction : IFunction
{
var function = this.ServiceLocator.GetInstance<TFunction>();
function.Log = log;
function.ServiceLocator = this.ServiceLocator;
return function;
}
}

Within the constructor, all dependencies are registered by the ServiceLocatorBuilder instance. This includes all function classes that implement the IFuction interface. FunctionFactory has the Create() method and it encapsulates the service locator.

public static class GetArmTemplateDirectoriesHttpTrigger
{
public static FunctionFactory FunctionFactory { get; set; } = new FunctionFactory();
public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, ILogger log)
{
var options = new GetArmTemplateDirectoriesFunctionParameterOptions() { Query = GetQuery(req) };
var res = await FunctionFactory.Create<IGetArmTemplateDirectoriesFunction>(log)
.InvokeAsync(req, options)
.ConfigureAwait(false);
return res;
}
}

This function now has been refactored again. The IServiceLocator property is replaced with the FunctionFactory property. Then, the Run method calls the Create() method and InvokeAsync() method consecutively by fluent design approach. At the same time, GetArmTemplateDirectoriesFunctionParameterOptions is instantiated and passed the querystring value, which is injected to the InvokeAsync() method.
Now, we’ve got all function triggers and its dependencies are fully decoupled and unit-testable. It has also achieved to encapsulates the service locator so that we don’t even care about that any longer. From now on, code coverages have gone up to 100%, if TDD is properly applied.


So far, I’ve briefly introduced several useful design patterns to test Azure Functions codes. Those patterns are used for everyday development. This might not be the perfect solution, but I’m sure this could be one of the best practices I’ve found so far. If you have a better approach, please send a PR to the GitHub repository.
Azure Logic Apps will be touched in the next post.

Category:
Application Development and Integration
Tags:
, , ,

Join the conversation! 3 Comments

  1. For the last X number of years, we’ve been told that Service Locator is a horrible anti-pattern. Now it’s the answer to working with Azure Functions. It’s not credible.

    • @ericbl Thanks for your comment!
      Well, I agree that service locator is still an anti-pattern because of the reason stated in this post. If there is a better way to manage dependencies without relying on service locator, I would take that approach.

  2. I would have a look at Boris Wilhelm’s implementation that leverages the custom binding functionality in v2.0. It allows you to specify dependencies as function arguments. It uses the built-in DI stuff in .Net Core rather than service location.

    Linky: https://github.com/BorisWilhelms/azure-function-dependency-injection

Comments are closed.