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#
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:
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.
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:
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#
out Lavra.mapa(portao){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.