Pular para o conteúdo

Federação

Vários serviços, um esquema só para quem consulta: o portão, a composição e o campo que atravessa a fronteira.

Cada time tem o seu serviço, e cada serviço tem o seu esquema. Quem consulta não quer saber disso: quer usuario.pedidos.itens numa consulta, sem descobrir que usuário mora num lugar e pedido em outro.

O portão#

dataforge
portao := Lavra.portao()

Lavra.juntar(portao, "contas", esquema_de_contas)
Lavra.juntar(portao, "vendas", esquema_de_vendas)
Lavra.juntar(portao, "catalogo", esquema_de_catalogo)

portao.conferir()
Lavra.montar(api, portao.esquema, "/lavra")

Os tipos de cada serviço entram num esquema só. As buscas de cada um viram buscas do portão. Quem consulta vê um esquema.

O campo que atravessa#

Usuario é de contas; Pedido é de vendas. O campo usuario.pedidos não pertence a nenhum dos dois sozinho — ele é a fronteira, e é declarado como extensão:

dataforge
action pedidos_do_usuario(usuario, args, ctx):
    yield Lavra.pedir(ctx, "pedidos_por_usuario", usuario["id"])

Lavra.estender(portao, "Usuario", "pedidos", "[Pedido!]!",
    resolve := pedidos_do_usuario)

Repare no Lavra.pedir: a fronteira é exatamente onde o N+1 dói mais, porque cada travessia é uma chamada de rede. Cinquenta usuários viram uma chamada ao serviço de vendas, e não cinquenta.

dataforge
action buscar_pedidos(ids_de_usuario):
    r := cliente_de_vendas.consultar("""
busca Por($ids: [Integer!]!):
    pedidosPorUsuario(ids: $ids):
        usuario_id
        numero
        total
""", {"ids": ids_de_usuario})
    yield agrupar_por(r["dados"]["pedidosPorUsuario"], "usuario_id", ids_de_usuario)

Conflito de nome é erro#

Dois serviços que declaram Usuario param a composição:

text
erro: 'Usuario' é declarado por 'contas' e por 'perfis'.
  Dois tipos com o mesmo nome fariam a consulta devolver os campos de
  um ou de outro conforme a ordem do 'juntar'.
  Renomeie um dos dois, ou declare o tipo num serviço só e estenda-o
  do outro com Lavra.estender.

Fundir os dois em silêncio faria a resposta depender da ordem do `juntar` — que é o pior jeito de falhar: funciona na máquina de quem escreveu e muda quando alguém reordena duas linhas.

De onde vem cada campo#

dataforge
out Lavra.mapa(portao)
text
{servicos: [catalogo, contas, vendas],
 tipos: {Usuario: contas, Pedido: vendas, Produto: catalogo},
 extensoes: [{tipo: Usuario, campo: pedidos}]}

É a resposta para "quem declara isto?" — a pergunta que mais se faz num esquema federado, e a que mais custa responder lendo código de três repositórios.

O que este portão NÃO faz#

Ele não é o Apollo Federation. Não há @key, @external, _entities nem plano de consulta distribuído: o portão resolve a extensão chamando o serviço dono, uma vez por fronteira, com o lote fazendo o agrupamento.

Um planejador de consulta distribuído é um projeto próprio — ele decide quais subconsultas mandar, em que ordem, e como juntar os pedaços. Um subconjunto pela metade seria pior que a honestidade de não ter.