image

Neste artigo passaremos pela construção de uma função Serverless usando C# e .NET. Explicaremos o WHY of Serverless, mas também aprenderemos a construir, executar e depurar nossas primeiras funções no VS Code.

Se você estiver interessado em como autor de funções em JavaScript, dê uma olhada abaixo, caso contrário, por favor continue lendo.

Veja esta série se você estiver curioso sobre JavaScript e Serverless

https://dev.to/azure/serverless-series-16o8

Antes de construirmos nossas primeiras funções, vamos mencionar o que vamos cobrir neste artigo:

  • Por que sem servidor, perguntar a si mesmo por que você faz o que você faz é fundamental. Existem alguns critérios que tornam o uso de uma função Serverless uma boa escolha.
  • Conceitos principais, Serverless consiste em um conjunto de conceitos que você precisa saber para trabalhar com ele de forma eficiente e geralmente entender o que está acontecendo. Os conceitos mais importantes que você precisa conhecer são chamados de vinculações de entrada/saída.
  • Construindo nossas primeiras funções, aqui abordaremos como criar nossas funções configurando gatilhos, analisar a entrada dos parâmetros de rota/consulta ou de um Corpo postado. Também veremos como podemos definir nossa saída.

Recursos

Por que o Serverless Serverless é um modelo de execução de computação em nuvem.

Ok, isso soa como um punhado. O que queremos dizer com isso?

Simples, a função que escrevemos será executada na Nuvem. Isso significa que não precisamos nos preocupar em alocar uma máquina virtual ou mesmo executar, por exemplo, um servidor web para fazer o código ser executado, que é gerenciado para nós. Também significa que a alocação de recursos é gerenciada para nós.

Parece bom, o que mais?

Uma das principais razões para escolher o Serverless é o custo ou a falta dele. Sem servidor é barato. Você normalmente só paga quando sua função realmente funciona.

Por que é barato?

Suas funções não estão lá o tempo todo, casos deles serão girados como você precisa deles. Isso significa que você pode experimentar algo chamado um começo frio. Isso significa que pode levar algum tempo na primeira vez que uma instância é girada e você pode começar a interagir com ele.

Não sei se isso soa tão bem, e se eu quiser que meus clientes alcancem minha função 24 horas por dia, 7 horas por semana ou pelo menos evitar esse começo frio, e depois?

Se você realmente quer evitar partidas frias há um plano premium

https://docs.microsoft.com/en-us/azure/azure-functions/functions-premium-plan

mas antes de ir para isso, vamos falar sobre qual código realmente executar lá.

Okey?

Sim, todos os tipos de código e lógica de negócios não devem correr lá. Apenas coisas que você faz raramente devem ser executadas como Serverless, como uma função de utilidade ou algum tipo de computação.

Ok, eu entendo, eu acho que eu tenho algum código na minha base de código que eu poderia transformar em Serverless, que não precisa ser executado com tanta frequência. Economizarei dinheiro fazendo isso direito?

Sim, exatamente, raramente o código de execução pode ser feito Serverless e, assim, você economizará uma tonelada de dinheiro.

Isso é tudo?

Bem, há mais, o servidor geralmente é parte de um quadro maior na forma de interagir com a maioria dos recursos da nuvem. Ele pode ser acionado por tudo, desde bancos de dados, até filas para HTTP e também podemos facilmente passar o resultado da computação para outro serviço na Nuvem

Uau, isso soa… realmente grande.

Conceitos principais Ok então mencionamos parte dos conceitos principais um pouco já, mas vamos deixar mais claro o que eram.

Temos os seguintes conceitos:

  • Trigger, isso é o que aciona nossa função para começar. Isso pode ser uma chamada HTTP, um evento levantado ou uma linha inserida em um banco de dados e muito mais
  • A vinculação de entrada, uma vinculação de entrada é uma conexão a um recurso, pode ser coisas como SignalR, Table Storage, CosmosDB e assim por diante. A questão é que não temos que instanciar uma conexão, ela está pré-criada e pronta para interagirmos com ela.
  • Vinculação de saída, isso é simplesmente o que você pode escrever. Você usaria, por exemplo, uma vinculação de entrada para ter uma conexão com um banco de dados e ler uma lista de produtos. Você usaria inversamente uma vinculação ouput para criar uma nova entrada do produto dado dados de entrada para a função

Isso tudo pode soar um pouco exótico será mais claro à medida que aprendemos a criar algumas funções.

