Este artigo faz parte da coleção de conteúdos do #ServerlessSeptember. Aqui você encontrará todos os artigos publicados durante o mês de Setembro de 2019.
Durante esse artigo você aprenderá a realizar a migração de uma maneira simples, rápida e dinâmica de uma aplicação MEAN para uma arquitetura Serverless, fazendo uso do Azure Functions!
O projeto MEAN já está pronto e vocês podem fazer um git clone ou download AQUI
E, caso desejam ver a palestra sobre o assunto mencionado bastam ver o vídeo abaixo da palestra dada na BrazilJs de 2019, onde eu falo justamente sobre Azure Functions + Node.js + Arquitetura Serverless (agradeço imensamente a BrazilJs pelo convite e a disponibilização do vídeo):
YouTube:
Vamos nessa?!
Entendendo a estrutura do Projeto MEAN
Nesse projeto vamos focar nas duas pastas: api e front. Conforme a imagem abaixo:
Se vocês executarem essa aplicação observarão que estamos persistindo essa aplicação no MongoDb e usando o Back-End, que nesse caso estamos usando o Node.js
Os dados persistidos consiste em:
Classe: Funcionario
- idFuncionario: (number - guid gerado pelo MongoDb)
- nomeFuncionario: string
- cargo: string
- numeroIdentificador: number
Caso desejam executar localmente esse projeto, bastam seguir os passos no README.md do repositório do projeto.
Bom, agora que vocês estão com o projeto MEAN em mãos, vamos começar a fazer a migração para o Azure Functions?!
Mas antes, vamos entender o que seria o Azure Functions!
O que é Azure Functions?! ⚡️
Azure Functions é um serviço de computação serverless que permite executar o facilmente pequenos trechos de código ou funções na nuvem sobre demanda sem precisar provisionar ou gerenciar a infraestrutura.
E o Azure Functions possui suporte a inúmeras linguagens, entre elas:
Já as linguagens abaixo, já possui suporte, porém estão na sua versão preview:
- Bash
- PHP
Se desejarem saber mais detalhes das linguagens que possuem suporte ao Azure Functions, bastam acessar o link AQUI.
Porém, para esse artigo focaremos no JavaScript! 😉
Templates Importantes no Azure Functions
Antes de começar a realizar a migração é importante mencionar que o Azure Functions dispõe de inúmeros templates já prontos e preparados só para começar a serem usados. Entre eles:
- HTTPTrigger
- TimerTrigger
- CosmosDBTrigger
- BlobTrigger
- QueueTrigger
- EventGridTrigger
- EventHubTrigger
- ServiceBusQueueTrigger
- ServiceBusTopicTrigger
Não entrarei em detalhes de cada um, pois senão esse artigo ficará muito grande. Mas, caso desejam entender mais sobre cada template e qual o seu melhor uso numa determinada aplicação, recomendo a leitura na documentação AQUI.
Para esse post, estaremos fazendo uso do template: HTTPTrigger uma vez que esse template dispara a execução do seu código usando uma solicitação HTTP. E é justamente o que precisaremos para realizar a migração!
Caso você seja um(a) estudante de alguma Instituição de Ensino de Faculdade ou Universidade, poderá criar sua conta no Azure for Students. Essa conta te dará o benefício em possuir crédito de USD 100,00 para usar os serviços de maneira gratuita, sem necessidade de possuir um cartão de crédito. Para ativar essa conta, bastam acessar o link ao lado: AQUI. Com essa conta, você poderá fazer uso de 1.000.000 solicitações gratuitas por mês para processar eventos no Azure Functions!
Bom, depois desse overview sobre Azure Functions, podemos enfim começar a nossa migração! Vamos que vamos!
Instalando o pacote Azure Functions Core Tools
O Azure Functions Core Tools nos permitirá desenvolver e testar as funções de maneira local na nossa máquina a partir de um terminal ou prompt de comando.
Abaixo seguem os programas e o pacote que precisaremos para continuar no nosso tutorial:
Depois que você tiver o Node.js instalado na sua máquina, basta digitar o seguinte comando:
- Windows
npm install -g azure-functions-core-tools
- MacOs
brew tap azure/functions
brew install azure-functions-core-tools
- Linux (Ubuntu/Debian) com APT
curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.gpg
sudo mv microsoft.gpg /etc/apt/trusted.gpg.d/microsoft.gpg
Para maiores informações para instalar de maneira correta o Azure Functions Core Tools, bastam acessar o link AQUI
E como vou saber se de fato o meu Azure Functions Core Tools está instalado de maneira correta na minha máquina?! Bastam digitar o seguinte comando no terminal:
> func
Se acontecer conforme o gif abaixo é porque o pacote foi instalado com sucesso!
Ótimo. Agora, podemos criar as nossas funções. Para isso, crie um pasta local na sua máquina e vamos começar!
Criando uma Nova Aplicação no Azure Functions
Agora que já temos instalado o pacote, vamos criar uma nova aplicação. Para isso, bastam seguir o passos conforme o gif abaixo:
Observam que, quando abrimos o Visual Studio Code, precisamos clicar no botão YES
que aparece no canto inferior direito para habilitar alguns arquivos importantes no projeto.
Criando a Conexão com o MongoDb
Bom, vamos fazer agora algumas alterações necessárias no nosso projeto recém criado. Para isso, vamos instalar localmente o mongodb no nosso projeto. Digitem o seguinte comando:
> npm install mongodb
Ao instalar o mongoDb no projeto, observem que houve alterações no arquivo package.json
. No final o arquivo deverá ficar da seguinte maneira:
- arquivo: package.json
{
"name": "crud-serverless-mongodb",
"version": "1.0.0",
"description": "Projeto azure functions com persistencia com o mongoDb",
"scripts": {
"test": "echo \"No tests yet...\""
},
"author": "",
"dependencies": {
"mongodb": "^3.3.2"
}
}
Agora, vamos criar uma pasta chamada: shared
e dentro dela vamos criar o arquivo: mongo.js
. A estrutura do projeto agora ficará da seguinte maneira:
Vamos agora alterar o arquivo mongo.js
. Para isso, inclua o bloco de código abaixo:
- arquivo: shared/mongo.js
/**
* Arquivo: mongo.js
* Data: 10/11/2019
* Descrição: arquivo responsável por tratar a conexão da Base de Dados localmente
* Author: Glaucia Lemos
*/
const { MongoClient } = require("mongodb");
const config = {
url: "mongodb://localhost:27017/crud-serverless-mongodb",
dbName: "crud-serverless-mongodb"
};
async function createConnection() {
const connection = await MongoClient.connect(config.url, {
useNewUrlParser: true
});
const db = connection.db(config.dbName);
return {
connection,
db
};
}
module.exports = createConnection;
Aqui estamos criando a nossa conexão local com o MongoDb! Muito parecido com o que já fazemos no Back-End com o Node.js, não é mesmo?!
E vamos também alterar o arquivo local.settings.json
. Esse arquivo é responsável por 'guardar' todas as keys que não queremos que estejam expostas na hora de realizar o commit. Notem que esse arquivo está na lista de arquivos no .gitignore
.
Abram o arquivo local.settings.json
e façam as seguintes alterações:
- arquivo: local.settings.json
{
"IsEncrypted": false,
"Values": {
"FUNCTIONS_WORKER_RUNTIME": "node",
"AzureWebJobsStorage": "{AzureWebJobsStorage}"
},
"Host": {
"LocalHttpPort": 7071,
"CORS": "*"
}
}
Notem no bloco de código acima que já estamos habilitando o CORS
. Pois sem ele, não conseguimos realizar as operações de CRUD no front! Se desejarem entender um pouco mais sobre o CORS recomendo a leitura AQUI.
Bom, a primeira parte já está pronta! Agora vamos criar o nosso CRUD no Azure Functions!
Criando a função 'CreateFuncionario'
Para criar uma nova função bastam digitar o seguinte comando:
func new
Ao digitar esse comando ele dará várias opções de templates que o Azure Functions nos disponibiliza. No nosso caso, conforme já mencionado acima, vamos escolher o template: HttpTrigger
. Sigam os passos do gif abaixo:
Observem que, foi criado uma pasta CreateFuncionario
e dois arquivos:
function.json: aqui iremos definir as rotas e os métodos do nosso endpoint.
index.json: aqui iremos desenvolver a lógica inerente ao endpoint.
Vamos começar a alterar esses arquivos. Começando pelo function.json
- arquivo: CreateFuncionario/function.json
{
"bindings": [{
"authLevel": "anonymous",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": ["post"],
"route": "funcionarios"
},
{
"type": "http",
"direction": "out",
"name": "res"
}
]
}
Agora vamos alterar o arquivo index.js
:
- arquivo: CreateFuncionario/index.js
/**
* Arquivo: CreateFuncionario/index.js
* Data: 10/11/2019
* Descrição: arquivo responsável por criar um novo 'Funcionário'
* Author: Glaucia Lemos
*/
const createMongoClient = require('../shared/mongo')
module.exports = async function (context, req) {
const funcionario = req.body || {}
if (funcionario) {
context.res = {
status: 400,
body: 'Os dados do(a) Funcionário(a) é obrigatório!'
}
}
const { db, connection } = await createMongoClient()
const Funcionarios = db.collection('funcionarios')
try {
const funcionarios = await Funcionarios.insert(funcionario)
connection.close()
context.res = {
status: 201,
body: funcionarios.ops[0]
}
} catch (error) {
context.res = {
status: 500,
body: 'Error ao criar um novo Funcionário(a)'
}
}
}
Aqui estamos praticamente definindo a rota do Post
e desenvolvendo a lógica do Criar um novo Funcionário
.
Vamos executar esse endpoint?! Para executar, bastam digitar o seguinte comando:
> func host start
E ele irá listar o nosso endpoint criado! Vejam no gif:
Ele lista para nós o seguinte endpoint: [POST] http://localhost:7071/api/funcionario
A porta 7071
é a porta default do Azure Functions. E é justamente ela que iremos precisar para colocar no nosso Front-End!
Bom, vamos agora pegar essa rota e adicionar no Front-End! Para isso, precisamos realizar algumas alterações no projeto Front
. Vão até a pasta front
em: front -> src -> app -> funcionario.service.ts
e alterem o seguinte arquivo funcionario.service.ts
- arquivo: funcionario.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Injectable({
providedIn: 'root'
})
export class FuncionarioService {
// ==> Uri da api (Back-End)
uri = 'http://localhost:7071/api';
constructor(private http: HttpClient) { }
(...)
Só precisamos alterar a uri
definida no service do Angular.
Nesse momento, precisaremos executar o Mongo Compass e o Front-End. Observem no gif como persistirá o novo funcionário e que não precisaremos mais da pasta api
do projeto!
(Clique na imagem abaixo para visualizar o gif)
Persistiu lindamente! 😍
Agora, vamos fazer o listar!
Criando a função 'GetFuncionarios'
É a mesma premissa que foi feita acima, vamos criar uma nova função com o comando: func new
, nomear a função de GetFuncionarios
e alterar os arquivos: function.json
e index.js
(Clique na imagem abaixo para visualizar o gif)
- GetFuncionarios/function.json
{
"bindings": [{
"authLevel": "anonymous",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": ["get"],
"route": "funcionarios"
},
{
"type": "http",
"direction": "out",
"name": "res"
}
]
}
- GetFuncionarios/index.js
/**
* Arquivo: GetFuncionarios/index.js
* Data: 10/11/2019
* Descrição: arquivo responsável por listar todos os 'Funcionários'
* Author: Glaucia Lemos
*/
const createMongoClient = require('../shared/mongo')
module.exports = async context => {
const { db, connection } = await createMongoClient()
const Funcionarios = db.collection('funcionarios')
const res = await Funcionarios.find({})
const body = await res.toArray()
connection.close()
context.res = {
status: 200,
body
}
}
Vamos testar novamente! Vejam novamente o gif abaixo!
Novamente está funcionando perfeitamente. Já observaram que é fácil criar um CRUD com o Azure Functions, não é mesmo?! Agora é só seguir os mesmos passos para criar as próximas funções!
Criando a função 'GetFuncionarioById'
Agora que já ficou muito claro a todos aqui como é fácil criar um CRUD com o Azure Functions, vou começar a acelerar o processo de criação e só informar o que foi alterado nos arquivos function.json
e index.js
- GetFuncionarioById/index.js
{
"bindings": [{
"authLevel": "anonymous",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": ["get"],
"route": "funcionarios/{id}"
},
{
"type": "http",
"direction": "out",
"name": "res"
}
]
}
- GetFuncionarioById/function.json
// @ts-nocheck
/**
* Arquivo: GetFuncionarioById/index.js
* Data: 10/11/2019
* Descrição: arquivo responsável por listar Funcionário pelo Id
* Author: Glaucia Lemos
*/
const { ObjectID } = require('mongodb')
const createMongoClient = require('../shared/mongo')
module.exports = async function (context, req) {
const { id } = req.params
if (!id) {
context.res = {
status: 400,
body: 'Por favor, passe o número correto do Id do Funcionário!'
}
return
}
const { db, connection } = await createMongoClient()
const Funcionarios = db.collection('funcionarios')
try {
const body = await Funcionarios.findOne({ _id: ObjectID(id) })
connection.close()
context.res = {
status: 200,
body
}
} catch (error) {
context.res = {
status: 500,
body: 'Erro ao listar o Funcionário pelo Id.'
}
}
}
Não vamos testar agora. Vamos desenvolver as duas últimas funções: Update
e Delete
.
Criando a função: 'UpdateFuncionario'
Novamente, vamos criar uma nova função e alterar os arquivos function.json
e index.js
:
- UpdateFuncionario/index.js
{
"bindings": [{
"authLevel": "anonymous",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": ["put"],
"route": "funcionarios/{id}"
},
{
"type": "http",
"direction": "out",
"name": "res"
}
]
}
- UpdateFuncionario/index.js
// @ts-nocheck
/**
* Arquivo: UpdateFuncionario/index.js
* Data: 10/11/2019
* Descrição: arquivo responsável por atualizar 'Funcionário' por Id
* Author: Glaucia Lemos
*/
const { ObjectID } = require('mongodb')
const createMongoClient = require('../shared/mongo')
module.exports = async function (context, req) {
const { id } = req.params
const funcionario = req.body || {}
if (!id || !funcionario) {
context.res = {
status: 400,
body: 'Os campos são obrigatórios'
}
return
}
const { db, connection } = await createMongoClient()
const Funcionarios = db.collection('funcionarios')
try {
const funcionarios = await Funcionarios.findOneAndUpdate(
{ _id: ObjectID(id) },
{ set: funcionario }
)
connection.close()
context.res = {
status: 200,
body: funcionarios
}
} catch (error) {
context.res = {
status: 500,
body: 'Erro ao atualizar o Funcionário'
}
}
}
Show! Agora vamos desenvolver a nossa última função: Delete
!
Criando a função: 'DeleteFuncionario'
Novamente, bastam criar uma nova função, escolher a opção: HttpTrigger
, nomear a função de DeleteFuncionario
e alterar os arquivos function.json
e index.js
:
- DeleteFuncionario/function.json
{
"bindings": [{
"authLevel": "anonymous",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": ["delete"],
"route": "funcionarios/{id}"
},
{
"type": "http",
"direction": "out",
"name": "res"
}
]
}
- DeleteFuncionario/index.js
// @ts-nocheck
/**
* Arquivo: DeleteFuncionario/index.js
* Data: 10/11/2019
* Descrição: arquivo responsável excluir um 'Funcionário' pelo Id
* Author: Glaucia Lemos
*/
const { ObjectID } = require('mongodb')
const createMongoClient = require('../shared/mongo')
module.exports = async function (context, req) {
const { id } = req.params
if (!id) {
context.res = {
status: 400,
body: 'Os campos são obrigatórios!'
}
return
}
const { db, connection } = await createMongoClient()
const Funcionarios = db.collection('funcionarios')
try {
await Funcionarios.findOneAndDelete({ _id: ObjectID(id) })
connection.close()
context.res = {
status: 204,
body: 'Funcionário excluído com sucesso!'
}
} catch (error) {
context.res = {
status: 500,
body: 'Erro ao excluir Funcionário' + id
}
}
}
E está pronto o nosso CRUD! Vamos testar todos os endpoints?! Vejam o gif abaixo!
(Clique na imagem abaixo para visualizar o gif)
Coisa mais linda, não é mesmo?! Notem mais uma vez que, aquela pasta api
onde há inúmeros arquivos, não terá mais necessidade! Podemos praticamente deletar aquela pasta inteira!!!
Todo o código fonte desenvolvido estão aqui:
Palavras Finais
Hoje aprendemos a realizar a migração de uma aplicação MEAN para o Azure Functions, porém persistindo esses dados localmente e executando essas funções localmente. E se porventura precisarmos hospedar essa aplicação na Nuvem? E o nosso Back-end como ficaria?
No próximo post, estarei explicando a vocês como realizar a migração do MongoDb para o CosmosDb e como realizar o deploy dessas funções usando uma extensão do Azure Tools no próprio Visual Studio Code.
Se vocês desejam saber mais detalhes sobre Azure Functions recomendo a todos vocês os seguintes cursos totalmente gratuitos de Serverless & Azure Functions e alguns outros recursos importantes:
✅ Cursos Grátis - Azure Functions
✅ Azure para devs JavaScript & Node.js
✅ Documentação Azure Functions
✅ Criando a sua Primeira Função no Visual Studio Code
✅ Extensão Vs Code – Azure Functions
✅ E-Book Grátis - Azure Serverless Computing Cookbook
E, para ficarem por dentro das últimas atualizações não deixem de me seguir no Twitter! 😃
Até a próxima pessoal! ❤️ ❤️ ❤️