Este artículo es parte de la colección de contenidos #ServerlessSeptember. Aquí encontrarás todos los artículos publicados durante el mes de septiembre de 2019.
¡Durante este artículo aprenderá cómo migrar de manera simple, rápida y dinámica una aplicación MEAN a una arquitectura Serverless usando Azure Functions!
El proyecto MEAN ya está listo y puedes hacer un git cline o descargarlo AQUI
¡¿Aquí vamos?!
Comprendendo la estructura del proyecto MEAN
En este proyecto nos centraremos en las dos carpetas: api y front. Como la imagen de abajo:
Si ejecuta esta aplicación, notará que estamos persistiendo en MongoDb y estamos usando Back End, que en este caso estamos usando Node.js.
Los datos persistentes consisten en:
Classe: Funcionario
- idFuncionario: (number - guid generado por el MongoDb)
- nomeFuncionario: string
- cargo: string
- numeroIdentificador: number
Si desea ejecutar este proyecto localmente, solo siga los pasos en README.md desde el repositorio del proyecto.
Bueno, ahora que tiene el proyecto MEAN en la mano, ¡comencemos a migrar a Azure Functions?
Pero en primer lugar, vamos a entender lo que serían Azure Functions!
¿Qué son Azure Functions? ⚡️
Azure Functions es un servicio de proceso sin servidor que le permite ejecutar código a petición sin necesidad de aprovisionar ni administrar explícitamente la infraestructura.
Y Azure Functions tiene soporte varias lenguajes de programación, que incluyen:
Ya las lenguajes abajo, ya son compatibles, pero están en su versión preview:
- Bash
- PHP
Si desea conocer más detalles sobre las lenguajes que tiene soporte a Azure Functions, visite el enlace AQUÍ
Sin embargo, para este artículo nos centraremos en JavaScript. 😉
Plantillas importantes en Azure Functions
Antes de comenzar a migrar, es importante mencionar que Azure Functions tiene varias plantillas listas para usar. Entre ellos:
- HTTPTrigger
- TimerTrigger
- CosmosDBTrigger
- BlobTrigger
- QueueTrigger
- EventGridTrigger
- EventHubTrigger
- ServiceBusQueueTrigger
- ServiceBusTopicTrigger
No entraré en detalles de cada una de las plantillas, de lo contrario este artículo será muy grande. Pero si desea comprender más acerca de cada plantilla y cuál es su mejor uso en una aplicación en particular, le recomiendo leer la documentación AQUÍ.
Para este post, vamos a hacer uso de HTTPTrigger ya que esta plantilla activa la ejecución de su código usando una solicitud HTTP. ¡Y eso es justo lo que necesitaremos!
Si es estudiante en cualquier colegio o universidad, puede crear su cuenta de Azure for Students 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 ¡Con esta cuenta puede hacer uso de 1,000,000 solicitudes gratuitas por mes para procesar eventos en Azure Functions!
Bueno, después de esta descripción general de Azure Functions, ¡finalmente podemos comenzar nuestra migración! ¡Vamos adelante!
Instalación del paquete Azure Functions Core Tools
Azure Functions Core Tools le permite desarrollar y probar funciones en el equipo local desde el prompt command del sistema o usando el terminal.
Estos son los programas y el paquete que necesitamos para continuar en nuestro tutorial:
Una vez que tenga Node.js instalado en su máquina, simplemente escriba el siguiente 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 obtener más información para instalar correctamente Azure Functions Core Tools, visite el enlace AQUÍ
¿Y cómo sé si Azure Functions Core Tools están instaladas correctamente? Simplemente escriba el siguiente comando en el terminal:
> func
Si sucede de acuerdo con el siguiente gif, es porque el paquete se instaló correctamente.
Genial! Ahora podemos crear nuestras funciones. Para hacer esto, cree una carpeta local en su máquina y ¡comencemos!
Creando una nueva aplicación en Azure Functions
Ahora que tenemos el paquete instalado, creemos una nueva aplicación. Para hacer esto, solo siga los pasos como el gif:
Tenga en cuenta que cuando abrimos Visual Studio Code, debemos hacer clic en el botón YES
que aparece en la esquina inferior derecha para habilitar algunos archivos importantes en el proyecto.
Creando una conexión en MongoDb
Bueno, ahora hagamos algunos cambios necesarios en nuestro proyecto recién creado. Para esto, instalaremos localmente el mongodb en nuestro proyecto. Escriba el siguiente comando:
> npm install mongodb
Al instalar mongoDb en el proyecto, tenga en cuenta que ha habido cambios en el archivo package.json
. Al final, el archivo debe ser el siguiente:
- archivo: 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"
}
}
Ahora creemos una carpeta llamada: shared
y dentro crearemos el archivo:mongo.js
. La estructura del proyecto ahora se verá de la siguiente manera:
Ahora cambiemos el archivo mongo.js
. Para hacer esto, incluya el siguiente código:
- archivo: 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;
¡Aquí estamos creando nuestra conexión local con MongoDb! ¡Muy parecido a lo que ya hacemos en el Back End con Node.js!
Y también cambiemos el archivo local.settings.json
. Este archivo es responsable de "almacenar" todas las claves que no queremos exponer al realizar el commit. Tenga en cuenta que este archivo está en la lista de archivos en .gitignore
.
Abra el archivo local.settings.json
y realice los siguientes cambios:
- archivo: local.settings.json
{
"IsEncrypted": false,
"Values": {
"FUNCTIONS_WORKER_RUNTIME": "node",
"AzureWebJobsStorage": "{AzureWebJobsStorage}"
},
"Host": {
"LocalHttpPort": 7071,
"CORS": "*"
}
}
Tenga en cuenta en el código anterior que ya estamos habilitando CORS. ¡Porque sin él, no podemos realizar operaciones CRUD en el Front-End! Si quieres entender un poco más sobre CORS te recomiendo leer AQUI.
Bueno, la primera parte está lista! ¡Ahora creemos nuestro CRUD en Azure Functions!
Creando la función 'CreateFuncionario'
Para crear una nueva función, simplemente escriba el siguiente comando:
func new
Ingresar este comando le dará varias opciones de plantilla que Azure Functions pone a nuestra disposición. En nuestro caso, como ya se mencionó anteriormente, elija la plantilla: HttpTrigger
. Siga los pasos del gif:
Tenga en cuenta que se crearon una carpeta CreateFuncionario
y dos archivos:
function.json: Aquí definiremos las rutas y los métodos de nuestro endpoint.
index.json: Aquí desarrollaremos la lógica del endpoint.
Comencemos a alterar estos archivos. Comenzando con function.json
- archivo: CreateFuncionario/function.json
{
"bindings": [{
"authLevel": "anonymous",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": ["post"],
"route": "funcionarios"
},
{
"type": "http",
"direction": "out",
"name": "res"
}
]
}
Ahora cambiemos el archivo index.js
:
- archivo: 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)'
}
}
}
Aquí estamos definiendo prácticamente la ruta POST
y desarrollando la lógica de Crear un nuevo Empleado.
Vamos a ejecutar este endpoint? Para ejecutar, simplemente escriba el siguiente comando:
> func host start
¡Y enumerará nuestro endpoint creado! Mira el gif:
Es el siguiente endpoint: [POST] http://localhost:7071/api/funcionario
La puerta 7071
es la puerta predeterminado para Azure Functions. ¡Y eso es exactamente lo que necesitaremos poner en nuestro Front End!
Bueno, ahora tomemos esta ruta y agréguela al Front End. Para esto necesitamos hacer algunos cambios al proyecto Front
. Vaya a la carpeta front
en:front -> src -> app -> funcionario.service.ts
y cambie el siguiente archivo funcionario.service.ts
- archivo: 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) { }
(...)
Solo necesitamos cambiar el 'uri' definido en el service de Angular.
En este punto, necesitaremos ejecutar Mongo Compass y Front End. ¡Observe en el gif cómo persistirá el nuevo empleado y ya no necesitaremos la carpeta api
del proyecto!
(Haga clic en la imagen para mirar el gif)
¡Persistió maravillosamente! 😍
¡Ahora, hagamos la lista de empleados!
Creando la función 'GetFuncionarios'
Es lo mismo que hicimos anteriormente, creemos una nueva función con el comando: func new
, nombremos la funciónGetFuncionarios
y cambiemos los archivos: function.json
eindex.js
(Haga clic en la imagen para mirar el 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
}
}
¡Intentémoslo de nuevo! ¡Mira el gif de nuevo!
De nuevo está funcionando perfectamente. Ya notó que es fácil crear un CRUD con Azure Functions, ¿no? ¡Ahora siga los mismos pasos para crear las prójimas funciones!
Creando la función 'GetFuncionarioById'
Ahora que está muy claro para todos aquí lo fácil que es crear un CRUD con Azure Functions, comenzaré a acelerar el proceso de creación y solo informaré lo que ha cambiado en los archivos function.json
eindex.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.'
}
}
}
No probemos ahora. Desarrollemos las dos últimas funciones: Update
yDelete
.
Creando la función 'UpdateFuncionario'
Una vez más, vamos a crear una nueva función y cambiar los archivos y index.js
function.json
:
- 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'
}
}
}
Bueno. Ahora desarrollemos nuestra última función: Delete
.
Creando la función 'DeleteFuncionario'
Nuevamente, solo cree una nueva función, elija la opción: HttpTrigger
, nombre la funciónDeleteFuncionario
y cambie los archivosfunction.json
eindex.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
}
}
}
¡Y nuestro CRUD está listo! Vamos a probar todos los endpoints? ¡Mira el gif!
(Haga clic en la imagen para mirar el gif)
Lo más hermoso no es así? Tenga en cuenta nuevamente, que la carpeta api
donde hay numerosos archivos, ¡ya no la necesitará!
Todo el código desarrollado está aquí:
Conclusión
Hoy hemos aprendido cómo migrar una aplicación MEAN a Azure Functions, pero persistiendo estos datos localmente y realizando estas funciones localmente. ¿Qué sucede si necesitamos hacer deploy de esta aplicación en la nube? ¿Y cómo sería nuestro backend?
En el próximo post, le explicaré cómo migrar de MongoDb a CosmosDb y cómo implementar estas funciones con una extensión de Azure Tools en Visual Studio Code.
Si desea conocer más detalles sobre Azure Functions, le recomiendo los siguientes cursos totalmente gratuitos de Serverless & Azure Functions y algunos otros recursos importantes:
✅ Cursos Grátis - Azure Functions
✅ Azure para desarrolladores de JavaScript y Node.js
✅ Documentación de Azure Functions
✅ Creando su primera función mediante Visual Studio Code
✅ VS Code Extension – Azure Functions
✅ E-Book Grátis - Azure Serverless Computing Cookbook
Y para saber esta y otras novedades, ¡sígueme en twitter!
Hasta pronto! ❤️ ❤️ ❤️