DISCLAIMER: This post is purely a personal opinion, not representing or affiliating my employer’s.
UPDATE (May 9, 2019): During the //Build event, Jeff Hollan officially announced this dependency injection. Here's the official document: https://docs.microsoft.com/en-us/azure/azure-functions/functions-dotnet-dependency-injection.
UPDATE (Marth 28, 2019): As of the current runtime version,
2.0.12353.0, this constructor injection method has been pulled off for stabilisation purpose. Here's the conversation between myself and Jeff Hollan from Azure Functions Team.
OH NOOOOO @AzureFunctions has pulled off the constructor injection feature... or changed the way of doing. I should figure that out.— Justin Yoo 🎙 #NHNForward (@justinchronicle) March 28, 2019
I think it broke and we are fixing - know we are making last changes and improvements. Not quite sure on what you’re seeing. It’s not quite publically documented yet but in the final stretch— Jeff Hollan (@jeffhollan) March 28, 2019
Err OK. I should wait until it’s publicly announced. I was too early... 😅 Can I expect it at the //Build event?— Justin Yoo 🎙 #NHNForward (@justinchronicle) March 28, 2019
It may not be. I know there were some bugs that were mentioned earlier but possible were caught before rolling out. In regards to us putting final touches on DI. Also plan to expose function specific builder interfaces— Jeff Hollan (@jeffhollan) March 28, 2019
In January 2019, Azure Functions Team has released a new version of its runtime,
I wasn't able to believe what that meant at the first place.
Does that mean we now can get rid of the infamous
staticmodifier from both classes and methods?
In fact, Fabio from Azure Functions Team showed a demo at Ignite 2018, so I wasn't that surprised at all but thought it would be a matter of time. Rather, I was more excited at the new beginning of Azure Functions runtime.
Maybe it was just me who didn't pay too much attention on this. But, throughout this post, I'm going to walk through how we can chuck out the
static modifier from the Functions code, and use a constructor for dependency injection.
The sample code used in this post can be found at here.
Writing Instance Method
In C#, classes, fields, properties or methods can have the
static modifiers. If we put this, regardless the class is instantiated or not, we can directly access to those fields, properties or methods. On the other hand, without the
static modifier, we can access to them only after the class is instantiated. We call the method of the instance as
Instance Method. Constructors can only be useful when we define a class without the
static modifier, with regards to dependency injections.
The problem that Azure Functions has been keeping so far is that Function classes always comes with the
static modifier. That has forced each method in the class to have the same
static modifier as well. This brought about a lot of headaches when considering dependency injections and, in order to sort out this issue, either property injections using a service locator or method injections using custom binding extensions was introduced and these were, in general, not very recommended unless necessary.
Now, the new Azure Functions runtime enables to get rid of the
static modifier. Let's have a look at the code below:
Does it look different? Maybe not. But, when you closely look at the class definition – between
class – and method definition – between
public async and
Task<IActionResult>, there's no
static modifier any more. Wow, how does this even work? Let's run the Function app. If it runs properly, it should stop at the debugging break point:
And its result will look like
It really is the instance method! This brings up massive implication that we can inject dependencies through constructors!
Injecting Dependencies via Property
If you use the library, Aliencube.AzureFunctions.Extensions.DependencyInjection, property injection can be used. I wrote a blog post around this property injection a while ago.
static property implements the
FunctionFactory class through the
IFunctionFactory interface and it accepts
AppModule instance as its dependency, which registers all dependencies. The
AppModule class actually looks like this:
GetSamplesFunction instance is registered as
IGetSamplesFunction and the function method invokes it. Now, let's keep the same structure but just remove the
Injecting Dependencies via Constructor
The approach that new Azure Functions runtime takes is to use
StartUp class, which is similar to what ASP.NET Core app does. There's no longer
AppModule necessary. Let's have a look at the code below:
StartUp class implements the
IWebJobStartup interface, which only defines one method,
Configure(IWebjobBuilder builder). In addition to that, it uses the decorator,
AttributeTargets.Assembly, which registers dependencies as a part of starting up the host runtime. Let's see how dependencies are registered through the constructor.
The function method still keeps the existing structure, but substitutes the existing
static property of
IFunctionFactory with the constructor so that we can minimise the changes on the existing code-base.
Unit Testing with Constructor Injection
Now, we know Azure Function allows constructor injection. Why does this really matter? Let's think of unit testing code. Even before the constructor injection being enabled, we were able to perform unit testing, but it was pretty ugly. Now, we have a constructor, which means the same unit testing can be done in more beautiful way. Let's have a look at the code below:
SampleHttpTrigger class doesn't have the
static modifier any more, and it accepts a dependency through the constructor, we just simply mock the dependency,
IGetSamplesFunction here in this example. This is not different from any other unit testing approach at all. Can you see how easy the unit testing is now?
Azure Functions Keeps Growing!
So far, we've walked through how to use constructor injection for Azure Functions, without using the
static modifier. Like Azure WebJobs, Azure Functions, that works very well at the low level like replacing very simple workload, has now evolved to provide more sophisticated features and better development experiences. It is now 1) testable by proper dependency injection that has been dealt in this post, 2) deployable by providing container packaging, and 3) discoverable by Open API adoption. With these three aspects, I'm expecting there will be more use cases that we have never seen before.
This was originally posted at Platform Engineering Blog.