Saiba como os nanoserviços diferem dos microsserviços e como construí-los com ASP.NET e Azure Functions.

As arquiteturas de aplicativos evoluíram muito rapidamente nos últimos anos. A arquitetura monolítica clássica foi dividida em uma coleção de microsserviços para apoiar uma infraestrutura de desenvolvimento e implantação mais dinâmica. No entanto, embora sua popularidade, há certas desvantagens em usar uma arquitetura de microsserviço. Recentemente, uma divisão mais granular de um aplicativo distribuído está se tornando popular: nanoserviços. Os nanoserviços não são um substituto para microsserviços, mas são adeptos a lidar com algumas de suas deficiências, e eles podem fornecer melhor isolamento e granularidade.

Este artigo introduzirá a arquitetura de nanoserviços e mostrará como criá-los com ASP.NET e Funções Azure.

Da arquitetura monolítica à nanoservimentar

Uma arquitetura monolítica compreende uma coleção de componentes que são construídos, implantados, dimensionados e mantidos como uma única unidade. Embora a simplicidade, é extremamente difícil mudar, dimensionar ou manter tais aplicações. Um aplicativo monolítico normalmente usa uma plataforma tecnológica homogênea. Construir um monólito com tecnologias heterogêneas é extremamente difícil. Ao longo de um tempo, um monólito pode se tornar altamente complexo para lidar: agilidade operacional, escalabilidade e manutenção podem se tornar um desafio. A arquitetura de microsserviço evoluiu para lidar com as deficiências das arquiteturas monolíticas nos últimos anos. A adoção de microsserviços tem aumentado principalmente devido à busca por uma melhor escalabilidade, flexibilidade e desempenho.

O que é arquitetura de microsserviço?

A arquitetura de microsserviço é uma variante de arquitetura orientada a serviços (SOA) que constrói um aplicativo que inclui serviços leves, vagamente acoplados e modulares. Esses serviços podem ser executados em uma ampla variedade de plataformas e são desenvolvidos, testados, implantados e gerenciados de forma independente. A arquitetura de microsserviço é mais fácil de manter, dimensionar e testar porque são menores e mais focadas do que seus antecessores. Você pode aproveitar a arquitetura de microsserviço para substituir aplicativos monolíticos complexos e de longo prazo com despesas significativas de recursos e gerenciamento. O termo microsserviço refere-se ao escopo limitado de funcionalidade fornecido pelo serviço, em vez do comprimento do código usado para criá-lo.

O advento da arquitetura de nanoserviços

Não há uma definição precisa de quão grande ou pequeno um microsserviço deve ser. Embora a arquitetura de microsserviço possa resolver as deficiências de um monólito, cada microsserviço pode crescer ao longo de um tempo. A arquitetura de microsserviço não é adequada para aplicações de todos os tipos. Sem o planejamento adequado, os microsserviços podem crescer tão grandes e pesados quanto o monólito que eles devem substituir. Um nanoserviço é um componente pequeno, autônomo, implantável, testável e reutilizável que quebra um microserviço em pedaços menores. Um nanoserviço, por outro lado, não reflete necessariamente toda uma função de negócio. Como são menores que os microsserviços, diferentes equipes podem trabalhar em vários serviços em um determinado momento. Um nanoserviço deve executar apenas uma tarefa e expô-la através de um ponto final da API. Se você precisar de seus nanoserviços para fazer mais trabalho para você, vincule-os com outros nanoserviços. Os nanoserviços não substituem os microsserviços - complementam as deficiências dos microsserviços.

Benefícios da arquitetura de nanoserviços

As vantagens do uso de nanoserviços são as seguintes:

  • Leve. Um nanoserviço é muito mais focado do que um microsserviço e, portanto, é muito mais leve.
  • Segurança. A natureza granular dos nanoserviços permite que ele tenha seu protocolo de segurança e seja implantado independentemente de outros nanoserviços. Como resultado, você pode isolar um nanoserviço que lida com dados confidenciais de outro que não.
  • Encapsulamento puro. Um nanoserviço encapsula a lógica do negócio, mas é restrito a apenas um recurso. O nanoserviço pode ser chamado de qualquer lugar desde que você tenha a permissão necessária para invocá-lo.
  • Loop de feedback. Um nanoserviço pode coletar dados e métricas da mesma forma que um microsserviço, permitindo um monitoramento e ferramentas de métricas extensas. A natureza granular dos nanoserviços facilita loops de feedback mais rápidos – isso permite loops rápidos de feedback, o que pode ajudá-lo a diagnosticar e resolver problemas mais rapidamente. Você também pode ter feedback constante sobre como seu nanoserviço está se saindo em tempo real.

Nanoserviços vs. microserviços

As palavras microsserviço e nanosserviço tendem a ser sinônimos, mas há diferenças sutis entre elas. Embora o primeiro tenha evoluído para suprentar as deficiências dos monólitos, este último é uma forma evoluída de arquitetura de microsserviço e lida com suas complexidades, mas não é um substituto. A arquitetura nanoservice é uma boa escolha quando seus microsserviços se tornam muito grandes ou se você precisa de mais flexibilidade e isolamento do que eles podem fornecer. Os nanoserviços têm um escopo limitado do que os microsserviços, e eles normalmente estão no nível funcional. Você poderia construir um microsserviço a partir de nanoserviços. Os microsserviços superam os monólitos de várias maneiras, e os nanoserviços são ainda melhores. Você pode usar nanoserviços para construir a lógica do negócio uma vez e encapsulá-la dentro de uma função sem servidor, permitindo que ela seja reutilizada. Para projetar uma arquitetura robusta e escalável, você deve usar uma combinação de arquitetura de microsserviço e nanosserviço para aproveitar o melhor dos dois mundos e, ao mesmo tempo, eliminar as desvantagens de cada um.

