O blog da AWS
Uma Plataforma SaaS Multi-Tenant Para Etiquetagem Automática de Vídeos – Parte I, o Front-End
Por Marcelo França, Arquiteto de Soluções Senior em Partner AWS Brasil
Introdução
Este é o primeiro blog post em uma série que descreve de ponta-a-ponta uma solução para análise (etiquetagem) automática de conteúdo em vídeo utilizando Amazon Rekognition – um serviço de deep learning (tipo específico de machine learning) capaz de identificar objetos, eventos (ex.: casamentos), e conceitos (ex.: natureza). A solução, ofertada aos clientes como uma plataforma SaaS, é 100% serverless, permitindo que o modelo silo[1] para software multi-tenant possa ser adotado, sem o efeito colateral de custos fixos mínimos repetidos em cada cliente. Neste modelo, diferentemente dos modelos pool e bridge, cada tenant (cliente) possui sua própria infraestrutura em uma conta AWS em separado – uma boa prática chamada de “estratégia multi-account”[2]. Por fim, o processo DevOps chamado Infrastructure as a Code (IaaC) permite que o onboarding de novos clientes dure minutos, já que toda a infraestrutura de TI é provisionada de forma automatizada.
A solução está dividida em três camadas: User Interface (o foco deste primeiro blog post), Microservices, e Data Store – as duas últimas serão objetos de posts futuros. Partindo da necessidade do cliente final, a expectativa é a de que, ao invés de uma equipe dedicada a assistir e catalogar cada um dos vídeos, todo o processo seja automatizado a partir do upload do arquivo, permitindo posteriormente a consulta a partir de uma palavra-chave. Por exemplo, ao se procurar por “wedding”, deverão ser listados todos os vídeos contendo cenas de casamento, para que o usuário final possa escolher reproduzir ou realizar o download dos mesmos. Assim, acelera-se tanto o processo de ingestão de conteúdo, mas também o de análise e consumo. Vale salientar que, apesar do serviço Amazon Rekognition ser capaz de retornar o segundo do vídeo onde o objeto, evento ou conceito foi detectado, bem como o número de instâncias detectadas e suas coordenadas (ex.: vários carros estacionados em uma rua, como na Figura 1), por simplicidade, esta solução apenas considerou se o elemento foi encontrado ou não em determinado vídeo.
Visão Geral da Arquitetura da Solução
Como pode ser visto na Figura 2 (AOD ou Architectural Overview Diagram), o usuário de um tenant, através do navegador, irá acessar uma aplicação Web Single-Page Application (SPA) escrita em Angular. Para tanto, sua requisição passará primeiro pelo serviço de DNS Amazon Route 53, que o redirecionará para a rede de distribuição de conteúdo (CDN) do serviço Amazon CloudFront. Este, entrega globalmente, com baixa latência, os arquivos armazenados em um bucket privado – Amazon Simple Storage Service (Amazon S3) como serviço de armazenagem de objetos (Passo 1). A solução conta com três serviços de proteção: AWS Shield (ex.: DDoS), AWS WAF (ex.: SQL Injection), e Amazon Cognito (autenticação e autorização). Assim sendo, para acessar APIs protegidas, o usuário terá que primeiro se autenticar, fornecendo e-mail e senha, através do serviço Amazon Cognito (Passo 2), obtendo um token JWT, seguindo o protocolo OAuth 2.0.
Como implementar autenticação e construir telas de login pode ser considerado um “trabalho pesado que não faz diferença”, alavancamos os recursos nativos do Amazon Cognito, em especial seu diretório de usuários (User Pool) e UI. Ao mesmo tempo, na camada de User Interface da solução, ou front-end, utilizamos o AWS Amplify como acelerador. Ele é um conjunto de ferramentas composto por uma biblioteca JavaScript de código-aberto [3], interface de linha de comando (CLI) e console, além de serviços de hospedagem, com o objetivo de agilizar o desenvolvimento de aplicações móveis e para a Web. Veremos a seguir como o Amplify simplifica a interação com o Amazon S3, CloudFront, Amazon Cognito e Amazon API Gateway – este último responsável pelo gerenciamento das APIs dos microsserviços (back-end), objetos de um futuro post nesta série.
Passo a passo
Aqui iremos construir e publicar a aplicação front-end, assumindo que as APIs já tenham sido publicadas, bem como os usuários já tenham sido cadastrados. Iremos criar uma nova aplicação Angular e adicionar a capacidade de publicação na nuvem. Depois, iremos conectá-la tanto ao serviço de autenticação, quanto às Web APIs. Opcionalmente, também iremos demonstrar como vincular um domínio registrado, bem como um certificado para dar mais segurança “em trânsito” (https). Por fim, como bônus, iremos discutir a alternativa de termos um pipeline automatizado (CI/CD) para provisionar a infraestrutura do front-end, e realizar o build e deploy da aplicação SPA, sempre que ocorra um merge na branch principal.
Estes são os principais passos que iremos seguir, considerando a região de Ohio (us-east-2):
- Fazer o bootstrap de uma nova aplicação Angular SPA;
- Inicializar e configurar o Amplify na aplicação criada;
- Adicionar a infraestrutura referente à hospedagem na nuvem e publicá-la;
- Importar a infraestrutura de segurança existente, para permitir acesso às APIs protegidas;
- Vincular um domínio e um certificado à nossa aplicação [opcional];
Link para o repositório com o código-fonte:
https://github.com/aws-samples/video-labeling-angular-blog
Pré-requisitos
Para esse passo a passo, você deve possuir os seguintes pré-requisitos:
- Uma conta criada na AWS[4] e um usuário com acesso para criação de recursos;
- AWS CLI[5] instalado e configurado (“config”) – opcionalmente usando SSO[6] (“credentials”);
- Amazon Cognito (user pool) e API Gateway (AWS Lambda exposta via HTTP [7]);
- Git (repositório criado), Node.js, Angular CLI, Amplify CLI, VS Code – ou softwares equivalentes;
- Nome de domínio (hosted zone) registrado no Route 53 e certificado (SSL/TLS) público criado no AWS Certificate Manager (ACM) [opcional];
- Conhecimento de JavaScript e AWS;
Passo 1: Fazer o bootstrap de uma nova aplicação Angular SPA.
Para informações detalhadas, consulte o README.md.
Para criar e acessar uma nova aplicação Angular:
- Verifique a versão instalada e, opcionalmente, instale a última atualização do Angular 10.
ng --version npm i @angular/cli@10
- Crie uma aplicação com um código básico padrão (bootstrap) – as opções podem variar.
ng n appAWSomeBlog --directory=. --minimal=true --routing=true --skipTests=true --style=scss --strict=true
- Realize o build e teste a aplicação para garantir que a mesma está funcionando localmente.
ng serve --open
O screenshot da Figura 3 mostra o resultado destes comandos.
Passo 2: Inicializar e configurar o Amplify na aplicação criada.
Para instalar o Amplify CLI e adicioná-lo à aplicação:
- Verifique a versão instalada e, opcionalmente, instale a última atualização do Amplify CLI. Você também pode fazer o opt out.
amplify --version npm i –g @aws-amplify/cli amplify configure --usage-data-off
- Adicione o Amplify à aplicação, o inicializando – escolha o profile
amplify init ? Enter a name for the project: prjAppBlog [Enter] ? Enter a name for the environment (dev) [Enter] ? Choose your default editor: Visual Studio Code [Enter] ? Choose the type of app that you’re building: javascript [Enter] ? What javascript framework are you using: angular [Enter] ? Source Directory Path: (src) [Enter] ? Distribution Directory Path: (dist/appAWSomeBlog) [Enter] ? Build Command: ng build --prod [Enter] ? Start Command: (ng serve) [Enter] ? Do you want to use an AWS profile? (Y/n) [Enter] ? Please choose the profile you want to use: default [Enter]
- Verifique o status dos recursos.
amplify status
O screenshot da Figura 4 mostra o resultado destes comandos.
Note que não foi preciso executar o comando “amplify configure”, já que, como pré-requisito, existe um usuário com as devidas permissões. Caso tal usuário não exista, este comando poderá ser utilizado. Ele pedirá que você se autentique no AWS Console. Uma vez autenticado, o Amplify CLI irá pedir pra você criar um usuário IAM com “AdministratorAccess”.
Passo 3: Adicionar a infraestrutura referente à hospedagem na nuvem e publicá-la.
Para publicar a aplicação na nuvem:
- Adicione o recurso de hospedagem através do Amplify CLI – lembrando que o bucket precisa ter um nome único (globalmente).
amplify add hosting ? Select the plugin module to execute: Amazon CloudFront and S3 [Enter] ? Select the environment setup: PROD (S3 with CloudFront using HTTPS) [ENTER] ? hosting bucket name: prjappblog-hosting-bucket [Enter]
- Publique os recursos na AWS – isto criará de fato os recursos (bucket, bucket policy, distribuição e identidade de acesso) e publicará a aplicação, o que poderá levar alguns minutos.
amplify publish
? Are you sure you want to continue? (Y/n) [Enter]
O screenshot da Figura 5 mostra o resultado destes comandos.
Note que aqui temos os recursos criados. O bucket para armazenamento dos arquivos (build) da aplicação SPA e uma distribuição de conteúdo garantindo uma baixa latência, graças ao cache global. Do ponto de vista da segurança, outros dois recursos foram criados: a bucket policy (código abaixo) que limita o acesso ao bucket apenas à identidade da distribuição, e esta última.
Política associada ao bucket prjappblog-hosting-bucket-dev:
{
"Version": "2012-10-17",
"Id": "MyPolicy",
"Statement": [
{
"Sid": "APIReadForGetBucketObjects",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity BLA123BLA123BLA123"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::prjappblog-hosting-bucket-dev/*"
}
]
}
Também é possível verificar a criação da aplicação no console do Amplify, como pode ser visto na Figura 6, acima.
Ainda quanto ao processo de publicação, atente para o fato de que, se tentamos acessar o endpoint de imediato, podemos receber uma página com uma descrição de erro por acesso negado (Figura 7).
Em resumo, a distribuição ainda não foi entregue em todos os pontos globais (edge locations) e, com isso, nossa solicitação (cache miss) acabou sendo redirecionada para a origem – que é o próprio bucket. Como, por sua vez, este é privado, não conseguimos acessar os arquivos. Caso tenha interesse em entender mais detalhes sobre este comportamento, e como evita-lo, recomendamos a leitura deste issue [8].
Passo 4: Importar a infraestrutura de segurança existente, para permitir acesso às APIs protegidas.
Para importar as configurações do diretório de usuários já existente (user pool):
- Opcionalmente, evolua o código da aplicação e faça um novo deploy. O objetivo aqui é incluir uma interface mais amigável, por exemplo, com menus em vertical do lado esquerdo, bem como uma barra horizontal como cabeçalho, utilizada para exibir dados do usuário autenticado e do tenant, já que se trata de uma aplicação SaaS multi-tenant. Note que a opção “-c” gera uma solicitação de invalidação de arquivos da distribuição [9], tornando a atualização quase automática. Atente para o fato que esta operação, entretanto, pode gerar custos adicionais.
amplify publish –c
- Adicione o recurso de autenticação (diretório de usuários) do Amazon Cognito através do Amplify CLI. E faça novamente uma publicação para que as modificações sejam aplicadas.
amplify import auth ? What type of auth resource do you want to import? Cognito User Pool only [Enter] amplify publish ? Are you sure you want to continue (Y/n) [Enter]
O screenshot da Figura 8 mostra que automaticamente o user pool é identificado e importado, uma vez que satisfez os pré-requisitos do Amplify (por exemplo, um user pool que possua um identity pool associado). Note também a mensagem informativa de que uma nova versão (4.43.0) do Amplify CLI estaria disponível. É uma boa prática atualizar para a última versão disponível antes de realizarmos um novo deploy (publicação).
A seguir, como mostrado na Figura 9, os recursos são atualizados e é feito o upload de um novo build da aplicação. Note que o arquivo “src/aws-exports.js” contém as informações sobre os recursos AWS vinculados (bucket, distribuição e user pool). Alternativamente, podemos realizar ajustes manuais, bem como ter várias versões dentro do subdiretório “environment” – com o cuidado para que não sejam sobrescritas quando da execução de novos comandos. A substituição pode ser configurada no arquivo “angular.json” (fileReplacements).
Código de exemplo do arquivo aws-exports.js:
const awsmobile = {
"aws_project_region": "us-east-2",
"aws_cognito_region": "us-east-2",
"aws_user_pools_id": "us-east-2_blablabla",
"aws_user_pools_web_client_id": "123blablabla456",
"oauth": {
"domain": "bla-123456789.auth.us-east-2.amazoncognito.com",
"scope": ["email", "openid", "profile"],
"redirectSignIn": "https://subdominio.cloudfront.net/login",
"redirectSignOut": "https://subdominio.cloudfront.net/home",
"responseType": "token" // or "code"
},
"federationTarget": "COGNITO_USER_POOLS",
"aws_content_delivery_bucket": "bla-tenant-hostingbucket-dev",
"aws_content_delivery_bucket_region": "us-east-2",
"aws_content_delivery_url": "https://subdominio.cloudfront.net"
};
export default awsmobile;
Fragmento de código de exemplo do arquivo src/main.ts, inspirado em [10]:
// Amplify Configuration import Amplify, { Auth } from 'aws-amplify'; import awsconfig from './environments/aws-exports'; Amplify.configure(awsconfig); // End Amplify Configuration
Fragmentos de código TypeScript do arquivo src/app/shared/services/auth.service.ts, fazendo uso da biblioteca Amplify já configurada:
public async getCurrentUserInfo() { console.log((await this.getIdToken()).decodePayload()); let tenant = (await this.getIdToken()).decodePayload()['custom:tenantId']; let email = (await this.getIdToken()).decodePayload()['email']; return `${email} (${tenant})`; } async getAccessToken() { return (await Auth.currentSession()).getAccessToken(); } async getIdToken() { return (await Auth.currentSession()).getIdToken(); } async getRefreshToken() { return (await Auth.currentSession()).getRefreshToken(); } async doAmplifyLogin() { try { Auth.federatedSignIn(); } catch (error) { console.log('Error signing in: ', error); } } async doAmplifyLogout() { try { Auth.signOut(); } catch (error) { console.log('Error signing out: ', error); } } }
Enquanto o método “doAmplifyLogin()” redireciona o usuário para a interface de login do Amazon Cognito (que pode ser customizada [11]) e, após uma autenticação com sucesso, retorna o token JWT para a aplicação SPA no endereço configurado em “redirectSignIn” (Callback URL); “doAmplifyLogout()” invalida a sessão atual, redirecionando o usuário para o valor de “redirectSignOut”. Agora, de posse dos tokens, podemos acessar as APIs protegidas, vide o fragmento de código abaixo (httpOptions):
Fragmento de código TypeScript do arquivo src/app/shared/services/labels.service.ts:
public getLabels (token: string) : Observable<HttpResponse<any>> { const httpOptions = { headers: new HttpHeaders({ 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}`}), observe: 'response' as const }; return this._http.get<any>(this.getUrl(), httpOptions); }
Ainda com relação aos tokens, é importante ressaltar que, enquanto o id token contém informações sobre o usuário autenticado, o access token é o que permite o acesso. Opcionalmente, o refresh token poderia ser utilizado para evitar que o usuário final tenha que ficar se autenticando de tempos em tempos (silent refresh). Porém, em “clientes públicos” (como SPAs e aplicações móveis), o uso desse tipo de token pode ser desabilitado, até por questões de segurança, se estivermos utilizando o chamado implicit flow, presente na especificação OpenID Connect [12].
Passo 5: Vincular um domínio e um certificado à nossa aplicação [opcional].
Para substituir o subdomínio da distribuição por um personalizado:
- No CloudFront, localize a distribuição e acesse suas configurações (clique em seu ID);
- Na aba “General”, clique no botão Edit.
- No campo “Alternate Domain Names (CNAMEs)”, digite o nome desejado, por exemplo “blog.francas.team”;
- No campo “SSL Certificate”, troque para “Custom SSL Certificate” e selecione o certificado na caixa de texto mais abaixo – por exemplo, “*.francas.team”;
- Clique no botão “Yes, Edit” no canto inferior direito da página para salvar as alterações.
- No Route 53, localize a hosted zone (por exemplo “francas.team”) e acesse suas configurações;
- Clique no botão “Create record”;
- No campo “Record name”, digite o subdomínio utilizado na distribuição, por exemplo, “blog”;
- Clique no toggle “Alias” para ativá-lo;
- No campo “Route traffic to”, escolha a opção “Alias to CloudFront distribution” – mantenha “Record Type” igual a “A”;
- Finalmente, confirme a região, selecione a distribuição, e clique no botão “Create records”.
O screenshot da Figura 10 mostra o registro antes de ser criado. Em seguida, imagens da aplicação sendo executada para a busca de um vídeo no catálogo, de acordo com os critérios especificados.
Note que na barra de título da aplicação (Figura 11), exibimos o usuário (e-mail), bem como o tenant ID (neste caso, “77”). Estas informações podem ser extraídas do “ID Token”. Note ainda que, para fins de exemplo, ao clicar no botão “Get Pre-Signed URL”, tentamos acessar uma API protegida, sem passarmos o “Access Token”, resultando em um HTTP 401 – Não Autorizado.
A próxima tela (Figura 12) exibe o campo “Pick a label” (autocomplete) contendo todos as “etiquetas” identificadas, ou seja, todos os elementos reconhecidos nos vídeos que foram carregados (upload) para a plataforma, após a análise automatizada do serviço Amazon Rekognition. A carga deste campo é feita através de uma chamada de API, que por sua vez realiza uma consulta em uma tabela do Amazon DynamoDB.
Por fim, ao escolhermos uma “etiqueta”, todos os vídeos contendo aquela etiqueta são listados (Figura 13). Ao selecionar um vídeo, retornamos uma pre-signed URL (download) do Amazon S3, que por fim retorna o vídeo. Note que escolhemos, neste caso, a etiqueta “Aerial View” (vista aérea), que corresponde à cena exibida do vídeo em questão.
Realizando a Limpeza
Para evitar cobranças futuras, remova todos os recursos. Será necessário excluir manualmente o bucket criado, após esvaziá-lo. Note que você precisará recriar estes recursos no futuro, caso deseje realizar novos testes por ocasião dos futuros blog posts desta série.
amplify delete
? Are you sure you want to continue? This CANNOT be undone. (This would delete all the environments of the project from the cloud and wipe out all the local files created by Amplify CLI) Yes [Enter]
E caso você receba um erro (Removing backend envs for app bla123bla123 failed) ao tentar apagar a aplicação via console (Actions/Delete app) do Amplify, você pode executar os comandos abaixo antes de tentar novamente:
[aws sso login] aws amplify delete-backend-environment --app-id bla123bla123 --environment-name dev
Conclusão
Este foi o primeiro de uma série de blogs sobre uma solução de SaaS multi-tenant. Aqui demonstramos como o Amplify pode acelerar a construção de aplicações seguras e escaláveis na nuvem, bem como sua integração com o Amazon Cognito para obtenção de tokens JWT e acesso a APIs protegidas. Também demonstramos como o Amazon Rekognition pode ser utilizado para fazer análise de conteúdo de vídeo e nos oferecer uma forma simples de filtrar vídeos baseados em uma determinada “etiqueta”.
Os próximos blog posts irão detalhar o back-end e os data stores. E se o seu foco é em aplicações multiplataforma, não deixe de conferir o blog sobre a disponibilidade geral do Amplify Flutter [13].
Referências
- “SaaS Storage Strategies – Building a Multitenant Storage Model on AWS” (em Inglês): https://d0.awsstatic.com/whitepapers/Multi_Tenant_SaaS_Storage_Strategies.pdf
- “Establishing your best practice AWS environment” (em Inglês): https://aws.amazon.com/organizations/getting-started/best-practices/
- “Getting started – Amplify Docs” (em Inglês) : https://docs.amplify.aws/start
- “Crie uma conta gratuita na AWS”: https://aws.amazon.com/pt/free
- “Interface da Linha de Comando da AWS”: https://aws.amazon.com/pt/cli/
- “Configurar a AWS CLI para usar o Logon único da AWS”: https://docs.aws.amazon.com/pt_br/cli/latest/userguide/cli-configure-sso.html
- “Trabalhar com APIs HTTP”: https://docs.aws.amazon.com/pt_br/apigateway/latest/developerguide/http-api.html
- “Amplify Publish – CloudFront 307 redirection and Access Denied after deployment in non-US region” (em Inglês): https://github.com/aws-amplify/amplify-cli/issues/611
- “Invalidar arquivos”: https://docs.aws.amazon.com/pt_br/AmazonCloudFront/latest/DeveloperGuide/Invalidation.html
- “Create authentication service” (em Inglês): https://docs.amplify.aws/lib/auth/getting-started/q/platform/js#create-authentication-service
- “Personalização da interface do usuário de aplicativo integrada para cadastrar e fazer login de usuários”: https://docs.aws.amazon.com/pt_br/cognito/latest/developerguide/cognito-user-pools-ux.html
- “Authentication using the Implicit Flow” (em Inglês): https://openid.net/specs/openid-connect-core-1_0.html#ImplicitFlowAuth
- “Amplify Flutter já está disponível: crie belos aplicativos multiplataforma” (em Inglês): https://aws.amazon.com/blogs/aws/amplify-flutter-is-now-generally-available-build-beautiful-cross-platform-apps/
Sobre o Autor
No papel de Arquiteto de Soluções, França apoia parceiros e empresas da Fortune 500 na América Latina. Ele escreveu sua primeira linha de código aos 12 anos de idade, é pós-graduado pela PUC-Rio, possui MBA em Gestão de Projetos pela Fundação Getúlio Vargas, além de ser Mestre em Sistemas de Informação pela UNIRIO e Doutor em Engenharia de Software pela UFRJ. Ah, ele adora jogar PlayStation!!! Isso quando sua filha Rafaela (de 2 anos) deixa, claro…
Revisores
Juliano Fernandes Baeta é Arquiteto de Soluções para Global Systems Integrators para a América Latina. É um entusiasta de Big Data, Analytics, e Machine Learning. Sua missão é ajudar parceiros a construir soluções seguras, eficazes e resilientes na AWS.
Gerson Itiro Hidaka atualmente trabalha como Arquiteto de Soluções da AWS e atua no atendimento a parceiros mundiais chamados de Global System Integrators and Influencers (GSIIs) na região da América Latina. Entusiasta de tecnologias como Internet das Coisas (IoT), Drones, Devops e especialista em tecnologias como virtualização, serverless, container e Kubernetes. Trabalha com soluções de TI a mais de 24 anos, tendo experiência em inúmeros projetos de otimização de infraestrutura, redes, migração, disaster recovery e DevOps em seu portfólio.