Sitemap

Gemini: un framework per la generazione automatica di API REST

4 min readMay 9, 2019

Sviluppare API di tipo REST (o quasi) oramai è diventato piuttosto semplice, esistono una moltitudine di framework per ogni tipo di linguaggio (dai più moderni tipo node ai più maturi come Java). Possiamo utilizzare sia microframework per definire la logica ogni singola rotta (tipo express), che utilizzare dei framework più strutturati e che riescono ad astrarre anche nella memorizzazione dei dati (come Spring o Loopback).

Ogni framework (e filosofia) ha i suoi vantaggi e svantaggi. Per applicazioni personali o piccoli progetti sicuramente andare di micro-framework è una scelta sicuramente sensata. Il vantaggio si ha soprattutto quando il progetto muta in continuazionei: di solito quando i requisiti non sono definiti o si sta sperimentando. Lo svantaggio si ha quando le entità iniziano ad aumentare e ci si accorge che si sta riscrivendo sempre la stessa cosa (con qualche variazione). Utilizzare un framework strutturato è sicuramente più lento/complesso (soprattutto all’inizio se non lo si conosce), bisogna fare le cose come dice il framework e non è semplicissimo deviare dalla filosofia sottostante. Tuttavia per progetti enterprise e di una certa entità avere una filosofia e processo di sviluppo è fondamentale. Creare un applicazione enterprise in express vi assicuro che richiede persone di qualità notevole e ben organizzate affinché il tutto non degeneri in poco tempo.

Detto ciò con Gemini proviamo ad unire i due mondi, per creare un framework strutturato, ma flessibile allo stesso tempo… Impossibile? Forse no…

Se siete impazienti potete trovare il repository con le primissime versioni qui

In questo articolo invece partiamo con il definire il DSL di Gemini , ovvero lo schema delle entità/api REST.

Un idea di DSL

Ho lavorato con diversi framework che consentono di definire direttamente il modello dati (come Spring Data Rest) o magari estrapolare il modello dati direttamente dalle istanze (o dal JSON) come LoopBack. Sono potenti non c’è che dire ma quando le risorse aumentano di complessità (e annidamento) si sente la pesantezza e bisogna sempre mettere le mani qua e la (soprattutto con le relazioni).

L’obiettivo è quello di definire le entità (risorse REST) ragionando in modo astratto, definendo ogni entità come se fosse un vero e proprio tipo di dato. Prima di andare nel dettaglio ecco un esempio molto semplificato per le API che esprimono l’acquisto di libri.

# Entity Abstract Type Definition - Book OrdersINTERFACE Domain {
TEXT name *
TEXT description
}

# Entity Abstract Type Definition - Book Orders

ENTITY CATEGORY IMPLEMENTS Domain
ENTITY COUNTRY IMPLEMENTS Domain

ENTITY Book {
TEXT isbn *
TEXT name
AUTHOR author
DECIMAL price
[TEXT] tags
}

ENTITY Author {
TEXT name *
COUNTRY country
DATE birthdate
}

ENTITY BookOrder {
Book book
QUANTITY quantity
DECIMAL finalPrice
DATETIME dateTime
}

Non c’è bisogno di spiegare il senso, la definizione è veramente molto intuitiva. Ma più nel dettaglio

  • ogni entità (definita come ENTITY) è trattata come un vero e proprio tipo (corrispondente al nome dell’ entità)
  • le entità sono definite sempre e solo con un unico livello di annidazione sui campi (in sostanza come se fossero campi di una tabella di tipo relazionale)
  • Esistono i più comuni tipi primitivi (date, time, datetime, number, double, text e così via) e array su questi tipi.
  • Le relazioni vengono espresse con campi di tipo riferimento ad entità. Ad esempio AUTHOR come riferimento singolo e [CATEGORY] come array di riferimenti nell’ entità BOOK
  • ogni entità può avere una chiave logica che identifica univocamente un record dell’ entità (espressa con il simbolo * ). Le entità possono avere una chiave logica multipla.
  • Per evitare di replicare la definizione dei campi delle entità simili è possibile definire delle interfacce che le entità possono implementare. Ad esempio CATEGORY e COUNTRY le possiamo trattare come se fossero dei domini, dove ci serve il nome come chiave (e una descrizione).

Sembra troppo semplice, ma vi assicuro che già così il DSL è sufficiente per esprimere al completo una risorsa REST (entità) e anche il formato corpo delle richieste/risposte JSON.

Risorse e rotte

E’ chiaro che con il semplice schema sopra debbano valere delle forti assunzioni per l’autogenerazione delle rotte e delle risorse. Ma supponendo di non avere altri metadati potremmo avere le seguenti risorse

# per ogni risorsa -> POST (inserimento) GET (lista)/api/category
/api/country
/api/book
/api/author
/api/bookorder
# per ogni risorsa sul singolo id -> PUT GET DELETE
# dove lk sta per chiave logica (logical key)
/api/category/:lk
/api/country/:lk
/api/book/:lk
/api/author/:lk
/api/bookorder/:lk

Un esempio di richiesta potrebbe essere la seguente

# GET /api/book/978-0553808049{  
"author":"George R. R. Martin",
"isbn":"978-0553808049",
"name":"A Game of Thrones",
"price":37.4,
"tags":[]
}

dove:

  • per i campi riferimento possiamo utilizzare le chiavi logiche o l’intero oggetto (decidibile magari con un header custom)
  • non abbiamo assolutamente fatto riferimento a come i dati vengono poi trattati nel backend. Esistono solamente chiavi logiche e campi primitivi in notazione JSON.
  • è chiaro che andrà definito un formalismo (e un interfaccia) per i tipi di dato più particolari, come le date o gli istanti temporali.

Siete curiosi in merito agli avanzamenti ? Ecco il repository…

Fatemi sapere cosa ne pensate!!!!!

Andrea Tarquini
Andrea Tarquini

Written by Andrea Tarquini

#software #developer and #engineer ➙ Proud #italian 🇮🇹 #geek ➙ #fullstack and #maker for fun

No responses yet