Computação sem servidor e funções do Azure

A promessa da nuvem de tamanho ilimitado, melhor manutenção de recursos e custos mais baixos exigiu novas maneiras de pensar sobre a execução de aplicativos. A computação sem servidores é uma dessas tecnologias que se proliferaram nos últimos anos. Ele permite que você construa e implante rapidamente software e serviços, removendo a necessidade de manter a infraestrutura subjacente.

Os nanoserviços incorporados neste artigo aproveitarão a tecnologia sem servidor usando funções do Azure. Uma função Azure é essencialmente um pedaço de código que é executado na nuvem, normalmente em um ambiente sem servidor projetado para acelerar e simplificar o desenvolvimento de aplicativos. É um serviço de computação sem servidor que permite executar código sob demanda sem gerenciar recursos ou hospedá-lo em um servidor. Ao usar funções do Azure, você precisaria pagar pela computação sem servidor com base na quantidade de tempo que sua Função Azure foi executada. As Funções Azure são uma ótima maneira de construir nanoserviços - sem servidor com APIs simples. Você pode acionar uma Função Azure usando qualquer evento no Azure, usando serviços de terceiros, bem como qualquer sistema local.

O aplicativo ItemCheckOut

Vamos colocar conceitos de nanoserviço em prática implementando um exemplo concreto. Considere um processo de checkout de itens de um aplicativo de e-commerce. Normalmente, a lista das etapas que você teria que seguir é:

  1. Verifique se um item está disponível
  2. Adicione itens ao carrinho
  3. Pagamento de processo
  4. Envie um e-mail de confirmação
  5. Atualizar estoque

Cada uma dessas etapas corresponde a um nanoserviço que pode ser implementado por uma função autônoma sem servidor. Você pode usar o Azure, AWS ou Google Cloud, etc., para escrever sua função sem servidor. Neste artigo, aproveitaremos as Funções do Azure para construir e implantar nossas funções sem servidor. Juntas, essas funções compõem o microsserviço ItemCheckout, conforme mostrado na imagem a seguir.

image

Pré-requisitos

Para executar os exemplos de código mostrados neste artigo, aqui estão os requisitos mínimos que você deveria ter instalado em seu sistema:

  • .NET 5 SDK
  • Visual Studio 2019

A estrutura de aplicação

O aplicativo que você vai construir incluirá quatro projetos como parte de uma única solução visual studio:

  • ItemCheckOutAPI. Este projeto contém o código necessário para invocar os nanoserviços implementados como Funções Azure.
  • Nanoservices.Entities. Este projeto define as classes utilizadas para representar os dados.
  • Nanoservices.Infrastructure. Neste projeto, você definirá a infraestrutura para armazenar e operar os dados.
  • NanoservicesFunctionApp. Este projeto implementa os nanoserviços reais.

A imagem a seguir mostra como será a estrutura de solução do aplicativo concluído:

image

Vamos construir cada um desses projetos nas seções que se seguem.

A seguir, as dependências de cada um dos quatro projetos nesta aplicação:

  1. Nanoservices.Entities -> Nenhum
  2. ItemCheckOutAPI -> Nanoservices.Entities
  3. Nanoservices.Infrastructure -> Nanoservices.Entities
  4. NanoservicesFunctionApp -> Nanoservices.Entities e Nanoservices.Infrastructure

O seguinte diagrama de dependência ilustra essas dependências:

image

Implementando o aplicativo ItemCheckOut

Seguiremos as etapas abaixo descritas para construir o aplicativo ItemCheckOut e implantar os nanoserviços no Azure:

  1. Crie as classes da entidade. Nesta etapa, você criará as classes básicas que o aplicativo gerenciará.
  2. Crie a infraestrutura do repositório. Nesta etapa, você definirá as classes responsáveis pelo manuseio de dados.
  3. Crie o componente lógico de negócios. Esta etapa implementa a lógica de negócios do microsserviço que você vai construir.
  4. Registre as dependências. Nesta etapa, você prepara a conexão entre os componentes que você já criou e os nanoserviços que você vai criar na próxima etapa.
  5. Crie as Funções Azure. Aqui, você implementará os nanoserviços reais como Funções Azure.
  6. Implantar as Funções Azure. Nesta etapa, você publicará ao Azure os nanoserviços criados na etapa anterior.
  7. Ligue para os nanoserviços. Esta etapa implementa o microsserviço que invoca os nanoserviços.

Então, vamos começar.

Criar as classes de entidade

Para manter as coisas simples, usaremos apenas três classes de entidades, ou seja, , e . Crie um projeto de biblioteca de classe no Visual Studio e nomeie-o . Em seguida, crie um arquivo de classe neste projeto nomeado com o seguinte código:CartItemCustomerProductNanoservices.EntitiesCartItem.cs

// Nanoservices.Entities/CartItem.cs
using System;

