Realize conversões complexas sob demanda e a baixo custo

Muitas vezes me deparo com cenários em que os dados que meu aplicativo precisa estão disponíveis, mas não em um formato que possa ser prontamente consumido.

As funções de nuvem e outros microsserviços realmente brilham aqui. Eles podem converter dados de um formulário para outro sob demanda e geralmente são muito baratos. Como são contextos de execução distintos, eles não precisam corresponder à pilha de tecnologia do aplicativo consumidor. Isso os libera para usar a linguagem e as bibliotecas mais adequadas ao trabalho de conversão.

Eles também encapsulam totalmente (ocultam) o código de conversão muitas vezes deselegante, mantendo a lógica do aplicativo consumidor simples e focada.

Uma conversão que eu preciso rotineiramente é de sites estáticos para APIs RESTful. Deixe-me ilustrar com alguns exemplos.


Exemplo 1: Resultados do Catálogo da Biblioteca

Tomemos o exemplo do site da minha biblioteca local. Quero criar um plug-in de navegador para notificar os compradores on-line quando um livro que eles estão procurando está disponível na biblioteca.

Foto cedida por Nicepik

O site da biblioteca fornece um catálogo on-line pesquisável. No entanto, todas as páginas do site são renderizadas no lado do servidor. A biblioteca também não expõe uma API da web pública.

Como está, minha única opção é enviar minha consulta de pesquisa para o servidor da biblioteca e adicionar lógica à minha extensão para analisar o HTML dos resultados da pesquisa retornados.

Mas e se, em vez disso, criarmos uma função de nuvem que aceite uma solicitação da extensão do meu navegador, faça essa chamada de rede para o servidor da biblioteca, analise o HTML retornado e envie de volta uma resposta JSON bem formatada?

Arquitetura da API de conversão

Teremos efetivamente convertido o site estaticamente renderizado da minha biblioteca local em uma API JSON RESTful.

Agora isso é legal.

Aqui está o meu código de função da nuvem. (Eu uso a estrutura sem servidor com o AWS Lambda para desenvolvimento e implantação).

import { APIGatewayProxyHandler } from 'aws-lambda'; import 'source-map-support/register'; import axios from 'axios'; import * as cheerio from 'cheerio'; import LIBRARY_SEARCH_URL from './endpoint'; const SELECTORS = { RESULT_ENTRIES: '.results_cell', TITLE_LINK: '.displayDetailLink', THUMBNAIL: '.thumbnail img', AUTHOR_NAME: '.INITIAL_AUTHOR_SRCH' } function queryLibrary(queryStr) { const url = new URL(LIBRARY_SEARCH_URL); url.search = `qu=${queryStr.replace(' ', '+')}`; return axios.get(url.href); } function extractEntries(html) { const $ = cheerio.load(html); const results = $(SELECTORS.RESULT_ENTRIES); const entries = []; results.each((_, elem) => { const rawThumbnailSrc = $(SELECTORS.THUMBNAIL, elem).attr('src'); const thumbnailSrc = rawThumbnailSrc.match('no_image.png') ? '' : rawThumbnailSrc; const authorText = $(SELECTORS.AUTHOR_NAME, elem).text(); const authorName = authorText.replace('by', '').trim(); const result = { title: $(SELECTORS.TITLE_LINK, elem).text(), thumbnail: thumbnailSrc, author: authorName }; entries.push(result); }); return entries; } export const query: APIGatewayProxyHandler = async (event, _context) => { if (!event.queryStringParameters || !event.queryStringParameters.q) { return { statusCode: 400, body: JSON.stringify({ message: 'No query provided' }), }; } const res = await queryLibrary(event.queryStringParameters.q); const resHTML = res.data; const entries = extractEntries(resHTML); return { statusCode: 200, body: JSON.stringify({ entries }) }; }

E aqui está uma imagem do JSON que meu aplicativo recebe da nova API. Isso lista as primeiras entradas no catálogo da biblioteca para a palavra computador .

Com sorte, você já pode ver o poder nesta arquitetura de conversão de dados de microsserviço.


