I've been talking about managing dependencies and unit testing in Azure Functions quite a few times in those articles:
- Is Your Serverless Application Testable? – Azure Functions
- Precompiled Azure Functions Revisited
- Testing Precompiled Azure Functions
- Debugging Azure Functions in Our Local Box
- Managing Dependencies in Azure Functions
- Testing Azure Functions in Emulated Environment with ScriptCs
Throughout my articles, the service locator pattern always took the centre of dependency management. The combination of Common Service Locator and Autofac certainly convinced me this would be the only way to handle dependencies for Azure Functions.
A few weeks back, I was asked to take a coding test before being engaged with a client. The topic was simple – given a JSON payload as a source of truth, I need to build an application to process the payload to display an instructed result. I, of course, decided to use Azure Functions to fulfill their requirements. Because the test itself was pretty straight forward, it could be done within a couple of hours with full of spaghetti code. However, they also wanted a sort of over-engineering including dependency injections, SOLID principles, unit testing, etc.
So, I started writing an Azure Functions application for it. As soon as I started, I realised that:
"Why can't I upgrade my Autofac version? Is it because of the common service locator locks-in the Autofac version?"
This means that Azure Functions doesn't yet support assembly binding redirects out-of-the-box. Apparently, it's possible for libraries used internally. However, this is not applied to my case, if my Azure Functions app has dependencies that need binding redirects. Even though, there is an workaround for this concern, I was reluctant using this approach for Autofac.
What if I can use Autofac directly, without relying on Common Service Locator? Can I do this? It would be worth trying, yeah? Let's move on.
Here's my coding test repository as an example.
ServiceLocatorBuilder
No More In my previous post, I introduced ServiceLocatorBuilder
for Autofac integration like:
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; | |
} | |
} |
This was called within FunctionFactory
like:
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; | |
} | |
} |
It seemed to be redundant. I wasn't happy about that, but justified myself this would be the only way to do it. Now this is the time to rewrite. Let's do it.
FunctionFactory
New In the constructor of the new FunctionFactory
class, instantiate the Autofac.IContainer
instance directly from Autofac.ContainerBuilder.ContainerBuilder
instance. Then, within the Create<TFunction>()
method, the given type of function instance is resolved directly from the Autofac.IContainer
instance. Here's the code.
public class FunctionFactory : IFunctionFactory | |
{ | |
private readonly IContainer _container; | |
public FunctionFactory(RegistrationHandler handler = null) | |
{ | |
// Create the Autofac container directly from | |
// Autofac container builder. | |
this._container = new ContainerBuilder() | |
.RegisterModule<AppModule>(handler) | |
.Build(); | |
} | |
public TFunction Create<TFunction>(TraceWriter log) | |
where TFunction : IFunction | |
{ | |
// Resolve the function instance directly from the container. | |
var function = this._container.Resolve<TFunction>(); | |
function.Log = log; | |
return function; | |
} | |
} |
FunctionFactory
HttpTrigger with The basic usage is the same as the previous version of FunctionFactory
. Simply create an instance and use it within the function method.
public static class IndexHttpTrigger | |
{ | |
public static IFunctionFactory FunctionFactory { get; set; } = new FunctionFactory(); | |
[FunctionName("IndexHttpTrigger")] | |
public static async Task<HttpResponseMessage> Run( | |
[HttpTrigger(AuthorizationLevel.Function, "get", Route = "pets")]HttpRequestMessage req, | |
TraceWriter log) | |
{ | |
AglCodingTestHttpTriggerFunctionOptions options = GetOptions(req); | |
try | |
{ | |
var res = await FunctionFactory.Create<IAglCodingTestHttpTriggerFunction>(log) | |
.InvokeAsync(req, options) | |
.ConfigureAwait(false); | |
return res as HttpResponseMessage; | |
} | |
catch (Exception ex) | |
{ | |
return req.CreateErrorResponse(HttpStatusCode.InternalServerError, ex); | |
} | |
} | |
} |
With this approach, you don't need to use service locator pattern any longer for your dependency management. Hope this helps.