namespace Nanoservices.Entities
{
    public class CartItem
    {
        public Guid Id { get; set; }
        public Guid Product_Id { get; set; }
        public Guid Customer_Id { get; set; }
        public int Number_Of_Items { get; set; }
        public DateTime Item_Added_On { get; set; }
    }
}

A classe representa cada item no carrinho.CartItem

Adicione um novo arquivo de classe ao projeto nomeado . Esta classe terá o seguinte código:Nanoservices.EntitiesCustomer.cs

// Nanoservices.Entities/Customer.cs
using System;

namespace Nanoservices.Entities
{
    public class Customer
    {
        public Guid Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string EmailAddress { get; set; }
    }
}

Finalmente, adicione o arquivo de classe com este código:Product.cs

// Nanoservices.Entities/Product.cs
using System;

namespace Nanoservices.Entities
{
    public class Product
    {
        public Guid Id { get; set; }
        public string Code { get; set; }
        public string Name { get; set; }
        public int Quantity_In_Stock { get; set; }
        public decimal Unit_Price { get; set; }
    }
}

Criar a infraestrutura do repositório

Crie outro projeto de biblioteca de classe na mesma solução chamada . Este projeto conterá o repositório e as classes de lógica empresarial e suas interfaces. O projeto tem uma dependência do projeto que você criou na etapa anterior. Então, adicione essa dependência no Visual Studio.Nanoservices.InfrastructureNanoservices.InfrastructureNanoservices.Entities

Agora, crie três pastas de solução neste projeto chamado , e . A imagem a seguir fornece uma prévia de como vamos organizar os arquivos neste projeto:DefinitionsRepositoriesServices

image

Por uma questão de simplicidade, não usaremos nenhum banco de dados neste exemplo. Aplicando o Padrão de Design do Repositório,os dados da amostra residirão apenas na classe.CheckOutRepository

Antes de criar essa classe, vamos primeiro definir uma interface chamada . Na pasta, crie um arquivo de classe nomeado com o seguinte código:ICheckOutRepositoryDefinitionsICheckOutRepository.cs

// Nanoservices.Infrastructure/Definitions/ICheckOutRepository.cs
using Nanoservices.Entities;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Nanoservices.Infrastructure.Definitions
{
    public interface ICheckOutRepository
    {
        Task<List<CartItem>> GetAllCartItems();
        Task<List<Product>> GetAllProducts();
        Task<List<Customer>> GetAllCustomers();
        public Task<bool> IsItemAvailable(string productCode);
        public Task<bool> AddItemToCart(CartItem cartItem);
        public Task<bool> ProcessPayment(CartItem cartItem);
        public Task<bool> SendConfirmation(CartItem cartItem);
        public Task<bool> UpdateStock(CartItem cartItem);
    }
}

Na pasta, crie um arquivo de classe com o seguinte código:RepositoriesCheckOutRepository.cs

// Nanoservices.Infrastructure/Repositories/CheckOutRepository.cs
using Nanoservices.Entities;
using Nanoservices.Infrastructure.Definitions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Nanoservices.Infrastructure.Repositories
{
    public class CheckOutRepository : ICheckOutRepository
    {
        private readonly List<Customer> customers = new List<Customer>();
        private readonly List<Product> products = new List<Product>();
        private readonly List<CartItem> cartItems = new List<CartItem>();
        public CheckOutRepository()
        {
            customers.Add(new Customer()
            {
                Id = Guid.NewGuid(),
                FirstName = "Joydip",
                LastName = "Kanjilal",
                EmailAddress = "joydipkanjilal@yahoo.com"
            });

            customers.Add(new Customer()
            {
                Id = Guid.NewGuid(),
                FirstName = "Steve",
                LastName = "Smith",
                EmailAddress = "stevesmith@yahoo.com"
            });

            products.Add(new Product
            {
                Id = Guid.NewGuid(),
                Code = "P0001",
                Name = "Lenovo Laptop",
                Quantity_In_Stock = 15,
                Unit_Price = 125000
            });

            products.Add(new Product
            {
                Id = Guid.NewGuid(),
                Code = "P0002",
                Name = "DELL Laptop",
                Quantity_In_Stock = 25,
                Unit_Price = 135000
            });

            products.Add(new Product
            {
                Id = Guid.NewGuid(),
                Code = "P0003",
                Name = "HP Laptop",
                Quantity_In_Stock = 20,
                Unit_Price = 115000
            });

            cartItems.Add(new CartItem()
            {
                Id = Guid.NewGuid(),
                Product_Id = products.Where(p => p.Code.Equals("P0001")).FirstOrDefault().Id,
                Customer_Id = customers.Where(c => c.FirstName == "Joydip").FirstOrDefault().Id,
                Number_Of_Items = 1,
                Item_Added_On = DateTime.Now
            });

            cartItems.Add(new CartItem()
            {
                Id = Guid.NewGuid(),
                Product_Id = products.Where(p => p.Code.Equals("P0003")).FirstOrDefault().Id,
                Customer_Id = customers.Where(c => c.FirstName == "Steve").FirstOrDefault().Id,
                Number_Of_Items = 10,
                Item_Added_On = DateTime.Now
            });

            cartItems.Add(new CartItem()
            {
                Id = Guid.NewGuid(),
                Product_Id = products.Where(p => p.Code.Equals("P0002")).FirstOrDefault().Id,
                Customer_Id = customers.Where(c => c.FirstName == "Joydip").FirstOrDefault().Id,
                Number_Of_Items = 5,
                Item_Added_On = DateTime.Now
            });
        }
        public Task<List<CartItem>> GetAllCartItems()
        {
            return Task.FromResult(cartItems);
        }
        public Task<List<Product>> GetAllProducts()
        {
            return Task.FromResult(products);
        }
        public Task<List<Customer>> GetAllCustomers()
        {
            return Task.FromResult(customers);
        }
        public Task<bool> AddItemToCart(CartItem cartItem)
        {
            cartItems.Add(cartItem);
            return Task.FromResult(true);
        }
        public Task<bool> IsItemAvailable(string productCode)
        {
            var p = products.Find(p => p.Code.Trim().Equals(productCode));

            if (p != null)
                return Task.FromResult(true);
            return Task.FromResult(false);
        }
        public Task<bool> ProcessPayment(CartItem cartItem)
        {
            var product = products.Where(c => c.Id == cartItem.Product_Id).FirstOrDefault();
            var totalPrice = product.Unit_Price * cartItem.Number_Of_Items;
            //Write code here to process payment for the purchase
            return Task.FromResult(true);
        }
        public Task<bool> SendConfirmation(CartItem cartItem)
        {
            var customer = customers.Where(c => c.Id == cartItem.Customer_Id).FirstOrDefault();
            //Write code here to send an email to the customer confirming the purchase
            return Task.FromResult(true);
        }
        public Task<bool> UpdateStock(CartItem cartItem)
        {
            var p = products.Find(p => p.Id == cartItem.Product_Id);
            p.Quantity_In_Stock--;
            return Task.FromResult(true);
        }
    }
}