Eu recomendo dar uma olhada neste link

https://docs.microsoft.com/en-us/azure/azure-functions/functions-triggers-bindings

para ver o escopo completo de todas as vinculações de entrada/saída suportadas

Construindo nossa primeira função

Ok, chegamos à parte de saída, codificando algumas funções. Antes de começar, precisaremos instalar algumas extensões para tornar nossa experiência de autoria um pouco melhor.

Preparar

Precisaremos do seguinte:

  • As funções do Azure são ferramentas principais
  • Extensão de funções azure para VS Code, busca por . Deve ser assim:Azure Functions

image

  • Porque vamos trabalhar com C# vamos precisar da extensão para ele, deve parecer que sim

image

Vamos codificar

Estamos prontos para codificar. Faremos o seguinte:

  • Gerar um aplicativo de função Azure
  • Crie uma função Azure desencadeada por HTTP

Gerar um aplicativo de função Azure Nossas funções precisarão ser hospedadas dentro de um aplicativo. Um aplicativo pode ter várias funções nele embora. Isso é bastante direto, basta trazer a paleta de comando em VS Code por selecionar ou acertar a combinação de teclas se você estiver em um Mac. Uma vez que você tenha o menu para cima tipo: .View/Command PaletteCOMMAND + SHIFT + PAzure Functions: Create new project

Isso vai pedir- lhe:

  • Diretório, em que diretório armazenar seu aplicativo, selecione seu diretório atual
  • Linguagem de código, ele continuará a pedir-lhe para linguagem de código, selecione C #
  • Gatilho, então ele pedirá o seu gatilho de primeiras funções, selecione HTTP Trigger
  • Nome da função, depois disso ele pedirá o nome da função, digite Hello
  • Namespace, então você será solicitado para um namespace, por enquanto, vá com , você sempre pode alterá-lo mais tardeCompany.Function
  • Direitos de acesso, Quando perguntado sobre Direitos de Acesso, selecione . A segurança é uma grande coisa, mas para este exercício vamos nos concentrar em entender partes, podemos reforçar a segurança mais tarde.Anonymous

Ele também pedirá o nome de sua primeira função, sua linguagem e tipo de gatilho.

Isso vai gerar um projeto para você. No entanto, a partir dele ele vai reclamar tudo em letras vermelhas que há dependências não resolvidas. Você pode corrigir isso clicando no popup que aparece logo após o projeto ser feito ou você pode entrar no seguinte comando no terminal, na raiz do projeto:restore

dotnet restore

Isso restaurará as dependências que foram apontadas no projeto e você logo verá como o código parece feliz novamente.

A anatomia de uma função

Vamos dar uma olhada no arquivo que foi gerado quando criamos nosso aplicativo: Hello.cs

// Hello.cs

using System;
using System.IO;
using System.Threading.Tasks;
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 Company.Function
{
    public static class Hello
    {
        [FunctionName("Hello")]
        public static async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
            ILogger log)
        {
            log.LogInformation("C# HTTP trigger function processed a request.");

            string name = req.Query["name"];

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

            return name != null
                ? (ActionResult)new OkObjectResult($"Hello, {name}")
                : new BadRequestObjectResult("Please pass a name on the query string or in the request body");
        }
    }
}

A maioria das coisas que precisamos saber sobre funções azure em C# está nesse pedaço de código. Vamos passar por isso, desde o início:

  • FunctionName, este é um decorador que leva um argumento de string. É assim que a função será chamada quando depurarmos ou, de alguma forma, tentar testar nosso aplicativo. O argumento de sequência é para que possamos esperar que a URL final para esta função seja .Hello[someUrl]/Hello
  • Run, este é um nome de função arbitrária, poderia ser chamado de qualquer coisa
  • HttpTrigger, este é um decorador decorando nosso parâmetro HttpRequest. Começamos no decorador, definindo o para , isso significa que qualquer pessoa de fora pode acessar a função. Há outras opções sobre as quais vamos explorar em artigos futurosreqAuthorizationLevelAnonymousAuthorizationLevel
  • LISTA HTTP Verbs, Next é uma lista de sequência de parâmetros composta por verbos HTTP. Podemos ver que temos os valores e até agora, o que significa que a função responderá a esses verbos HTTP. Podemos definitivamente estender a lista para dizer, mas no REST você tende a suportar apenas um verbo por tipo de ação.getpost”get”, “post”, “put”, “delete”
  • Padrão de rota, o próximo valor é . Se mudarmos isso, podemos começar a apoiar parâmetros de rota, mostraremos isso mais tarde.Route

