Azure Functions

Making serverless great again!

What do you need?

  • Machine
  • OS
  • Web Server
  • Queue
  • Storage
  • ...

All you need is...

public static void Run(TimerInfo myTimer, ICollector<string> queueItems, TraceWriter log)
{
    List<string> fileNames;

    DownloadFilesFromFtp(out fileNames);
    foreach(var file in fileNames) {
        var content = File.ReadAllLines(fileName);
        var serializedData = JsonConvert.SerializeObject(content);
        queueItems.Add(serializedData);
    }
}
public static void Run(TimerInfo myTimer, ICollector<string> queueItems, TraceWriter log)
{
     queueItems.Add(...);
}

About Me

Blog: http://blog.codenova.pl/

Twitter: @Kamil_Mrzyglod

GitHub: https://github.com/kamil-mrzyglod

StackOverflow: https://stackoverflow.com/users/1874991/kamo

LinkedIn: www.linkedin.com/in/kamil-mrzygłód-31470376

Serverless - what is it?

Source: http://www.commitstrip.com/en/2017/04/26/servers-there-are-no-servers-here/

IaaS, PaaS, SaaS...

http://www.episerver.com/contentassets/c2298831dbc04581ab7a6af1df35dc0d/pizza.jpg

Azure Functions

https://cmatskas.com/content/images/2017/01/functions.jpg

Scenarios

Source: https://azure.microsoft.com/pl-pl/services/functions/

Integrations

Azure CosmosDB

Azure Event Hub

Azure Notification Hub

Azure Storage

Azure Mobile Apps

GitHub

Languages

Hosting Plans

App Service Plan

Consumption Plan

Scaling

Scaling #2

protected virtual async Task<bool> TryAddIfLoadFactorMaxWorker(
    string activityId, 
    IEnumerable<IWorkerInfo> workers, 
    IWorkerInfo manager)
{
    var loadFactorMaxWorker = workers.FirstOrDefault(w => w.LoadFactor == int.MaxValue);
    if (loadFactorMaxWorker != null)
    {
        _tracer.TraceInformation(activityId, loadFactorMaxWorker, "Worker have int.MaxValue loadfactor.");

        await RequestAddWorker(activityId, workers, manager, force: false);

        return true;
    }

    return false;
}

Pricing

METER PRICE FREE GRANT
Execution Time €0.000014/GB-s 400,000 GB-s
Total Executions €0.169 per million executions 1 million executions

Example

Average memory consumption - 128MB(0,125GB)

3 millions of executions(each 1 second)

Consumption cost: 3M-s * 0,125GB = 375 000GB-s(FREE)

So...

Execution cost: 3M * 0,169EUR = 0,507EUR

Total:

0,507EUR

Monitoring

Development

CSX

#r "System.Configuration"
#r "System.Data"

using System.Configuration;
using System.Data.SqlClient;
using System.Threading.Tasks;

public static async Task Run(TimerInfo myTimer, TraceWriter log)
{
    var str = ConfigurationManager.ConnectionStrings["sqldb_connection"].ConnectionString;
    using (SqlConnection conn = new SqlConnection(str))
    {
        conn.Open();
        var text = "UPDATE SalesLT.SalesOrderHeader " + 
                "SET [Status] = 5  WHERE ShipDate < GetDate();";

        using (SqlCommand cmd = new SqlCommand(text, conn))
        {
            // Execute the command and log the # rows affected.
            var rows = await cmd.ExecuteNonQueryAsync();
            log.Info($"{rows} rows were updated");
        }
    }
}

Precompiled functions

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Text.RegularExpressions;
using LicznikNET.vNext.FtpJob;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Host;
using Newtonsoft.Json;

namespace LicznikNET.vNext.EventAggregator
{
    public class EventAggregator
    {
        private const string FtpServerUri = "ftp://some-server.com";
        private const string CurrentDirectory = "D:\\home\\site\\wwwroot\\EventAggregator\\";

        private static readonly Regex Regex = new Regex(@"[0-9]{8}.txt");
        private static TraceWriter _log;
        private static ICollector<string> _queue;