A classe implementa a interface, e seu construtor adiciona alguns dados de amostra para jogar.CheckOutRepositoryICheckOutRepository

Criar o componente de lógica de negócios

O componente da lógica empresarial do nosso microsserviço é responsável por fornecer suas principais funcionalidades. Sendo esta uma implementação mínima de um aplicativo baseado em microserviços, juntamente com seus nanoserviços, você não teria realmente nenhuma lógica de negócio como tal neste componente. No nosso caso, ele funcionará como um passo a passo entre a classe que implementaremos em uma seção posterior e no repositório.CartManager

Vamos primeiro criar a interface de serviço. Na pasta de solução do projeto, crie um arquivo de classe nomeado com o seguinte código:DefinitionsNanoservices.InfrastructureICheckOutService.cs

// Nanoservices.Infrastructure/Definitions/ICheckOutService.cs
using Nanoservices.Entities;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Nanoservices.Infrastructure.Definitions
{
    public interface ICheckOutService
    {
        public Task<List<CartItem>> GetAllCartItems();
        public Task<List<Product>> GetAllProducts();
        public Task<List<Customer>> GetAllCustomers();
        public Task<bool> IsItemAvailable(string productCode);
        public Task<bool> AddItemToCart(CartItem cartItem);
        public Task<bool> ProcessPayment(CartItem cartItem);
        public Task<bool> SendConfirmation(CartItem cartItem);
        public Task<bool> UpdateStock(CartItem cartItem);
    }
}

A interface contém a assinatura de todas as operações que correspondem a um nanoserviço. Em seguida, crie uma classe nomeada em um arquivo nomeado dentro da pasta de solução do projeto com o seguinte código lá:ICheckOutServiceCheckOutServiceCheckOutService.csServicesNanoservices.Infrastructure

// Nanoservices.Infrastructure/Services/ICheckOutService.cs
using Nanoservices.Entities;
using Nanoservices.Infrastructure.Definitions;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Nanoservices.Infrastructure.Services
{
    public class CheckOutService : ICheckOutService
    {
        private readonly ICheckOutRepository _repository;
        public CheckOutService(ICheckOutRepository repository)
        {
            _repository = repository;
        }
        public Task<List<CartItem>> GetAllCartItems()
        {
            return _repository.GetAllCartItems();
        }
        public Task<List<Product>> GetAllProducts()
        {
            return _repository.GetAllProducts();
        }
        public Task<List<Customer>> GetAllCustomers()
        {
            return _repository.GetAllCustomers();
        }
        public Task<bool> AddItemToCart(CartItem cartItem)
        {
            return _repository.AddItemToCart(cartItem);
        }
        public Task<bool> IsItemAvailable(string productCode)
        {
            return _repository.IsItemAvailable(productCode);
        }
        public Task<bool> ProcessPayment(CartItem cartItem)
        {
            return _repository.ProcessPayment(cartItem);
        }
        public Task<bool> SendConfirmation(CartItem cartItem)
        {
            return _repository.SendConfirmation(cartItem);
        }
        public Task<bool> UpdateStock(CartItem cartItem)
        {
            return _repository.UpdateStock(cartItem);
        }
    }
}

Crie um projeto de Funções Azure no Visual Studio

Supondo que você já tenha uma conta no Azure, examinaremos como construir e implantar funções do Azure. Você pode criar uma função Azure a partir do Portal Azure ou do Estúdio Visual. Neste artigo, vamos criar e implantar funções do Azure usando o Visual Studio 2019.

