Skip to content

daulet/Indigo.Functions

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

84 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Indigo Functions

Write better Azure Functions

runtime license

This project aims at increasing usabiltiy of Azure Functions in real life applications with usage of custom input and output bindings. Azure Functions come with built in support for some triggers, inputs and outputs, mainly for Azure services like Cosmos DB, Azure Storage, Event Grid, Microsoft Graph etc. However, mature applications require more than just that: some sort of dependency injection for testability purposes; use of non-Azure services, like Redis; configurable parameters that are not hardcoded into the function. Custom input and output bindings provided by this project solve these problems in native Azure Functions way.

Binding Purpose Sample Nuget
[Config("key")] Configuration via Application Settings ConfigurationFunction Nuget version
[Inject] Dependency Injection with Autofac AutofacFunction Nuget version
[Inject] Dependency Injection with built-in .NET Core container InjectionFunction Nuget version
[Inject] Dependency Injection with Unity containers UnityFunction Nuget version
[Redis("key")] Redis input and output with POCO support RedisFunction Nuget version

Dependency Injection

Use [Inject] attribute to inject all your dependencies in Azure Function declaration.

[FunctionName("Example")]
public static IActionResult Run(
    [HttpTrigger("GET")] HttpRequest request,
    [Inject] IStorageAccess storageAccess)
{
    ...
}

Microsoft.Extensions.Configuration.IConfiguration instance is pre-registered for your convinience that you can use to read settings from local settings file or application settings depending on whether you running locally or on Azure respectively. In addition, Microsoft.Extensions.Logging.ILogger instance is also pre-registered for you to log to file system and App Insights. Just declare it as dependency in your implementation class anywhere in your dependency tree.

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging; 

public class ValueProvider
{
    public ValueProvider(IConfiguration configuration, ILogger logger)
    {
        _configuration = configuration;
        _logger = logger;
    }

    public string ReadSetting(string settingName)
    {
        _logger.LogInformation($"Reading value of '{settingName}'");

        return _configuration[settingName];
    }

    ...
}

Supported IoC containers:

Autofac

Nuget version Nuget downloads

Create implementation of IDependencyConfig interface (public visibility) in your function's binary:

public class DependencyConfig : IDependencyConfig
{
    public void RegisterComponents(ContainerBuilder builder)
    {
        builder
            .RegisterType<StorageAccess>()
            .As<IStorageAccess>();
    }
}

For further details see working sample or function declarations in tests.

ServiceCollection

Nuget version Nuget downloads

Register all your dependencies in Startup class:

[assembly: WebJobsStartup(typeof(InjectionFunctionSample.Startup))]
namespace InjectionFunctionSample
{
    public class Startup : IWebJobsStartup
    {
        public void Configure(IWebJobsBuilder builder)
        {
            builder.Services.AddSingleton<ICache, CacheProvider>();
            builder.Services.AddTransient<ICacheConfigProvider, CacheConfigProvider>();
            builder.Services.AddTransient<IStorageAccess, StorageAccess>();
            builder.Services.AddTransient<ITableAccess, CloudTableAccess>();
        }
    }
}

For further details see working sample or function declarations in tests. For details on how to use ASP.NET Core's ServiceCollection see official guide.

Unity

Nuget version Nuget downloads

Create implementation of IDependencyConfig interface (public visibility) in your function's binary:

public class DependencyInjectionConfig : IDependencyConfig
{
    public void RegisterComponents(UnityContainer container)
    {
        container.RegisterType<IStorageAccess, StorageAccess>();
    }
}

For further details see working sample or function declarations in tests.

FAQ

  • What if I need multiple containers for my application?

    Azure Functions or any Function as a Service is a culmination of decades long effort towards reducing deployment, but more importatnly maintenance complexity by breaking down a monolith into applications to individual functions. So use it right, and separate your other function that needs a different container into a separate binary.

Configuration

Nuget version Nuget downloads

Some applications might have pre-production environments that require different set of parameters (settings) to be fed into your application, e.g. integration tests might have more aggressive timeouts or different integration URL for external service.

[FunctionName("ConfigFunctionExample")]
public static IActionResult Run(
    [HttpTrigger("GET")] HttpRequest request,
    [Config("StringSetting")] string stringValue,
    [Config("IntSetting")] int intValue,
    [Config("TimeSpanSetting")] TimeSpan timeSpanValue)
{
    ...
}

Here is a working sample. The binding supports simple types and string. In addition, it supports structs like DateTime, DateTimeOffset, Guid and TimeSpan. A full list of supported types can be found in integration tests.

Redis

Nuget version NuGet downloads

[Redis] binding enables reading Redis strings:

[FunctionName("GetCachedString")]
public static IActionResult GetString(
    [HttpTrigger("GET", Route = "cache/{key}")] HttpRequest request,
    [Redis(Key = "{key}")] string cachedValue)
{
    return new OkObjectResult(cachedValue);
}

OR you can deserialize (JSON) string keys into custom objects:

[FunctionName("GetPoco")]
public static IActionResult GetPoco(
    [HttpTrigger("GET", Route = "poco/{key}")] HttpRequest request,
    [Redis(Key = "{key}")] CustomObject cachedValue)
{
    ...
}

public class CustomObject
{
    public int IntegerProperty { get; set; }

    public string StringProperty { get; set; }
}

And of course your can write back to Redis:

[FunctionName("SetPoco")]
public static async Task<IActionResult> SetPoco(
    [HttpTrigger("POST", Route = "poco/{key}")] HttpRequest request,
    [Redis(Key = "{key}")] IAsyncCollector<CustomObject> collector)
{
    string requestBody;
    using (var reader = new StreamReader(request.Body))
    {
        requestBody = reader.ReadToEnd();
        var value = JsonConvert.DeserializeObject<CustomObject>(requestBody);
        await collector.AddAsync(value);
    }
    return new OkObjectResult(requestBody);
}

To configure your Redis connection string set it in RedisConfigurationOptions setting. See working sample or integration tests for full range of functionality.

Real life examples

This project is a consequence of building rehttp service using Azure Functions. I quickly came to realization that in order to build a reliable and maintainable service I was missing DI for unit testability, configurability for intergration testing and Redis POCO to keep my test code clean.

About

Write better Azure Functions

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published