        public static void Run(TimerInfo myTimer, ICollector<string> queueItems, TraceWriter log)
        {
            List<string> fileNames;
            _log = log;
            _queue = queueItems;

            DownloadFilesFromFtp(out fileNames);
            RegisterContentInEventProcessor(fileNames);
        }
    }
}

Precompiled functions #2

using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.WebJobs.Host;

namespace FunctionApp1
{
    public static class Function1
    {
        [FunctionName("HttpTriggerCSharp")]
        public static async Task<HttpResponseMessage> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)]HttpRequestMessage req, TraceWriter log)
        {
            log.Info("C# HTTP trigger function processed a request.");

            // parse query parameter
            string name = req.GetQueryNameValuePairs()
                .FirstOrDefault(q => string.Compare(q.Key, "name", true) == 0)
                .Value;

            // Get request body
            dynamic data = await req.Content.ReadAsAsync<object>();

            // Set name to query string or body data
            name = name ?? data?.name;

            return name == null
                ? req.CreateResponse(HttpStatusCode.BadRequest, "Please pass a name on the query string or in the request body")
                : req.CreateResponse(HttpStatusCode.OK, "Hello " + name);
        }
    }
}

Configuration

{
  "scriptFile": "LicznikNET.vNext.EventAggregator.dll",
  "entryPoint": "LicznikNET.vNext.EventAggregator.EventAggregator.Run",
  "disabled": false,
  "bindings": [
    {
      "name": "myTimer",
      "type": "timerTrigger",
      "direction": "in",
      "schedule": "0 */5 * * * *"
    },
    {
      "type": "queue",
      "name": "queueItems",
      "queueName": "eventAggregator",
      "connection": "StorageConnectionString",
      "direction": "out"
    }
  ]
}

OSS

Dessert

Proxies

{
    "$schema": "http://json.schemastore.org/proxies",
    "proxies": {
        "proxy1": {
            "matchCondition": {
                "methods": [ "GET" ],
                "route": "/api/{test}"
            },
            "responseOverrides": {
                "response.body": "Hello, {test}",
                "response.headers.Content-Type": "text/plain"
            }
        }
    }
}

#############

{
    "$schema": "http://json.schemastore.org/proxies",
    "proxies": {
        "proxy1": {
            "matchCondition": {
                "methods": [ "GET" ],
                "route": "/api/{test}"
            },
            "backendUri": "https://<AnotherApp>.azurewebsites.net/api/<FunctionName>",
            "requestOverrides": {
                "backend.request.headers.Accept": "application/xml",
                "backend.request.headers.x-functions-key": "%ANOTHERAPP_API_KEY%"
            }
        }
    }
}

Durable Functions

#r "Microsoft.Azure.WebJobs.Extensions.DurableTask"
 
public static async Task<long> Run(DurableOrchestrationContext backupContext)
{
    string rootDirectory = backupContext.GetInput<string>();
    if (string.IsNullOrEmpty(rootDirectory))
    {
        rootDirectory = Environment.CurrentDirectory;
    }
 
    string[] files = await backupContext.CallFunctionAsync<string[]>(
        "E2_GetFileList",
        rootDirectory);
 
    var tasks = new Task<long>[files.Length];
    for (int i = 0; i < files.Length; i++)
    {
        tasks[i] = backupContext.CallFunctionAsync<long>(
            "E2_CopyFileToBlob",
            files[i]);
    }
 
    await Task.WhenAll(tasks);
 
    long totalBytes = tasks.Sum(t => t.Result);
    return totalBytes;
}

Resources

https://docs.microsoft.com/en-US/azure/azure-functions/functions-overview

https://martinfowler.com/articles/serverless.html

https://github.com/Azure/Azure-Functions

http://azureserverless.com/blog/

 

https://github.com/Azure/Azure-Functions/wiki/Samples-and-content

Questions?

https://docs.google.com/forms/d/e/1FAIpQLSfA0nB6xJHq-eGmtnCyec8CiqYk4m97XYz72jxKyJVPiVpfbA/viewform?c=0&w=1&includes_info_params=true

deck

By kamil_mrzyglod

deck

  • 517