Siga os passos descritos abaixo para criar uma Função Azure no Visual Studio 2019.

  1. De dentro do Visual Studio IDE, clique com o botão direito do mouse em sua solução na janela Explorador de soluções e selecione Adicionar >Novo Projeto.
  2. Na janela “Adicionar novo projeto”, selecione “Funções Azure” como modelo de projeto.
  3. Clique em “Next”.
  4. Na janela “Configure seu novo projeto”, especifique o nome e a localização do projeto Funções Azure. Use como nome para o projetoNanoservicesFunctionApp
  5. Clique em “Criar”.
  6. Na janela “Criar um novo aplicativo funções do Azure”, selecione “Http Trigger” como o gatilho a ser usado. Veja a imagem a seguir como uma referência:

image

  1. Selecione a “conta de armazenamento” como “Nenhum”, pois não usaremos uma aqui.
  2. Selecione “Nível de autorização” como “Anônimo”.
  3. Clique em “Criar”.

Um novo projeto de função Azure acionado pelo Http será criado. Por padrão, um arquivo nomeado será criado com o código de uma função padrão para sua referência. Agora, exclua este arquivo, pois não precisamos dele em nosso aplicativo. Criaremos nossas próprias Funções Azure em breve.Function1.cs

Registre as dependências

ASP.NET Core fornece suporte integrado para o padrão de design de injeção de dependência (DI). O DI permite que você alcance a Inversão de Controle (IoC) entre as classes e suas dependências e ajuda na criação de componentes acoplados e na escrita de códigos manteveis. ASP.NET Core fornece um contêiner de serviço embutido chamado . Você deve registrar suas dependências com o contêiner de serviço no método da classe em seu aplicativo.IServiceProviderConfigureServicesStartup

Para saber mais sobre a injeção de dependência em .NET, confira este artigo.

As funções azure também suportam a injeção de dependência. Vamos explorar como habilitá-lo neste projeto.

Como primeiro passo, instale o pacote neste projeto usando o gerenciador de pacotes NuGet. Em seguida, adicione o projeto como uma dependência neste projeto.Microsoft.Azure.Functions.ExtensionsNanoServices.Infrastructure

Agora, adicione um arquivo de classe nomeado ao seu projeto Azure Function com o seguinte código lá:Startup.cs

// NanoservicesFunctionApp/Startup.cs
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using Nanoservices.Infrastructure.Definitions;
using Nanoservices.Infrastructure.Repositories;
using Nanoservices.Infrastructure.Services;

[assembly: FunctionsStartup(typeof(NanoservicesFunctionApp.Startup))]

namespace NanoservicesFunctionApp
{
    public class Startup : FunctionsStartup
    {
        public override void Configure(IFunctionsHostBuilder builder)
        {
            builder.Services.AddScoped<ICheckOutRepository, CheckOutRepository>();
            builder.Services.AddScoped<ICheckOutService, CheckOutService>();
        }
    }
}

O atributo de montagem indica o nome do tipo a ser usado durante a inicialização.FunctionsStartup

A classe amplia a classe base. Dentro de seu método, você registra as dependências necessárias através do método.StartupFunctionsStartupConfigureAddScoped

Crie as Funções do Azure

Nesta seção, você criará as Funções Azure que implementam os nanoserviços. Essas funções serão invocadas com base em uma solicitação HTTP, daí o nome Funções Azure acionadas por HTTP. Você pode aprender mais sobre funções Azure acionadas por HTTP daqui.

Agora, crie um arquivo nomeado na raiz do projeto com o seguinte código lá:CartManager.csNanoservicesFunctionApp

// NanoservicesFunctionApp/CartManager.cs
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Extensions.Logging;
using Nanoservices.Entities;
using Nanoservices.Infrastructure.Definitions;
using Newtonsoft.Json;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web.Http;

namespace NanoservicesFunctionApp
{
    public class CartManager
    {
        private readonly ICheckOutService _checkoutService;
        public CartManager(ICheckOutService checkoutService)
        {
            _checkoutService = checkoutService;
        }
    }
}

Agora criaremos todas as nossas Funções Azure, uma a uma, como métodos da classe.CartManager

A função IsItemAvailable Azure

Esta função verifica se um item está disponível para compra e retorna verdadeiro se estiver disponível, falso de outra forma. O trecho de código a seguir ilustra o método a ser adicionado à classe:IsItemAvailableCartManager

[FunctionName("IsItemAvailable")]
public async Task<IActionResult> IsItemAvailable(
    [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)]
    HttpRequest req, ILogger log)
{
    string productCode = req.Query["productcode"];
    return new OkObjectResult(await _checkoutService.IsItemAvailable(productCode));
}

A função GetAllCartItems Azure

Esta função retorna uma lista de todos os itens do carrinho. O trecho de código a seguir ilustra a implementação:GetAllCartItems

[FunctionName("GetAllCartItems")]
public async Task<IActionResult> GetAllCartItems(
    [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)]
    HttpRequest req, ILogger log)
{
    return new OkObjectResult(await _checkoutService.GetAllCartItems());
}

A função GetAllCustomers Azure

A função recupera todos os registros de clientes. O trecho de código a seguir ilustra esta função:GetAllCustomers

[FunctionName("GetAllCustomers")]
public async Task<IActionResult> GetAllCustomers(
    [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)]
    HttpRequest req, ILogger log)
{
    return new OkObjectResult(await _checkoutService.GetAllCustomers());
}

A função GetAllProducts Azure

A função retorna uma lista de todos os produtos disponíveis:GetAllProducts

[FunctionName("GetAllProducts")]
public async Task<IActionResult> GetAllProducts(
    [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)]
    HttpRequest req, ILogger log)
{
    return new OkObjectResult(await _checkoutService.GetAllProducts());
}

