Apesar da VTEX já possuir soluções próprias para implementações com ferramentas externas, é comum a necessidade de criar integrações com parceiros que ainda não estão no ecossistema da plataforma.
E foi isso que precisamos fazer para integrar a nova parceira da Motorola na França, LiveScale, com a VTEX.
Neste artigo vamos contar como que criamos um app, que liga o sistema de carrinho e listagem de produtos da VTEX na LiveScale utilizando a estrutura do IO e sua arquitetura de serviços. Confira!
Alinhamento técnico
Antes de iniciar o desenvolvimento, é necessário entender mais profundamente todos os requisitos do projeto. Para isso, fizemos reuniões com o time de desenvolvedores da LiveScale, onde sanamos dúvidas técnicas e alinhamos expectativas.
Além de um Swagger contendo todas rotas necessárias e o conteúdo esperado, também foi encaminhado exemplos da implementação em outras plataformas, como o da SFCC.
Com o decorrer do tempo, também fizemos outros encontros, para que nos acompanhassem, garantindo que a tarefa caminhasse no caminho correto.
Antes de pôr a mão na massa
Para começar, criamos fluxogramas e descrições de requisitos, visando entender quais builders da VTEX iríamos precisar e qual o fluxo lógico da aplicação.
Como a execução tinha certa complexidade, foi preciso utilizar os builders, node (utilizar serviços e clientes), admin (criar telas de configuração no painel admin), react (Criar a UI que será utilizada no admin), messages (internacionalização) e graphql (algumas queries e mutations).
Com essas descrições em mãos, enviamos o formulário de criação de app para VTEX, um processo necessário para garantir que teremos acesso ao app, e a liberdade para publicá-lo.
Setup do repositório e configurações iniciais
A VTEX provê de diversos boilerplates para criação de apps, no nosso cenário, onde seria necessário consumir alguns serviços do lado do IO, optamos por utilizar o Service Example. Mas também utilizamos alguns trechos de outros boilerplates, como o Admin Example.
Para criar o projeto, basta utilizar o comando vtex init e depois selecionar a atividade que faz mais sentido pra você, como pode ser visto nessa documentação.
Com nosso repositório criado, fizemos as configurações de permissão necessárias no arquivo manifest.json. Lá, definimos as policies para as APIs que são precisas, como checkout, catalog_system, vbase e graphql.
Também fizemos configurações de ferramentas que otimizam a experiência do desenvolvedor, como eslint.
Criando os Clientes
A VTEX possui uma arquitetura bem definida, o que não só facilita o desenvolvimento, mas também garante mais performance. Antes de começar a desenvolver, foi necessário estudar e analisar mais profundamente as ferramentas que ela utiliza.
Por baixo da implementação dos Clients e Services, a VTEX utiliza KoaJS, um framework JavaScript desenhado pelo mesmo time do express. O objetivo dessa ferramenta é ser menor, mais expressiva e criar uma fundação mais sólida para criação de aplicações web e APIs. A VTEX também provê uma biblioteca que facilita o trabalho com o node, chamada node-vtex-api.
Entendendo a arquitetura do IO
No VTEX IO, Clients são abstrações para outros serviços, podendo ser tanto internos (outros clients VTEX) como externos. Já que iriamos expor informações da loja para o LiveScale, só precisamos utilizar serviços internos. A infraestrutura do “programajá” fornece algumas configurações por padrão, como:
- Cache
- Suporte para métricas nativas
- Opções de Retry e timeout
Fonte: https://github.com/vtex-apps/service-example
Como criar e estender os Clients
A SDK @vtex/api já fornece uma forma estruturada de criar clients. Para isso, é necessário entender qual tipo de comunicação vamos estabelecer, como na planilha a seguir:
Tipo | Caso de uso |
AppClient | Comunicação com outros serviços de IO através de chamadas HTTP |
AppGraphQLClient | Comunicação com outros serviços IO GraphQL |
ExternalClient | Comunicação com API’s externas |
JanusClient | Comunicação com API’s Core Commerce da VTEX, através do Janus Router |
InfraClient | Comunicação com serviços de infra do VTEX IO |
Após isso, é só estender o client que faz mais sentido. Para facilitar a visualização, confira o arquivo que a própria VTEX disponibiliza para explicar como é a estrutura de um Client. No exemplo, esse arquivo ficaria localizado em node/clients/github.ts.
Fonte: https://developers.vtex.com/docs/guides/vtex-io-documentation-how-to-create-and-use-clients
Seguindo o exemplo acima, implemente seus métodos e, após isso, adicione seu Client ao serviço que você está criando, seguindo estes passos:
1. Crie um arquivo index.ts no caminho node/clients, ele será responsável por unificar e abstrair todos seus clients, além de expor eles para seus middlewares.
JavaScript
import { IOClients } from '@vtex/api'
import GithubClient from './github.ts'
export class Clients extends IOClients {
public get status() {
return this.getOrSet('github', GithubClient)
}
}
2. Importe o seu Client na raiz do projeto node, em node/index.ts.
JavaScript
import type { ClientsConfig, ParamsContext } from "@vtex/api";
import { method, Service } from "@vtex/api";
import { Clients } from "./clients";
3. Implemente o client utilizando o ClientsConfig da @vtex/api.
JavaScript
const clients: ClientsConfig<Clients> = {
implementation: Clients,
options: {
default: {
retries: 20,
timeout: TIMEOUT_MS
}
}
};
4. Agora, basta utilizar o client no Serviço exportado.
JavaScript
export default new Service<Clients, State>({
clients,
routes: {
...
},
})
5. Use a seguinte tipagem no arquivo index.ts da pasta node, que irá te ajudar na implementação das funções.
JavaScript
declare global {
type Context = ServiceContext<Clients, State>
}
6. Pronto! Agora você já pode utilizar seu serviço em middlewares através do contexto.
JavaScript
export const authorize = async (ctx: Context) {
const { clients: { github } } = ctx
...
const data = await github.getUser(/*...*/)
}
Seguindo os passos, você tem os clients implementados com sucesso. A seguir o exemplo de um deles e um método aplicado.
Checkout
OrderForm Retorna um orderForm dado um orderFormId.
JavaScript
public orderForm = (orderFormId: string) =>
this.post<OrderForm>(
this.routes.orderForm(orderFormId),
{ expectedOrderFormSections: ["items"] },
{ metric: "checkout-orderForm" }
);
Implementando nossos clients através de middlewares
Após estender e criar nossos clients, chegou a hora de criarmos middlewares para consumi-los, e servir suas informações em endpoints.
O objetivo da utilização dos middlewares será implementar métodos que interajam diretamente com os elementos e integrá-los com o serviço.
Seguindo o Single-Responsibility Principle, do S.O.L.I.D., iremos separar a lógica de cada domínio de nossos clients em um middleware.
Criando validators utilizando middlewares
Para centralizar a lógica de validação, criamos validators, funções que verificam se as informações necessárias foram declaradas antes de utilizarmos os clients. No exemplo a seguir, verificamos se o basketId foi informado.
JavaScript
// Helper para retornar erros de input de usuário
import { UserInputError } from "@vtex/api";
// Função que encapsula lógica de serialização dos parâmetros recebidos
import serializeParams from "../utils/serializeParams";
export default async function validateBasketId(
ctx: Context,
next: () => Promise<any>
) {
const {
vtex: {
route: { params }
}
} = ctx;
const { basketId } = params;
// Caso não exista basketId, é retornado um erro
if (!basketId) {
throw new UserInputError("Basket ID is missing");
}
// Caso exista basketId, ele é salvo como state na aplicação.
ctx.state.basketId = serializeParams(basketId);
await next();
}
Após passar pelo validator, é chamado o próximo middleware através da função next().
Acessando nossos clients
Em seguida, podemos interagir com nossos Clients. Para isso, nosso middleware será uma classe, onde cada um de seus métodos corresponde a um endpoint que será definido no futuro. Logo abaixo, você vê a implementação do middleware Baskets e um de seus métodos, o show, responsável por retornar um carrinho, dado um ID.
JavaScript
// Função helper que dado um orderForm (padrão VTEX),
// retorna um basket (padrão LiveShopping)
import convertBasketResponse from "../utils/conveters/convertBasketResponse";
export default class Baskets {
// Métodos
public async show(ctx: Context, next: () => Promise<any>) {
const {
// Uso do estado, para recuperar o valor do basketId
state: { basketId },
// Uso do nosso Client
clients: { checkout: checkoutClient },
host
} = ctx;
// Dado um basketId, utilizando nosso client, retornamos um orderForm
const orderForm = await checkoutClient.orderForm(basketId);
// Conversão do padrão de orderForm para basket
ctx.body = convertBasketResponse(orderForm, host);
await next();
}
}
}
Juntando tudo em nosso serviço
Após criarmos nossos clients e middlewares precisamos unir tudo através de um serviço, conforme mostrado anteriormente.
Criando nosso serviço
Assim, criamos o local na raiz da pasta node, no arquivo index.ts. Confira através dos comentários no código como se dá o seu funcionamento.
JavaScript
// Tipagens da VTEX
import type { ClientsConfig, ParamsContext } from "@vtex/api";
// Classes e funções que iremos utilizar para criar nosso serviço e seus métodos
import { method, Service } from "@vtex/api";
// Nossos Clients
import { Clients } from "./clients";
// Middleware validator
import validateBasketId from "./middlewares/validateBasketId";
// Middleware que encapsula a lógica dos baskets
import Baskets from "./middlewares/baskets";
const TIMEOUT_MS = 800;
// Implementação dos clients e de algumas configurações
const clients: ClientsConfig<Clients> = {
implementation: Clients,
options: {
default: {
retries: 20,
timeout: TIMEOUT_MS
}
}
};
// Geração da classe Baskets
const baskets = new Baskets();
// Criação do serviço, com declaração do client e das rotas
export default new Service<Clients, State, ParamsContext>({
clients,
routes: {
// O nome de cada filho do objeto routes corresponde a um endpoint,
// posteriormente declarado no arquivo services.json, na raiz da pasta node
basketsList: method({
// Criação da cadeia de execução dos middlewares para cada verbo
// Nesse exemplo, ao utilizarmos o GET, passamos primeiro para o
// validateBasketId, e caso a função next() seja nele passamos para
// o método show da classe baskets.
GET: [validateBasketId, baskets.show]
}),
}
});
Expondo nosso serviço através de endpoints, utilizando o service.json
Após criar nosso serviço, é necessário declarar qual serão os endpoints que irão acessar os filhos do objeto routes, na nossa classe principal Service.
JavaScript
{
// Configurações gerais
"memory": 256,
"ttl": 10,
"timeout": 2,
"minReplicas": 2,
"maxReplicas": 4,
"workers": 1,
"routes": {
// Declaração da rota e atribuição do endpoint
"basketsList": {
"path": "/_v/baskets/:basketId",
"public": false
}
}
}
E pronto! Esse é o fluxo utilizado no desenvolvimento de apps seguindo a arquitetura da VTEX.
Espero que este conteúdo tenha sido útil para você. E se você quiser receber outros conteúdos como este sobre tecnologia, experiência e marketing para e-commerce, não deixe de se cadastrar em nossa newsletter.
Escrito por:
Miguel Gonçalves, Desenvolvedor Mobile Sênior
at Corebiz