Pular para o conteúdo

Do REST para o Lavra

O que muda, o que não muda, e o caminho de migração que não exige parar.

A migração que funciona não é reescrever a API: é pôr o Lavra ao lado do REST, sobre os mesmos casos de uso, e mover tela por tela. As duas convivem no mesmo servidor.

No RESTNo LavraO que muda de verdade
GET /produtosbusca: produtoso cliente escolhe os campos
GET /produtos/1busca: produto(id: 1)nada, exceto a forma
POST /produtosmudanca: criarProdutoo retorno traz o objeto criado
GET /produtos/1/categoriacampo categoria dentro de produtosome a segunda ida à rede
?campos=nome,precoa própria consultadeixa de ser convenção e passa a ser tipo
404void no campoausência não é erro, e o cliente trata diferente
Cache-Controlnão háo cache volta a ser problema do cliente e do resolvedor

Os dois no mesmo servidor#

dataforge
adopt Arcane.Lavra as Lavra
adopt Arcane.Kiln as Kiln

record Produto:
    id: Integer
    nome: String

PRODUTOS := [Produto(1, "café"), Produto(2, "filtro")]

esq := Lavra.esquema("loja")
Lavra.tipo(esq, Produto)
Lavra.busca(esq, "produtos", "[Produto!]!",
    resolve := lambda r, a, c => PRODUTOS)
Lavra.conferir(esq)

// O REST continua, e o Lavra entra ao lado dele.
action listar_rest(req):
    yield Kiln.json([{"id": p.id, "nome": p.nome} cycle p in PRODUTOS])

app := Kiln.app()
Kiln.get(app, "/api/produtos", listar_rest)
Lavra.montar(app, esq, "/lavra")

// e os dois respondem
rest := Kiln.test(app, "GET", "/api/produtos")
assert rest["status"] is 200
grafo := Lavra.executar(esq, "busca:\n    produtos:\n        nome")
assert len(grafo["dados"]["produtos"]) is 2
out "REST em /api/produtos e Lavra em /lavra, no mesmo app"

O que o Lavra NÃO resolve#

  • Cache de HTTP. Uma consulta é um POST com corpo; nenhum CDN a cacheia sozinho. Quem quer cache de borda continua no REST, ou usa consulta guardada com GET.
  • Upload de arquivo. Há convenções (multipart com um mapa de variáveis), e todas são mais complicadas que um POST /upload.
  • Autorização. Ela continua sendo sua — e agora por campo, não por rota, o que é mais trabalho e mais preciso.
  • O N+1. Ele piora: o cliente é quem decide a profundidade. Sem lote, a primeira tela de alguém derruba o banco.
  • Versionamento. "Não precisa versionar" só vale enquanto ninguém remove campo — e Lavra marca obsoleto justamente porque remover é inevitável.

O campo que vai sumir avisa antes#

dataforge
adopt Arcane.Lavra as Lavra

record Produto:
    id: Integer
    nome: String
    titulo: String

esq := Lavra.esquema("loja")
Lavra.tipo(esq, Produto)
Lavra.campo(esq, "Produto", "titulo", "String",
    resolve := lambda p, a, c => p.nome,
    obsoleto := "use 'nome'; 'titulo' sai na 3.0")
Lavra.busca(esq, "produto", "Produto",
    resolve := lambda r, a, c => Produto(1, "café", "café"))
Lavra.conferir(esq)

// Ele continua RESPONDENDO — quebrar no dia do aviso não é aviso.
r := Lavra.executar(esq, "busca:\n    produto:\n        titulo")
assert r["dados"]["produto"]["titulo"] is "café"
assert len(r["erros"]) is 0

// …e o aviso viaja em 'extensoes.avisos', que é onde um cliente
// procura o que vai quebrar depois.
avisos := r["extensoes"]["avisos"]
assert len(avisos) is 1
out avisos[0]["mensagem"]

// E o esquema diz que ele está de saída.
assert "obsoleto" in Lavra.texto_do_esquema(esq)

Um campo obsoleto que para de funcionar no dia do aviso não é um aviso, é uma quebra com aviso prévio de zero. O valor do marcador está em ele continuar respondendo enquanto a ferramenta do cliente já reclama.