A função AddItemToCart Azure

Esta função é usada para adicionar itens ao carrinho. A seguir está o código-fonte da Função Azure:AddItemToCart

[FunctionName("AddItemToCart")]
public async Task<IActionResult> AddItemToCart(
    [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)]
    HttpRequestMessage req, ILogger log)
{
    string jsonContent = await req.Content.ReadAsStringAsync();
    if (string.IsNullOrEmpty(jsonContent))
    {
        return new BadRequestErrorMessageResult("Invalid input.");
    }

    CartItem cartItem = JsonConvert.DeserializeObject<CartItem>(jsonContent);
    return new OkObjectResult(await _checkoutService.AddItemToCart(cartItem));
}

A função UpdateStock Azure

A função é usada para atualizar o estoque de itens. A seguir está o código-fonte desta Função Azure:UpdateStock

[FunctionName("UpdateStock")]
public async Task<IActionResult> UpdateStock(
    [HttpTrigger(AuthorizationLevel.Anonymous, "get", "put", Route = null)]
    HttpRequestMessage req, ILogger log)
{
    string jsonContent = await req.Content.ReadAsStringAsync();
    if (string.IsNullOrEmpty(jsonContent))
    {
        return new BadRequestErrorMessageResult("Invalid input.");
    }
  
    CartItem cartItem = JsonConvert.DeserializeObject<CartItem>(jsonContent);
    return new OkObjectResult(await _checkoutService.UpdateStock(cartItem));
}

A classe CartManager

O código fonte completo do arquivo é dado abaixo para sua referência:CartManager.cs

// NanoservicesFunctionApp/CartManager.cs
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Extensions.Logging;
using Nanoservices.Entities;
using Nanoservices.Infrastructure.Definitions;
using Newtonsoft.Json;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web.Http;

namespace NanoservicesFunctionApp
{
    public class CartManager
    {
        private readonly ICheckOutService _checkoutService;
        public CartManager(ICheckOutService checkoutService)
        {
            _checkoutService = checkoutService;
        }

        [FunctionName("IsItemAvailable")]
        public async Task<IActionResult> IsItemAvailable(
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)]
            HttpRequest req, ILogger log)
        {
            string productCode = req.Query["productcode"];
            return new OkObjectResult(await _checkoutService.IsItemAvailable(productCode));
        }

        [FunctionName("GetAllCartItems")]
        public async Task<IActionResult> GetAllCartItems(
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)]
            HttpRequest req, ILogger log)
        {
            return new OkObjectResult(await _checkoutService.GetAllCartItems());
        }

        [FunctionName("GetAllCustomers")]
        public async Task<IActionResult> GetAllCustomers(
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)]
            HttpRequest req, ILogger log)
        {
            return new OkObjectResult(await _checkoutService.GetAllCustomers());
        }

        [FunctionName("GetAllProducts")]
        public async Task<IActionResult> GetAllProducts(
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)]
            HttpRequest req, ILogger log)
        {
            return new OkObjectResult(await _checkoutService.GetAllProducts());
        }

        [FunctionName("UpdateStock")]
        public async Task<IActionResult> UpdateStock(
           [HttpTrigger(AuthorizationLevel.Anonymous, "get", "put", Route = null)]
            HttpRequestMessage req, ILogger log)
        {
            string jsonContent = await req.Content.ReadAsStringAsync();
            if (string.IsNullOrEmpty(jsonContent))
            {
                return new BadRequestErrorMessageResult("Invalid input.");
            }

            CartItem cartItem = JsonConvert.DeserializeObject<CartItem>(jsonContent);
            return new OkObjectResult(await _checkoutService.UpdateStock(cartItem));
        }

        [FunctionName("AddItemToCart")]
        public async Task<IActionResult> AddItemToCart(
           [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)]
            HttpRequestMessage req, ILogger log)
        {
            string jsonContent = await req.Content.ReadAsStringAsync();
            if (string.IsNullOrEmpty(jsonContent))
            {
                return new BadRequestErrorMessageResult("Invalid input.");
            }

            CartItem cartItem = JsonConvert.DeserializeObject<CartItem>(jsonContent);
            return new OkObjectResult(await _checkoutService.AddItemToCart(cartItem));
        }
    }
}

Implantar as Funções Azure

Para implantar as Funções Azure que criamos anteriormente, siga os passos descritos abaixo:

  1. Clique com o botão direito do mouse no projeto na janela Explorador de soluções e clique em Publicar.NanoservicesFunctionApp
  2. Na janela Publicar que aparece, selecione Ozure (é o padrão) como o alvo de publicação, já que estaremos hospedando nosso Nanoservice na plataforma de nuvem Azure. Veja a imagem a seguir para referência:

image

  1. Clique em Next.

  2. Agora, especifique “Aplicativo de Funções Azure (Windows)” como o destino (este é o padrão) para hospedar o aplicativo:

image

  1. Clique em Next.
  2. Por fim, especifique o nome de assinatura, exibição e o a ser usado. Como você não tem um já criado, você pode criar um novo clicando no símbolo +. Especifique o nome do as .FunctionAppFunctionAppFunctionAppNanoservicesFunctionApp
  3. Clique em Terminar.
  4. Verifique se as informações de configurações e hospedagem estão corretas:

image

  1. Clique em Publicar para concluir o processo.

