Você já ouviu falar de aplicações serverless, mas não sabe por onde começar? Que tal aprender enquanto você cria um “YouTube” sem configurar nenhum servidor?
No artigo de hoje, vamos aprender como desenvolver uma aplicação serverless do 0 utilizando serviços da AWS e o Serverless Framework. Com esse conhecimento, você será capaz de começar o seu próprio projeto e tirar vantagem dessa arquitetura que tem ficado cada vez mais popular entre as principais empresas do mundo.
Vamos lá?
ServerlessTube: o seu “YouTube” sem servidores!
Para aprendermos na prática a criar uma aplicação serverless do 0, vamos desenvolver um “tipo de YouTube”, onde usuários vão conseguir fazer upload, assistir e remover vídeos. Tudo isso sem termos que configurar se quer um servidor.
Esse será o resultado final:
Vamos começar a colocar a mão na massa?
Amazon Web Services: o nosso provedor de computação em nuvem
Apesar de não precisarmos configurar servidores quando desenvolvemos uma aplicação serverless, nós ainda precisamos de um ambiente para executar o nosso código. É ai que entram os provedores de Function-as-a-Service (FaaS).
FaaS são basicamente serviços que executam o seu código sem que você precise se preocupar com o como ele será executado. Toda a parte de configuração de servidores e ambientes é abstraída, fazendo com que você possa focar em apenas desenvolver a sua aplicação.
No nosso caso, vamos utilizar o FaaS da Amazon Web Services (AWS) chamado Lambda.
A AWS é a líder no mercado de nuvens públicas e foi a primeira a disponibilizar um FaaS em larga escala. Desde o anúncio do Lambda na re:Invent de 2014, mais e mais empresas no mundo todo estão usando o serviço para os mais diversos fins.
Para ter acesso ao Lambda, precisamos de uma conta na AWS. Caso você não tenha uma, não se preocupe, você pode criar uma conta gratuita na AWS.
Uma vez que você tiver feito login na sua conta, nós vamos precisar criar uma chave de acesso. Chaves de acesso são utilizadas para autenticar chamadas para a API da AWS, garantindo que apenas usuários autorizados possam fazer alterações na sua conta.
Para criar a sua chave de acesso, você vai precisar acessar o serviço Identity and Access Management (IAM). O IAM é responsável por gerenciar os usuários da sua conta e decidir quem tem acesso ao que.
Caso seja a sua primeira vez utilizando a AWS, todos os serviços podem ser acessados através da barra de pesquisa Localizar serviços. Basta digitar “IAM” e o serviço deve aparecer em primeiro na lista.
Na AWS, toda chave de acesso é associada a um usuário, então vamos precisar criar um usuário novo para o nosso projeto. Para criar usuários, clique em Usuários no menu esquerdo do IAM e depois em Adicionar usuário.
Nosso usuário só vai precisar de acesso programático, então você pode selecionar apenas essa opção, como na imagem abaixo. Agora clique em Próximo: Permissões.
Todo usuário na AWS possui um conjunto de permissões que dizem o que ele pode ou não fazer na plataforma. A AWS oferece uma série de políticas prontas que você pode utilizar, no entanto, é sempre uma boa prática limitar as permissões de um usuário para apenas aquilo que ele realmente precisa.
Para criar a nossa política de permissões, você pode clicar em Anexar políticas existentes de forma direta e depois no botão Criar política. Uma nova aba vai ser aberta no seu browser com duas opções: Editor visual e JSON.
Clique na opção JSON e substitua o exemplo no editor de texto pela política abaixo:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"apigateway:*",
"cloudformation:*",
"iam:CreateRole",
"iam:DeleteRole",
"iam:DeleteRolePolicy",
"iam:GetRole",
"iam:PassRole",
"iam:PutRolePolicy",
"lambda:*",
"logs:*",
"s3:*"
],
"Resource": "*"
}
]}
Agora clique em Revisar política, dê um nome para a sua política na próxima página ( serverlesstube, por exemplo) e clique em Criar política. Depois que a política for criada, volte para a aba do browser onde você estava criando o usuário e clique no botão para atualizar a lista de políticas disponíveis.
Agora digite o nome da política no campo de busca e selecione ela como na imagem acima. Feito isso, você pode clicar em próximo até ver o botão Criar Usuário.
Uma vez que o seu usuário estiver criado, a AWS vai te mostrar a chave de acesso que foi criada. Ela vai ser composta por um ID e por um segredo.
Copie esses dois valores em algum lugar ou baixe o arquivo .csv. Vamos precisar desses valores daqui a pouco.
Agora que você tem a sua chave de acesso, podemos partir para a próxima etapa: instalar o Serverless Framework.
Serverless Framework
Um dos objetivos do Serverless Framework é facilitar a criação de aplicações utilizando o Lambda e outros serviços da AWS. Com ele, você consegue construir a sua aplicação de forma mais rápida e fácil, além de incorporar algumas melhores práticas sem nenhum esforço extra.
Para instalar o framework, você vai precisar ter Node.js v6.5.0 ou mais recente instalado na sua máquina. Caso você precise instalar, os pacotes de instalação podem ser baixados aqui: Pacote de instalação Node.js.
Quando você tiver com o Node.js instalado, basta executar esse comando:
npm install -g serverless
Assim que o processo de instalação terminar, é hora de configurar sua chave de acesso para que possamos começar a criar recursos na AWS.
A configuração pode ser feita através do comando abaixo, substituindo <ID_DA_CHAVE_DE_ACESSO> e <CHAVE_DE_ACESSO_SECRETA> pelos valores que você copiou na seção anterior.
serverless config credentials --provider aws --key <ID_DA_CHAVE_DE_ACESSO> --secret <CHAVE_DE_ACESSO_SECRETA>
Pronto, agora que você está com o Serverless Framework configurado, é hora de criar o código do ServerlessTube!
Hora de assistir o primeiro vídeo no seu ServerlessTube!
Antes de começarmos a criar código, é importante entender qual é a arquitetura do nosso projeto. A imagem abaixo mostra os serviços da AWS envolvidos e o fluxo de dados da aplicação:
Existem três fluxos básicos:
- Upload de um vídeo: O usuário faz o upload pela interface web e o vídeo é salvo diretamente no Serviço S3;
- Listar vídeos disponíveis: A interface web manda uma requisição de listagem para o serviço API Gateway e a requisição é enviada para a função List no Lambda. Essa função vai buscar todos os vídeos disponíveis no S3 e retornar uma lista;
- Remover um vídeo: A interface web manda uma requisição de remoção de um vídeo para o serviço API Gateway e a requisição é enviada para a função Delete no Lambda. Essa função vai remover o vídeo do S3.
Agora que você tem um ideia de como a aplicação vai funcionar, vamos começar a criar o código do nosso projeto.
A primeira coisa é criar um novo diretório onde você vai salvar todos os arquivos do projeto.
Com o diretório criado, vamos começar criando o arquivo serverless.yml. Esse arquivo é utilizado pelo Serverless Framework para saber quais recursos precisam ser criados na AWS e como esses recursos se comunicam.
No nosso caso, precisamos traduzir a arquitetura da imagem acima para o formato que o framework consegue entender. Dessa forma, não vamos precisar fazer nada manualmente pelo console da AWS, tudo será criado de forma automatizada com apenas um comando.
O código abaixo descreve todos os elementos da nossa aplicação. Basta salvá-lo no diretório do seu projeto com o nome serverless.yml, substituindo <NOME_DO_SEU_BUCKET> por algum nome único, por exemplo, serverlesstube-COLOQUE_O_SEU_NOME_AQUI.
service: serverlesstube
frameworkVersion: ">=1.1.0"
custom:
bucket: <NOME_DO_SEU_BUCKET>
provider:
name: aws
runtime: nodejs8.10
stage: dev
region: us-east-1
iamRoleStatements:
- Effect: Allow
Action:
- s3:*
Resource:
- arn:aws:s3:::${self:custom.bucket}
- arn:aws:s3:::${self:custom.bucket}/*
functions:
list:
handler: serverlesstube.list
events:
- http:
path: video
method: get
cors: true
environment:
BUCKET: ${self:custom.bucket}
delete:
handler: serverlesstube.delete
events:
- http:
path: video/{id}
method: delete
cors: true
environment:
BUCKET: ${self:custom.bucket}
resources:
Resources:
s3Bucket:
Type: AWS::S3::Bucket
Properties:
BucketName: ${self:custom.bucket}
Agora que definimos a infraestrutura da nossa aplicação, precisamos criar o código das funções Lambda que vão lidar com as chamadas da nossa interface web para listar e remover vídeos.
O código abaixo expõe duas funções que vão ser utilizadas pelo Lambda: list para fornecer a lista de vídeos disponíveis e delete para remover vídeos. Salve esse código no diretório do seu projeto em um arquivo chamado serverlesstube.js.
const AWS = require("aws-sdk");
const s3 = new AWS.S3();
exports.list = async event => {
var response = {
statusCode: 200,
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Credentials": true
}
};
try {
const params = {
Bucket: process.env.BUCKET
};
const videos = await s3.listObjects(params).promise();
response.body = JSON.stringify(videos);
return response;
} catch (e) {
response.statusCode = 500;
response.body = JSON.stringify(e);
throw new Error(response);
}
};
exports.delete = async event => {
var response = {
statusCode: 200,
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Credentials": true
}
};
try {
const params = {
Bucket: process.env.BUCKET,
Key: event.pathParameters.id.replace(/%20/g, " ")
};
await s3.deleteObject(params).promise();
response.body = JSON.stringify("Video deleted successfully!");
return response;
} catch (e) {
response.statusCode = 500;
response.body = JSON.stringify(e);
throw new Error(response);
}
};
Depois que o arquivo estiver salvo, você deve ter dois arquivos no diretório do seu projeto: serverless.yml e serverlesstube.js. Agora abra um terminal ou prompt de comando e navegue até o diretório do seu projeto.
Quando chegar lá, digite o comando abaixo para fazer o deploy da nossa aplicação:
sls deploy
Ao final do deploy, você deve ver um resultado parecido com esse:
Service Information
service: serverlesstube
stage: dev
region: us-east-1
stack: serverlesstube-dev
api keys:
None
endpoints:
GET - https://XXX.execute-api.us-east-1.amazonaws.com/dev/video
DELETE - https://XXX.execute-api.us-east-1.amazonaws.com/dev/video/{id}
functions:
list: serverlesstube-dev-list
delete: serverlesstube-dev-delete
Nesse momento, o único valor que importa para nós é o endpoint criado. Esse valor vai ser necessário para configurar a interface web de forma que ela possa se comunicar com a API criada pelo Serverless Framework.
O endpoint deve ser algo parecido com o endereço abaixo:
https://XXX.execute-api.us-east-1.amazonaws.com/dev/video
Com o endereço da sua API em mãos, salve o código abaixo em um arquivo chamado index.html, alterando os valores das variáveis declaradas nas linhas 5 a 8:
- <ID_DA_CHAVE_DE_ACESSO>: é o ID da chave de acesso que criamos na AWS;
- <CHAVE_DE_ACESSO_SECRETA>: é a chave de acesso secreta que criamos na AWS;
- <NOME_DO_SEU_BUCKET>: é o nome do bucket que você definiu no seu arquivo serverless.yml;
- <ENDERECO_DA_SUA_API>: é o endpoint que você copiou no passo anterior.
<!DOCTYPE html>
<html lang="en">
<head>
<script>
var ACCESS_KEY_ID = "<ID_DA_CHAVE_DE_ACESSO>";
var SECRET_ACCESS_KEY = "<CHAVE_DE_ACESSO_SECRETA>";
var BUCKET_NAME = "<NOME_DO_SEU_BUCKET>";
var API_ADDRESS = "<ENDERECO_DA_SUA_API>";
</script>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
<meta name="description" content="" />
<meta name="author" content="" />
<title>ServerlessTube</title>
<link
rel="stylesheet"
href="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css"
integrity="sha384-GJzZqFGwb1QTTN6wy59ffF1BuGJpLSa9DkKMp0DgiMDm4iYMj70gZWKYbI706tWS"
crossorigin="anonymous"
/>
<style>
body {
overflow-x: hidden;
}
#sidebar-wrapper {
min-height: 100vh;
margin-left: -15rem;
-webkit-transition: margin 0.25s ease-out;
-moz-transition: margin 0.25s ease-out;
-o-transition: margin 0.25s ease-out;
transition: margin 0.25s ease-out;
}
#sidebar-wrapper .sidebar-heading {
padding: 0.875rem 1.25rem;
font-size: 1.2rem;
margin-left: -15px;
}
#sidebar-wrapper .logo img{
margin: 5px 5px;
}
#sidebar-wrapper .list-group {
width: 15rem;
}
#page-content-wrapper {
min-width: 100vw;
}
#wrapper.toggled #sidebar-wrapper {
margin-left: 0;
}
@media (min-width: 768px) {
#sidebar-wrapper {
margin-left: 0;
}
#page-content-wrapper {
min-width: 0;
width: 100%;
}
#wrapper.toggled #sidebar-wrapper {
margin-left: -15rem;
}
}
</style>
</head>
<body>
<div class="d-flex" id="wrapper">
<div class="bg-light border-right" id="sidebar-wrapper">
<div class="row">
<div class="logo text-right col-sm-4">
<img src="https://raw.githubusercontent.com/cloudnapratica/serverlesstube/master/images/logo.png" height="50px"/>
</div>
<div class="sidebar-heading col-sm-6" >ServerlessTube</div>
</div>
<div class="row">
<div class="sidebar-heading text-center col-sm-12">Vídeos Disponíveis</div>
</div>
<div id="video-list" class="list-group list-group-flush">
</div>
</div>
<div id="page-content-wrapper">
<div id="upload" class="container-fluid">
<h1 id="video-title" class="col-ms-4"></h1>
<video width="1280" height="720" id="video-player" controls></video>
<div id="video-controls" class="row">
</div>
</br>
<h2 class="mt-4">Faça o upload de um vídeo</h2>
<form
id="upload-form"
action="/"
method="post"
enctype="multipart/form-data"
>
<div class="form-group">
<label for="key">Título do Vídeo</label>
<input
type="input"
class="form-control"
id="key"
aria-describedby="Título do Vídeo"
placeholder="Título do Vídeo"
name="key"
/>
</div>
<div>
<input type="hidden" name="acl" value="public-read" />
<input type="hidden" name="Content-Type" value="video/mp4" />
<input
type="hidden"
name="x-amz-meta-uuid"
value="14365123651274"
/>
<input
type="hidden"
name="x-amz-server-side-encryption"
value="AES256"
/>
<input
id="credential"
type="hidden"
name="X-Amz-Credential"
value=""
/>
<input
type="hidden"
name="X-Amz-Algorithm"
value="AWS4-HMAC-SHA256"
/>
<input id="amz-date" type="hidden" name="X-Amz-Date" value="" />
<input type="hidden" name="x-amz-meta-tag" value="" />
<input id="policy" type="hidden" name="Policy" value="" />
<input
id="signature"
type="hidden"
name="X-Amz-Signature"
value=""
/>
</div>
<div class="form-group">
<label for="file">Arquivo</label>
<input type="file" class="form-control" id="file" name="file" />
</div>
<input
type="submit"
name="submit"
value="Upload"
class="btn btn-primary mb-2"
/>
</form>
</div>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9-1/crypto-js.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9-1/hmac-sha256.min.js"></script>
<script
src="http://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous"
></script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.6/umd/popper.min.js"
integrity="sha384-wHAiFfRlMFy6i5SRaxvfOCifBUQy1xHdJ/yoi7FRNXMRBu5WHdZYu1hA6ZOblgut"
crossorigin="anonymous"
></script>
<script
src="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/js/bootstrap.min.js"
integrity="sha384-B0UglyR+jN6CkvvICOB2joaf5I4l3gm9GU6Hc1og6Ls7i6U/mkkaduKaBhlAXv9k"
crossorigin="anonymous"
></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.form/4.2.2/jquery.form.min.js" integrity="sha384-FzT3vTVGXqf7wRfy8k4BiyzvbNfeYjK+frTVqZeNDFl8woCbF0CYG6g2fMEFFo/i" crossorigin="anonymous"></script>
<script>
var datetime = new Date();
var date = datetime
.toISOString()
.split("T")[0] .replace(/-/g, "");
datetime.setMinutes(datetime.getMinutes() + 30);
var policy = {
expiration: datetime.toISOString(),
conditions: [
{ bucket: BUCKET_NAME },
["starts-with", "$key", ""],
{ acl: "public-read" },
["starts-with", "$Content-Type", "video/mp4"],
{ "x-amz-meta-uuid": "14365123651274" },
{ "x-amz-server-side-encryption": "AES256" },
["starts-with", "$x-amz-meta-tag", ""],
{
"x-amz-credential":
ACCESS_KEY_ID + "/" + date + "/us-east-1/s3/aws4_request"
},
{ "x-amz-algorithm": "AWS4-HMAC-SHA256" },
{ "x-amz-date": date + "T000000Z" }
] };
var b64policy = btoa(JSON.stringify(policy));
document.getElementById("policy").value = b64policy;
var dateKey = CryptoJS.HmacSHA256(date, "AWS4" + SECRET_ACCESS_KEY);
var dateRegionKey = CryptoJS.HmacSHA256("us-east-1", dateKey);
var dateRegionServiceKey = CryptoJS.HmacSHA256("s3", dateRegionKey);
var signingKey = CryptoJS.HmacSHA256(
"aws4_request",
dateRegionServiceKey
);
var signature = CryptoJS.HmacSHA256(b64policy, signingKey);
document.getElementById("signature").value = signature.toString(
CryptoJS.enc.Hex
);
document.getElementById("credential").value =
ACCESS_KEY_ID + "/" + date + "/us-east-1/s3/aws4_request";
document.getElementById("amz-date").value = date + "T000000Z";
$.get(
API_ADDRESS,
function(data, status) {
var list = document.getElementById("video-list");
data.Contents.forEach(function(video) {
var item = document.createElement("a");
item.id = video.Key;
item.innerHTML = video.Key.split(".mp4")[0];
item.className = "list-group-item list-group-item-action bg-light";
item.href = "#";
item.addEventListener("click", updateVideoSource);
list.appendChild(item);
});
}
);
$(document).ready(function() {
document.getElementById("upload-form").action = "http://" + BUCKET_NAME + ".s3.amazonaws.com/";
});
function updateVideoSource(ev) {
document.getElementById("video-title").innerHTML = ev.target.id.split(".mp4")[0];
var oldSource = document.getElementById("video-source");
if (oldSource) {
oldSource.remove();
}
var source = document.createElement('source');
source.id = "video-source";
source.setAttribute("src", "https://s3.amazonaws.com/" + BUCKET_NAME + "/" + ev.target.id);
document.getElementById("video-player").appendChild(source);
document.getElementById("video-player").load();
var controls = document.getElementById("video-controls");
var oldControl = document.getElementsByName("delete-video");
if (oldControl[0]) {
oldControl[0].remove();
}
var button = document.createElement("a");
button.name = "delete-video";
button.id = ev.target.id;
button.innerHTML = "Remover vídeo";
button.className = "btn btn-danger";
button.style = "margin-left: 14px;";
button.href = "#";
button.addEventListener("click", deleteVideo);
controls.appendChild(button);
}
function deleteVideo(ev) {
$.ajax({
url: API_ADDRESS + "/" + ev.target.id,
type: 'DELETE',
success: function(result) {
location.reload();
}
});
}
</script>
</body>
</html>
Agora basta abrir o arquivo index.html no seu browser e fazer o upload de um vídeo. Caso você precise de um vídeo de exemplo, existem alguns aqui.
Depois de dar um título e escolher o arquivo, aguarde até que o upload do arquivo termine.
Depois que o upload do vídeo estiver completo, recarregue a página. O título que você deu ao vídeo deve aparecer na lista do lado esquerdo.
Clique nele para assistir o primeiro vídeo no seu ServerlessTube!
Limpando a sua conta da AWS
Quando você decidir remover o projeto da sua conta, primeiro remova todos os vídeos pela interface web, caso contrário você vai ver um erro quando tentar executar o comando abaixo.
Depois que você tiver removido todos os vídeos, basta executar o comando abaixo em um terminal ou prompt de comando no diretório do seu projeto:
sls remove
No final da remoção, você verá um resultado como o abaixo, mostrando que todos os recursos foram removidos com sucesso:
Serverless: Getting all objects in S3 bucket...
Serverless: Removing objects in S3 bucket...
Serverless: Removing Stack...
Serverless: Checking Stack removal progress...
....................................
Serverless: Stack removal finished...
Para proteger a sua conta da AWS, remova também o usuário que criamos para o projeto. Basta acessar o serviço IAM novamente, ir para a lista de usuários, selecionar o usuário e clicar em Excluir usuário.
Próximos Passos
Nesse artigo você aprendeu como criar uma aplicação serverless do 0 utilizando a AWS e o Serverless Framework. É claro que existem muitas coisas que não conseguimos cobrir aqui, como autenticação, testes, diferentes ambientes (dev, uat, prod), hospedagem da interface web, etc.
Qual aspecto de aplicações serverless você tem mais interesse em aprender? Entre em contato, terei o maior prazer em te ajudar!
Caso você precise, o código fonte do ServerlessTube também está disponível neste repositório.
Obrigado e até o próximo post!