De um catálogo estático, para um catálogo global real
Uma visão do próximo passo na arquitetura de produtos: utilizar o pipeline existente de verificação e ingestão de CSV/JSON para construir um catálogo global de produtos validado e independente da fonte.
De Produtos Mockados para um Catálogo Global Real
Quando a experiência global de produtos foi implementada pela primeira vez, utilizar um conjunto de dados mockados da Lomadee fazia todo sentido.
Naquele momento, o objetivo não era construir um sistema completo de gerenciamento de produtos. Precisávamos de produtos para fazer a experiência funcionar, testar as mecânicas da roleta, validar a interface e começar a pensar em como os catálogos interagiriam com os tenants.
Uma pequena camada de transformação era suficiente.
A aplicação carregava um arquivo JSON da Lomadee, extraía os campos que precisávamos, calculava informações como desconto e tier e disponibilizava o array resultante para a experiência do catálogo.
Era simples.
E, naquela época, simplicidade era exatamente o que precisávamos.
Mas o projeto evoluiu desde então.
Agora temos algo muito mais valioso do que um conjunto de dados de produtos mockados: um pipeline de ingestão capaz de transformar diferentes representações de produtos em uma estrutura interna de produto validada.
Isso muda a forma como a experiência global deve funcionar.
A Situação Atual
No momento, o catálogo global ainda depende, em última instância, de um conjunto de dados local da Lomadee.
O resolvedor de catálogo efetivamente possui duas possibilidades:
Tenant possui produtos
↓
Utiliza o catálogo do tenant
Tenant não possui produtos
↓
Utiliza os produtos da Lomadee
Sem tenant
↓
Utiliza os produtos da LomadeeA abstração em si é útil.
O problema é a fonte.
A experiência global não deveria depender fundamentalmente da Lomadee.
A Lomadee foi útil como fonte inicial de dados, mas agora nossa aplicação possui seu próprio modelo de produtos e suas próprias regras para determinar se um produto é válido.
Isso significa que podemos mover essa responsabilidade para uma etapa anterior do processo.
Em vez de transformar um conjunto de dados externo e mockado toda vez que a aplicação é iniciada, podemos ingerir os produtos uma vez, validá-los, persistí-los e permitir que a experiência global consuma a mesma estrutura canônica de produtos utilizada pelo restante do sistema.
O Pipeline de Ingestão Já Existe
Essa é uma das razões pelas quais essa transição é relativamente simples.
Já estamos trabalhando para separar as diferentes responsabilidades envolvidas na ingestão de produtos.
Atualmente, um CSV passa por um processo aproximadamente equivalente a:
CSV
↓
csvRowToProduct
↓
enrichProduct
↓
validateProduct
↓
produto verificado
↓
persistênciaO JSON segue a mesma ideia geral:
JSON
↓
jsonProductToProduct
↓
enrichProduct
↓
validateProduct
↓
produto verificado
↓
persistênciaOs adapters lidam com as diferenças entre as representações externas.
A camada de enriquecimento normaliza e preenche os valores necessários.
A camada de validação determina se o produto resultante atende aos requisitos da nossa aplicação.
Essa separação é importante.
O catálogo em si não deveria se importar se um produto originalmente veio de um arquivo CSV, documento JSON, provedor de afiliados ou qualquer outra fonte.
Depois que a ingestão termina, ele deve simplesmente ser um produto.
Uma Estrutura de Produto, Múltiplas Fontes
Essa é a direção arquitetural que queremos preservar.
Imagine que os produtos cheguem de várias fontes:
CSV da Shopee
│
▼
CSV Adapter
│
├──────────────┐
│
JSON de parceiro │
│ │
▼ │
JSON Adapter │
│ │
└──────┬──────┘
▼
Enriquecimento
│
▼
Validação
│
▼
Produto CanônicoAo final desse processo, a origem passa a ser secundária.
A aplicação recebe uma representação consistente do produto contendo informações como:
- nome
- imagem
- URL
- URL da oferta
- preço
- comissão
- taxa de comissão
- loja
- categoria
- afiliado
- descrição
- tier
- prioridade
- estoque
O princípio arquitetural mais importante aqui é que produtos globais e produtos de tenants não deveriam exigir schemas de produto diferentes simplesmente porque pertencem a catálogos diferentes.
Eles continuam sendo produtos.
O que muda é onde eles são armazenados e como são selecionados.
Introduzindo o Global Product Pool
O próximo passo é introduzir uma coleção persistente de produtos globais.
Conceitualmente:
tenants/
tenantA/
products/
tenantB/
products/
globalProducts/
productA
productB
productCOs produtos dos tenants continuam sendo específicos de cada tenant.
Os produtos globais passam a ser um pool compartilhado que pode ser utilizado pela experiência global e, quando apropriado, como catálogo de fallback para os tenants.
Isso nos dá um modelo muito mais claro:
Ingestão de Produtos
│
┌────────┴────────┐
│ │
Tenant Global
│ │
▼ ▼
Produtos do Tenant Produtos GlobaisO processo de ingestão continua sendo o mesmo.
Apenas o destino da persistência muda.
O Catalog Resolver Não Precisa Mudar Muito
Uma das coisas mais interessantes da arquitetura atual é que o consumidor já possui uma abstração útil.
Atualmente, a aplicação solicita:
const { products: catalog, source: catalogSource } =
await getCatalogForTenant(tenant);A experiência global não precisa saber de onde esses produtos vieram.
Esse contrato pode permanecer.
A diferença está no que acontece por trás dele.
O fluxo futuro passa a ser:
Experiência Global
│
▼
getCatalogForTenant(null)
│
▼
Global Product PoolPara um tenant:
Experiência do Tenant
│
▼
getCatalogForTenant(tenant)
│
├── Produtos do tenant disponíveis
│ ↓
└── Catálogo do tenantEssa é uma mudança pequena, mas com uma consequência arquitetural bastante significativa.
O catálogo global passa a ser um recurso real do domínio, em vez de um fixture de desenvolvimento.
Colocando a Lomadee de Volta no Lugar ao Qual Ela Pertence
O código atual de transformação da Lomadee foi útil porque adaptava uma representação externa para o formato esperado pela aplicação.
Mas agora temos um sistema de ingestão mais genérico.
Em vez de manter uma lógica especial de catálogo como:
JSON da Lomadee
↓
mapLomadeeProduct()
↓
catálogo globalpodemos eventualmente tratar a Lomadee apenas como mais uma fonte possível:
Lomadee
↓
Lomadee Adapter
↓
Produto CanônicoO mesmo poderia futuramente acontecer com outros provedores.
Isso nos dá um modelo muito mais escalável:
┌── CSV
│
├── JSON
│
Fontes externas ├── Lomadee
│
├── Shopee
│
└── Futuros provedores
│
▼
Camada de Ingestão
│
▼
Produto Canônico
│
┌──────┴──────┐
▼ ▼
Pool do Tenant Pool GlobalA camada de experiência não precisa mudar quando uma nova fonte é introduzida.
Um Fluxo de Importação Administrativo
Essa arquitetura também abre caminho para algo que intencionalmente evitamos até que o pipeline subjacente estivesse pronto: gerenciar o catálogo global através de uma interface administrativa.
O fluxo pretendido é aproximadamente:
Admin
│
│ upload CSV / JSON
▼
Autenticação
│
▼
Verificação
│
├── produtos válidos
├── avisos
└── produtos inválidos
│
▼
Preview
│
▼
Commit
│
▼
Global Product PoolÉ aqui que a funcionalidade de verificação existente se torna particularmente útil.
Um administrador não deveria precisar simplesmente enviar um arquivo e torcer para que os dados estejam corretos.
O sistema pode primeiro apresentar:
Total de produtos: 1.000
Válidos: 963
Avisos: 21
Inválidos: 37Os produtos válidos podem então ser visualizados antes que qualquer coisa seja efetivamente gravada no catálogo global.
Verificação Antes da Persistência
Essa separação é importante o suficiente para ser considerada parte da arquitetura, e não apenas uma funcionalidade da interface.
O ciclo de vida desejado é:
Dados Brutos
↓
Parse
↓
Normalização
↓
Enriquecimento
↓
Verificação
↓
Preview
↓
CommitE não:
Upload
↓
Gravar tudo em produçãoIsso nos dá a oportunidade de rejeitar produtos malformados antes que eles façam parte da experiência.
Também significa que as mesmas regras de verificação serão aplicadas independentemente de os dados virem de CSV ou JSON.
Produtos Globais Devem Ser Independentes da Fonte
Talvez a consequência mais importante dessa mudança seja que a experiência global se torna independente do provedor original.
Hoje, o modelo mental ainda é, de certa forma:
Produtos globais = produtos da LomadeeO objetivo é mudar isso para:
Produtos globais = produtos validados disponíveis para o catálogo globalSão conceitos muito diferentes.
A Lomadee se torna uma fonte.
CSV se torna um formato de transporte.
JSON se torna um formato de transporte.
O catálogo global se torna um conceito próprio do domínio.
Mantendo um Fallback Temporário
Também não há motivo para tornar essa migração desnecessariamente arriscada.
Durante a transição, o conjunto de dados existente da Lomadee pode permanecer como um fallback legado.
A lógica de resolução pode efetivamente se tornar:
Tentar o Global Product Pool
│
├── produtos disponíveis
│ ↓
│ catálogo global
│
└── vazio
↓
fallback legado da LomadeeIsso nos proporciona um caminho seguro de migração.
Podemos fazer o upload de um conjunto de dados real e validado, testar a experiência global, monitorá-la e só remover a fonte legada quando tivermos confiança de que o novo catálogo está funcionando corretamente.
A Arquitetura Para a Qual Estamos Caminhando
O cenário completo começa a ficar assim:
SISTEMA ADMIN
│
Upload autenticado
│
Arquivos CSV / JSON
│
▼
┌───────────────┐
│ Adapters │
└───────┬───────┘
│
▼
┌───────────────┐
│ Enriquecimento│
└───────┬───────┘
│
▼
┌───────────────┐
│ Verificação │
└───────┬───────┘
│
▼
Produtos Canônicos
│
│
▼
Global Pool
│
│
▼
Global UXE o serviço de catálogo continua sendo a fronteira entre toda essa complexidade e a experiência propriamente dita.
O Que Vem a Seguir
O próximo passo de implementação não é redesenhar o pipeline de ingestão.
É reutilizá-lo.
O principal trabalho será fazer com que a camada de persistência compreenda um destino:
global\
Em seguida, vamos introduzir um endpoint administrativo autenticado capaz de enviar dados CSV e JSON através do pipeline de verificação existente.
A primeira versão não precisa resolver todos os problemas futuros do catálogo.
Ela precisa apenas estabelecer esta relação:
Produto Validado
↓
Global Product Pool
↓
Experiência GlobalUma vez que isso exista, teremos uma base muito mais sólida para tudo o que vier depois.
Podemos futuramente pensar em composição de catálogos, múltiplos provedores de afiliados, catálogos sazonais, priorização, deduplicação, gerenciamento do ciclo de vida dos produtos e regras de seleção global mais sofisticadas.
Mas esses recursos passam a ser extensões de um catálogo de produtos real, em vez de soluções alternativas construídas em torno de um dataset mockado.
E esse provavelmente é o ponto de mudança arquitetural mais importante aqui.
A experiência global não vai mais consumir uma fonte mockada que, por acaso, contém produtos. Ela vai consumir o nosso próprio catálogo de produtos validados.