Uma vez que sua Função Azure tenha sido publicada, você pode consumi-la em seu aplicativo da mesma forma que consumir qualquer outro serviço usando .HttpClient

Testando as Funções Azure

Até aqui, tudo bem. Uma vez que os nanoserviços são implantados no ambiente Azure, você pode testá-los de dentro do próprio Portal Azure. Para testar as Funções do Azure, siga os passos descritos abaixo:

  • Faça login no Portal Azure (pule esta etapa se você já estiver logado).
  • Na tela Inicial, clique em .FunctionApp
  • Na tela, clique em já que é o que gostaríamos de testar.FunctionAppNanoservicesFunctionAppFunctionApp
  • Na tela seguinte, clique em Funções para listar cada uma das Funções Azure do FunctionApp
  • Clique na função Azure para testá-la.GetAllCustomers
  • Na próxima tela, clique em Código + Teste.
  • Agora, clique em Teste/Execução.
  • Em seguida, especifique o método HTTP e, opcionalmente, a chave. (As teclas de acesso de função ou de função fornecem um mecanismo de segurança padrão para acessar funções do Azure.)
  • Clique em Executar para executar a função Azure.

Veja como seria a saída:

image

Chame os Nanoservices de um aplicação de API web ASP.NET Core

Para completar o aplicativo, precisamos criar o microsserviço, que consumirá os nanoserviços publicados no Azure. Para isso, siga os passos mencionados abaixo em sua instância do Visual Studio aberta sobre a solução em que estamos trabalhando:ItemCheckOutAPI

  • De dentro do Visual Studio IDE, clique com o botão direito do mouse em sua solução na janela Explorador de soluções e selecione Adicionar >Novo Projeto.
  • Na janela “Adicionar novo projeto”, selecione “ASP.NET API da Web Principal” como modelo de projeto e clique em Next.
  • Na janela “Configure seu novo projeto”, especifique o nome do projeto como e clique em Next.ItemCheckOutAPI
  • Especifique o .NET 5 como a estrutura de destino que você gostaria de usar.
  • Certifique-se de que a caixa de seleção “Habilitar suporte ao OpenAPI” seja verificada.
  • Certifique-se de que a caixa de seleção “Configure for HTTPS” esteja verificada, mas a caixa de seleção “Ativar docker” não está verificada.
  • Clique em Criar para concluir o processo.

Construa o microserviço ItemCheckOut

Vamos agora construir nosso microsserviço minimalista. Ele implementará métodos de ação que correspondem a cada operação do processo de checkout de itens que discutimos anteriormente. Cada um desses métodos de ação envolveria as chamadas para as funções sem servidor que criamos anteriormente.

Antes de começar a codificar, adicione uma referência ao projeto.Nanoservices.Entities

Agora, na pasta, crie um controlador de API nomeado e substitua o código padrão pelo seguinte:ControllersItemCheckOutController

// ItemCheckOutAPI/Controllers/ItemCheckOutController.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Nanoservices.Entities;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;

namespace ItemCheckOutAPI.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class ItemCheckOutController : ControllerBase
    {
        private readonly string baseURL;
        private readonly IConfiguration _configuration;
        public ItemCheckOutController(IConfiguration configuration)
        {
            _configuration = configuration;
            baseURL = _configuration.GetSection("MyAppSettings").GetSection("AzureFunctionURL").Value;
        }

        [HttpGet("IsItemAvailable")]
        public async Task<bool> IsItemAvailable(string productCode)
        {
            return await IsItemAvailableInternal(productCode);
        }

        [HttpGet("GetAllCartItems")]
        public async Task<List<CartItem>> GetAllCartItems()
        {
            return await GetAllCartItemsInternal();
        }

        [HttpGet("GetAllProducts")]
        public async Task<List<Product>> GetAllProducts()
        {
            return await GetAllProductsInternal();
        }

        [HttpGet("GetAllCustomers")]
        public async Task<List<Customer>> GetAllCustomers()
        {
            return await GetAllCustomersInternal();
        }

        [HttpPost("AddItemToCart")]
        public async Task<bool> AddItemToCart([FromBody] CartItem cartItem)
        {
            return await AddItemToCartInternal(cartItem);
        }

        [HttpPost("ProcessPayment")]
        public async Task<bool> ProcessPayment([FromBody] CartItem cart)
        {
            return await Task.FromResult(true);
        }

        [HttpPost("SendConfirmation")]
        public async Task<bool> SendConfirmation([FromBody] CartItem cartItem)
        {
            return await Task.FromResult(true);
        }

        [HttpPut("UpdateStock")]
        public async Task<bool> UpdateStock([FromBody] CartItem cartItem)
        {
            return await UpdateStockInternal(cartItem);
        }
        private async Task<bool> IsItemAvailableInternal(string productCode)
        {
            string azureFunctionBaseUrl = baseURL + "IsItemAvailable";
            string queryStringParams = $"?productCode={productCode}";
            string url = azureFunctionBaseUrl + queryStringParams;
            using (HttpClient client = new HttpClient())
            {
                using (HttpResponseMessage responseMessage = await client.GetAsync(url))
                {
                    using (HttpContent content = responseMessage.Content)
                    {
                        string data = await content.ReadAsStringAsync();
                        if (data != null)
                            return bool.Parse(data);
                        return false;
                    }
                }
            }
        }
        private async Task<bool> AddItemToCartInternal(CartItem cartItem)
        {
            string url = baseURL + "AddItemToCart";
            var json = JsonConvert.SerializeObject(cartItem);
            var data = new StringContent(json, Encoding.UTF8, "application/json");

            using (HttpClient client = new HttpClient())
            {
                var response = await client.PostAsync(url, data);
            }

            return true;
        }
        private async Task<bool> UpdateStockInternal(CartItem cartItem)
        {
            string azureFunctionBaseUrl = baseURL + "UpdateStock";
            string url = azureFunctionBaseUrl;

            var json = JsonConvert.SerializeObject(cartItem);
            var data = new StringContent(json, Encoding.UTF8, "application/json");

            using (HttpClient client = new HttpClient())
            {
                var response = await client.PutAsync(url, data);
            }

            return true;
        }
        private async Task<List<CartItem>> GetAllCartItemsInternal()
        {
            string url = baseURL + "GetAllCartItems";
            using (HttpClient client = new HttpClient())
            {
                using (HttpResponseMessage responseMessage = await client.GetAsync(url))
                {
                    using (HttpContent content = responseMessage.Content)
                    {
                        string data = await content.ReadAsStringAsync();
                        return JsonConvert.DeserializeObject<List<CartItem>>(data);
                    }
                }
            }
        }
        private async Task<List<Product>> GetAllProductsInternal()
        {
            string url = baseURL + "GetAllProducts";
            using (HttpClient client = new HttpClient())
            {
                using (HttpResponseMessage responseMessage = await client.GetAsync(url))
                {
                    using (HttpContent content = responseMessage.Content)
                    {
                        string data = await content.ReadAsStringAsync();
                        return JsonConvert.DeserializeObject<List<Product>>(data);
                    }
                }
            }
        }
        private async Task<List<Customer>> GetAllCustomersInternal()
        {
            string url = baseURL + "GetAllCustomers";
            using (HttpClient client = new HttpClient())
            {
                using (HttpResponseMessage responseMessage = await client.GetAsync(url))
                {
                    using (HttpContent content = responseMessage.Content)
                    {
                        string data = await content.ReadAsStringAsync();
                        return JsonConvert.DeserializeObject<List<Customer>>(data);
                    }
                }
            }
        }
    }
}