Isso é tudo para o parâmetro HttpRequest. Vamos olhar para o tipo . Invocando este, somos capazes de escrever para os registros. Os registros serão visíveis localmente quando você depurar, mas também no portal, uma vez que você tenha implantado seu aplicativo.logILogger

Ok, então, vamos olhar para o corpo de função gerada:

string name = req.Query["name"];

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

return name != null
    ? (ActionResult)new OkObjectResult($"Hello, {name}")
    : new BadRequestObjectResult("Please pass a name on the query string or in the request body");

A primeira linha nos diz como analisar parâmetros de consulta. Usamos o com um argumento de cordas para fazê-lo. Então, por exemplo, se sua URL se parecesse com isso, a variável manteria o valor .Queryurl/Hello?name=’some value’namesome value

Em seguida, lemos o Corpo usando um StreamReader, nota o uso da palavra-chave . Note, como isso é combinado no início da cabeça de função com uma palavra-chave. A próxima linha depois disso é ligar e atribuí-la a um . Na próxima linha, tentamos ler do nosso corpo, desde que o Corpo não fosse nulo. Finalmente retornamos uma resposta com ou usando

.awaitasyncJsonConvert.DeserializeObjectdynamicname200OkObjectResult401BadRequestObjectResult

Execute a função

Em seguida, executaremos a função usando depuração de código VS e experimentaremos tanto o GET quanto o POST. Vá para o menu e escolha Este deve compilar seu aplicativo e iniciar o tempo de execução. Você deve ver o seguinte quando estiver feito:Debug /Start Debugging

image

Vá para o seu navegador e digite a URL indicada:http://localhost:7071/api/hello

image

O código está claramente infeliz conosco, pois não estamos fornecendo um parâmetro de consulta ou postando um corpo. Vamos tentar com um param de consulta para digitarmos e agora diz:?name=chrishttp://localhost:7071/api/hello?name=chris

image

Tudo bem!

Vamos colocar um ponto de interrupção e, em seguida, recarregar o navegador:

image

Como você pode ver acima o ponto de ruptura é atingido e nós somos capazes de inspecionar e depurar nosso código como estamos acostumados, coisas 😃 boas

Nós claramente recebemos a chamada GET para o trabalho, então e o POST? Traga seu cliente favorito para fazer chamadas REST, seja cURL ou Advanced Rest Client, isso é com você. Em seguida, crie um pedido como esse:

image

Então veja como seu ponto de interrupção está sendo atingido, assim:

image

Construa uma API REST

Gostaríamos de fazer um pouco mais do que apenas uma função simples, ou seja, gerar uma API REST. Não usaremos um banco de dados para isso, mas sim uma classe estática que manterá o estado.

Crie um diretório totalmente novo para isso e crie um novo Aplicativo de Função Azure. Quando perguntado o nome de um método escolher e ProductsGetHttpTrigger

Em seguida, crie os seguintes métodos usando a paleta de comando:

  • ProductCreate
  • ProductGet

Para gerar esses métodos trazemos a paleta de comando e desta vez escolhemos . Selecione e nomeie. Neste ponto, seu projeto deve ser assim:Azure Functions: Create FunctionHttpTrigger

image

E agora?

Vamos passar por cada uma das classes geradas em ordem, mas vamos primeiro criar a classe que vai manter nosso banco de dados Db.cs

Criando o banco de dados

Esta é apenas uma classe simples segurando nossos dados, mas em artigos futuros, vamos substituí-los por um banco de dados real.

// Db.cs

using System;
using System.Collections.Generic;
using System.Linq;

namespace Company {
  public class Product
  {
    public int Id { get; set; }
    public string Name { get; set; }
  }
  public static class Db {

    private static List<Product> products = new List<Product>(){
        new Product(){ Id = 1, Name= "Avengers End Game" },
        new Product(){ Id = 2, Name= "Wonder Woman" }
    };

    public static IEnumerable<Product> GetProducts() 
    {
      return products.AsEnumerable();
    }

    public static Product GetProductById(int id)
    {
      return products.Find(p => p.Id == id);
    }

