Professional Real World Azure Functions

2021, Dec 21

Microsoft Docs are great, they offer such detailed information and code examples about almost everything nowadays (Microsoft Product/Service related). Microsoft has understood that the constant changes of products/services required another level of documentation, to produce content to guide us, the hungry community.

Depending on what you are doing, sometimes getting some code examples directly from Microsoft Docs and using in Production systems are ok, but this is not always the case. As professionals, we should always understand the functional requirements we are trying to achieve, and also the non-functional requirements (NFR). Regardless of the Business requirements, there are some non-negotiables in my opinion you need to cover in your Azure Functions.

If you need a better understanding of Functional and Non-Functional requirements (NFR), check these links: https://en.wikipedia.org/wiki/Functional_requirement and https://en.wikipedia.org/wiki/Non-functional_requirement

How to create a Professional Real World Azure Function?

Because I have worked in so many applications, it seems almost second nature to me to look at systems and quickly identify what is there or not (missing). Some of the non-negotiables I mentioned, that must be there for me in every single solution:

1 - Security

Secrets are stored in Azure KeyVault, and consumed by the Azure Function.

The secrets should be set against a variable in your Function Application Settings:

@Microsoft.KeyVault(SecretUri=https://myvault.vault.azure.net/secrets/mysecret/)

or

@Microsoft.KeyVault(VaultName=myvault;SecretName=mysecret)

Reference: https://docs.microsoft.com/en-us/azure/app-service/app-service-key-vault-references?WT.mc_id=AZ-MVP-5005172

2 - Metrics/Logging

Performance metrics and behaviour of systems are covered with Application Insights.

For a function app to send data to Application Insights, it needs to know the instrumentation key of an Application Insights resource. The key must be in an app setting named. Available in your Function Application Settings:

APPINSIGHTS_INSTRUMENTATIONKEY

The other factor to consider is the Telemetry volume. Depending on how the telemetry is going to be consumed, real-time dashboards, alerting, detailed diagnostics, and so on, you will need to define a strategy to enable the level of detail needed or to reduce the volume of data generated. This can be done by tuning the Categories and Log Levels of host.json.

Reference: https://docs.microsoft.com/en-us/azure/azure-functions/configure-monitoring?WT.mc_id=AZ-MVP-5005172&tabs=v2#configure-log-levels

3 - Testing and testability

Dependency Injection is implemented to decouple dependencies and allow for easier Unit Testing.

This is a .NET class library (In-process)1 Function example. The custom dependency injection model doesn't apply to .NET Isolated functions (Out-of-process)2.

Any business logic or service integration should be handled by different layers. In this example, we use a generic EmailService service.

namespace DependencyInjectionService
{
    public interface IEmailService
    {
        void SendEmail(string emailTo, string subject, string body);
    }

    public class EmailService : IEmailService
    {
        public void SendEmail(string emailTo, string subject, string body)
        {
            // TODO: Send Email
        }
    }
}

Within the Function, there needs to be a Startup that will configure the dependencies.

using DependencyInjectionService;
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;

[assembly: FunctionsStartup(typeof(DependencyInjectionFunctionSample.Startup))]
namespace DependencyInjectionFunctionSample
{
    public class Startup : FunctionsStartup
    {
        public override void Configure(IFunctionsHostBuilder builder)
        {
            builder.Services.AddSingleton<IEmailService>((s) => new EmailService());
        }
    }
}

And the Function, in this case, a HTTP Trigger Function, needs to have it initialized on the constructor, so it can be consumed by any function methods.

using System.IO;
using System.Threading.Tasks;
using DependencyInjectionService;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;

namespace DependencyInjectionFunctionSample
{
    public class Email
    {
        private readonly IEmailService _emailService;

        public Email(IEmailService emailService)
        {
            _emailService = emailService;
        }

        [FunctionName("SendEmail")]
        public async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req,
            ILogger log)
        {
            log.LogInformation("Send Email function processed a request.");

            var requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            dynamic data = JsonConvert.DeserializeObject(requestBody);
            string email = data?.email;

            _emailService.SendEmail(email, "Subject", "Body");

            return new OkObjectResult("OK");
        }
    }
}

Sample code is available on PlayGoKids repository

Conclusion

Azure Functions are also known as a Function-as-a-Service (FaaS) service, a service that even removes the need to worry about the hosting environment, and because of it, you have inherent Azure Service Features that help you to more easily implement your NFRs.

Microsoft has guidelines on how to design solutions on Azure, the Microsoft Azure Well-Architected Framework, which provides a much more comprehensive, platform level, guidance on how to implement your solution within Microsoft Azure.


  1. In-process .Net class library
  2. Out-of-process .Net Isolated