Chame as Funções Azure dos Métodos de API

Cada um dos métodos de ação da classe corresponde a um nanoserviço que implementamos usando funções do Azure anteriormente. Observe que os métodos da classe ter o sufixo “Interno” são privados. Estes são os métodos que realmente se comunicarão com as Funções Azure.ItemCheckOutControllerItemCheckOutController

Como exemplo, o trecho de código a seguir mostra como a API do Núcleo de ASP.NET chama a Função Azure. O método aceita o código do produto como parâmetro e devoluções ou dependendo se o produto está disponível ou não para compra.IsItemAvailableInternaltruefalse

private async Task<bool> IsItemAvailableInternal(string productCode)
{            
    string azureFunctionBaseUrl = baseURL + "IsItemAvailable";
    string queryStringParams = $"?productCode={productCode}";
    string url = azureFunctionBaseUrl + queryStringParams;
    using (HttpClient client = new HttpClient())
    {
        using (HttpResponseMessage responseMessage = await client.GetAsync(url))
        {
            using (HttpContent content = responseMessage.Content)
            {
                string data = await content.ReadAsStringAsync();
                if (data != null)
                    return bool.Parse(data);
                return false;
            }
        }
    }
}

Os outros métodos privados seguem o padrão mostrado acima.

Use a Interface do Swagger UI para testar o aplicativo ItemCheckOut

Agora que os nanoserviços já foram publicados no Azure, você pode executar o aplicativo de microsserviço e, em seguida, invocar os pontos finais da API usando a Interface do Swagger UI com o seu navegador. Swagger está disponível em seu aplicativo ASP.NET graças ao suporte integrado do OpenAPI.ItemCheckOutAPI

A imagem a seguir mostra a saída do ponto final da API chamado usando Swagger UI:GetAllCartItems

image

Nanoserviços: O Futuro

Os microsserviços evoluíram para lidar com as falhas da arquitetura monolítica tradicional. Os nanoserviços são essencialmente microsserviços extra-pequenos com um escopo mais estreito e são mais focados e impulsionados pela computação sem servidor. Embora os nanoserviços sejam adeptos a lidar com as deficiências da arquitetura de microsserviço, eles têm seu próprio conjunto de problemas e desafios.

A tecnologia nanoservice ainda não está lá, mas com a computação sem servidor e soluções sem servidor ficando mais baratas dia após dia, tudo está definido para ser a tecnologia escolhida para construir aplicativos de alto desempenho e escaláveis por um bom tempo. Ele encontrará seu lugar neste mundo onipresente imaginado onde novas tecnologias e arquiteturas são suficientes para as necessidades do mundo empresarial em constante mudança.

A era dos microsserviços extra-pequenos alimentados pela computação sem servidor é inevitável. Por exemplo, o site da BBC é uma aplicação real de nanoserviços usados para renderizar páginas dinâmicas da Web, gerar a manchete, recuperar informações meteorológicas e atualizar pontuações de partidas de críquete, entre outras coisas.

O código-fonte completo do aplicativo ItemCheckOut construído ao longo deste artigo está disponível aqui.


Autor: Joydip Kanjilal

Artigo Original