    public static Product CreateProduct(string name)
    {
      var newProduct = new Product(){ Id = products.Count + 1, Name = name}; 
      products.Add(newProduct);
      return newProduct;
    }
  } 
}

Como você pode ver acima, criamos o tipo e os métodos, e .ProductGetProductsGetProductById()CreateProduct()

Lista de Produtos

Isso retornará uma lista de produtos e parece muito simples:

// ProductsGet.cs

using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using System.Threading.Tasks;
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 Company.Function
{    
    public static class ProductsGet
    {
        [FunctionName("ProductsGet")]
        public static async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
            ILogger log)
        {
            log.LogInformation("C# HTTP trigger function processed a request.");

            var json = JsonConvert.SerializeObject(new{
                products = Db.GetProducts()
            });

            return (ActionResult)new OkObjectResult(json);
        }
    }
}

Vale a pena comentar como criamos a resposta:

var json = JsonConvert.SerializeObject(new{
    products = Db.GetProducts()
});

O que fazemos aqui é criar uma resposta JSON parecida com esta:

{
  "products": [/* list of products*/]
}

Também vale a pena notar como limitamos o verbo HTTP permitido apenas .get

Obtenha um produto específico

Agora, trata-se de pedir um produto específico. Faremos isso criando uma rota como esta. A ideia é que encontremos um determinado produto digitando, por exemplo. Vamos dar uma olhada na implementação:url/ProductGet/{productid}url/ProductGet/1

// ProductGet.cs

using System;
using System.IO;
using System.Threading.Tasks;
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 Company.Function
{
    public static class ProductGet
    {
        [FunctionName("ProductGet")]
        public static async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "ProductsGet/{id:int}")] HttpRequest req,
            int id,
            ILogger log)
        {
            log.LogInformation("C# HTTP trigger function processed a request.");

            var product = Db.GetProductById(id);
            var json = JsonConvert.SerializeObject(new {
                product = product
            });

            return (ActionResult) new OkObjectResult(json);
        }
    }
}

Observe especificamente como nós, em nosso decorador, definimos o valor de . Este é um padrão que significa que responderemos a pedidos parecidos. Há outra coisa que analisamos da rota simplesmente digitando. Em seguida, simplesmente filtramos o produto correspondente pelo seguinte código abaixo e criamos nossa resposta

JSON: HttpTriggerRouteProductsGet/{id:int}url/ProductsGet/1idint id

var product = Db.GetProductById(id);
var json = JsonConvert.SerializeObject(new {
    product = product
});

Criar um produto

Para este, limitaremos o verbo HTTP a . Também vamos ler do corpo e analisar. O código se parece com o seguinte:postname

// ProductCreate.cs

using System;
using System.IO;
using System.Threading.Tasks;
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 Company.Function
{
    public static class ProductCreate
    {
        [FunctionName("ProductCreate")]
        public static async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequest req,
            ILogger log)
        {
            log.LogInformation("C# HTTP trigger function processed a request.");

            string name = null;

            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            log.LogInformation("request body", requestBody);
            dynamic data = JsonConvert.DeserializeObject(requestBody);
            name = name ?? data?.name;

            if(name != null) 
            {
                log.LogInformation("name", name);
                var product = Db.CreateProduct(name);
                var json = JsonConvert.SerializeObject(new{
                    product = product
                });
                return (ActionResult)new OkObjectResult(json);
            } 
            else {
                return new BadRequestObjectResult("Missing name in posted Body");
            }
        }
    }
}

A parte interessante aqui é como analisamos as informações do Corpo com este código:

string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
log.LogInformation("request body", requestBody);
dynamic data = JsonConvert.DeserializeObject(requestBody);
name = name ?? data?.name;

Fora isso, o código é bem direto e verifica se foi fornecido no Corpo se assim for em frente e criá-lo e finalmente criar uma resposta JSON.name

Resumo

Ok, nós fizemos algum progresso. Entendemos a razão para escolher o servidor sem servidor, conseguimos criar dois aplicativos diferentes do Azure Function, um mais simples que mostra conceitos como lidar com parâmetros de consulta e corpo e o outro exemplo mostrando uma API REST onde estamos perto do código de produção.

Este foi o primeiro artigo sobre Serverless e C# e espero que agora você entenda o básico. Acabamos de arranhar a superfície. O valor real está na capacidade de configuração de diferentes gatilhos e vinculações de entrada e saída e interagir com muitos outros serviços no Azure.


Autor: Chris Noring

Artigo Original