Exemplo 2: Faculdade de Ciência da Computação

Digamos que estamos criando um aplicativo para recomendar mentores do corpo docente a possíveis alunos de pós-graduação da Universidade de Utah com base em seus interesses de pesquisa.

Felizmente, uma lista de professores, completa com sinopses de pesquisa e até informações de contato, está disponível no site da universidade.

Vamos seguir o mesmo padrão para criar uma API da web. Aqui está o código:

import { APIGatewayProxyHandler } from 'aws-lambda'; import 'source-map-support/register'; import * as cheerio from 'cheerio'; import Axios from 'axios'; const BASE_URL = 'https://www.cs.utah.edu'; const SELECTORS = { FACULTY_ENTRIES: 'table#people:nth-child(3) tr.professor', NAME: '#info > tbody > tr:nth-child(1) > td > h8', OFFICE: '#info > tbody > tr:nth-child(2) > td:nth-child(2)', PHONE: '#info > tbody > tr:nth-child(3) > td:nth-child(2)', EMAIL: '#info > tbody > tr:nth-child(4) > td:nth-child(2)', RESEARCH_INTERESTS: '#info2 > tbody > tr:nth-child(2)', THUMBNAIL: 'img' } function extractFaculty(html) { const $ = cheerio.load(html); const faculty = []; const entries = $(SELECTORS.FACULTY_ENTRIES); entries.each((_, elem) => { const facultyEntry = { name: $(SELECTORS.NAME, elem).text(), thumbnail: `${BASE_URL}${$(SELECTORS.THUMBNAIL, elem).attr('src')}`, contactInfo: { office: $(SELECTORS.OFFICE, elem).text(), phone: $(SELECTORS.PHONE, elem).text(), email: $(SELECTORS.EMAIL, elem).text(), }, researchInterests: $(SELECTORS.RESEARCH_INTERESTS, elem).text() }; faculty.push(facultyEntry); }); return faculty; } export const harvest: APIGatewayProxyHandler = async (_, _context) => { const res = await Axios.get(`${BASE_URL}/people/faculty/`); const resHTML = res.data; const faculty = extractFaculty(resHTML); return { statusCode: 200, body: JSON.stringify({ faculty }) }; }
Código da Função API da Universidade de Utah

Aqui está a resposta da nova API do corpo docente:

Veja a API trabalhando ao vivo aqui .

Isto tem muitas semelhanças com o exemplo da biblioteca. Mas você percebe diferenças?

Aqui está uma importante: o corpo docente muda muito raramente .

No último exemplo, foi necessário pesquisar o site da biblioteca toda vez que a função de nuvem recebesse uma solicitação de pesquisa do consumidor. No entanto, este não é frequentemente o caso.

Neste exemplo do corpo docente, na verdade, verificar o site estático para atualizações raramente é necessário. Isso nos libera para armazenar em cache os dados do corpo docente e simplesmente pesquisar o site em busca de atualizações com pouca frequência. Fazer essa alteração separa a fonte de dados do aplicativo consumidor. Nós mudamos de uma função de nuvem para duas:

Arquitetura de conversão em cache

Por um lado, gosto desse desacoplamento porque, se você não controlar a fonte de dados (site estático ou outro), alterações não anunciadas podem exigir uma atualização do código de conversão.

Sem esse desacoplamento, a falha de conversão provavelmente seria imediatamente aparente para um usuário final.

Com ele, o aplicativo consumidor provavelmente continuará funcionando bem por algum tempo. Isso lhe dá tempo para atualizar o código de conversão com poucos soluços.

A única desvantagem, é claro, é a complexidade extra de garantir que o cache esteja sempre atualizado o suficiente para ser viável para seu aplicativo consumidor.


Conclusão

Embora este artigo seja voltado especialmente para a conversão de websites estáticos em APIs RESTful, permitam-me reiterar a aplicabilidade geral desse padrão de conversão da função da nuvem.

Ele fornece um ambiente modular, direcionado e independente para executar as operações de conversão, às vezes complexas, que são frequentemente exigidas pelos aplicativos modernos.