Gemini Backend: LowCode REST API - parte 1

6 minuti di lettura

Gemini Rest

In questo breve articolo introduttivo vi voglio fare vedere come è semplice ed immediato creare API REST sfruttando la piattaforma Gemini e l'approccio Low Code combinato al Domain Driven Design. In pratica scriveremo uno schema per il modello dati, e lanceremo un'immagine docker che farà tutto il resto. Da programmatore il Low Code per me ha senso quando ti aiuta a generalizzare parti comuni, o comunque ripetitive e ti lascia concentrare su tutto quello che aggiunge valore a ciò che stai facendo, il tutto ovviamente senza limitare lo sviluppatore.

Introduzione su Gemini

Gemini è un framework che sto sviluppando per velocizzare la parte di sviluppo delle API REST che mi capita di dover scrivere nel mio lavoro (e anche le tipiche GUI come tabelle e form, ma ne parlerò nel dettaglio nei prossimi articoli).

In pratica la parte ripetitiva di tutte le API REST è la scrittura dell'interfaccia dell'API stessa: quindi i soliti controller POST/PUT/GET di creazione, modifica e recupero dati, i controller di search per la ricerca dati e paginazione e così via. Quello che ho fatto quindi è creare un modulo che si preoccupa di gestire tutta la parte iniziale dell'API: controller, marshaling, gestione parametri ecc.

Il programmatore si occupa quindi solo di definire le configurazioni dell'API, usando principalmente uno schema tipizzato. Per quanto riguarda la memorizzazione dei dati dello schema si possono usare driver comuni già implementati (SQL, NoSql, file, ecc..) oppure implementare ad hoc il driver di persistenza a seconda di ciò di cui si ha bisogno.

In quest articolo userò il driver che ho sviluppato per MongoDB e un'immagine docker già pronta all'uso e pubblica su Docker HUB. Basterà lanciare l'immagine con un paio di parametri ed il gioco è fatto.

Step 1 - Definizione dello schema (Domain Driven Design)

Un'esempio di schema lo potete trovare nella directory di esempi del repository ufficiale. Lo riporto anche qua, ma vi potete sbizzarrire a definire quello che volete. Nell'esempio ho messo solo due entità molto semplici: Prodotti e Categorie. La struttura dello schema è autoesplicativa, abbiamo entità (tabelle in SQL, o collezioni in MongoDb) in cui vengono definiti i vari campi e la chiave logica (lk) da usare nelle API e come unique key.

type: ENTITY
entity:
  name: CATEGORY
  lk: [id]
  fields:
    - name: id
      type: STRING
      required: true
    - name: description
      type: STRING

---

type: ENTITY
entity:
  name: PRODUCT
  lk: [id]
  fields:
    - name: id
      type: STRING
      required: true
    - name: name
      type: STRING
    - name: description
      type: STRING
    - name: available
      type: BOOL
    - name: status
      type: ENUM
      enums: [DRAFT, PENDING, PRIVATE, PUBLISH]
    - name: regular_price
      type: DOUBLE
    - name: sale_price
      type: DOUBLE
    - name: categories
      type: ARRAY
      array:
        type: ENTITY_REF
        entityRef:
          entity: CATEGORY

Step 2 - Run dell'immagine

Bene ora possiamo lanciare Gemini che esporrà le nostre API REST sulla base delle entità definite nello schema.

docker run -p 8080:8080 \
-e GEMINI_SCHEMAS=/schemas/product_schema.yaml \
-e GEMINI_MONGODB_URL="mongodb+srv://_mongo_user:_mongo_pwd_@__path__.mongodb.net/db?retryWrites=true&w=majority" \
-e GEMINI_MONGODB_DB=starter \
-v $(pwd)/product_schema.yaml:/schemas/product_schema.yaml:ro \
aat7/gemini-micronaut-mongodb-restapi
  • Come database GEMINI_MONGODB_URL potete usare quello che volete. Per andare rapidi e provare vi consiglio di accedere a MongoDb Atlas e creare un cluster gratuito.
  • GEMINI_MONGODB_DB è il database in cui verranno create le collezioni per le varie entità dello schema
  • GEMINI_SCHEMAS serve ad indicare dove andare a prendere lo schema (o più di uno). Notare che lo schema è dentro il file product_schema.yaml della mia working directory che ho di conseguenza montato come volume all'interno del container.

Step 3 - Uso delle API

Ok ora siamo pronti per usare le API. Per lavorare con i dati delle varie entità dello schema il pattern del controller è /data/{entityName}, ma è più semplice farlo che dirlo. Iniziamo con l'inserire una categoria.

POST - Inseriamo qualche dato

curl --request POST "http://localhost:8080/data/category" \
--header "Content-Type: application/json" \
-d '{"data": {"id": "tech-1",
        "description": "Technology"
    }
  }'

La risposta ricevuta sarà qualcosa del tipo...

{
  "status":"success",
  "data": {
    "description":"Technology",
    "id":"tech-1"
  },
  "meta": {
    "lastUpdateTimeUnix":1640185713731,
    "lastUpdateTimeISO":"2021-12-22T15:08:33.731Z",
  }
}

O perché no, inserire più di una in una volta sola (in una singola transazione MongoDb).

curl --request POST "http://localhost:8080/data/category" \
--header "Content-Type: application/json" \
-d '{"data": [{
                  "id": "smartphone-1",
                  "description": "Smartphone"
              },
              {
                  "id": "clothing-1",
                  "description": "Clothing"
              },
              {
                  "id": "beauty-1",
                  "description": "beauty"
              }]
  }'

GET - Recupero dei dati

Con una GET alla root dell'entità /data/{entityName} possiamo recuperare tutti i suoi record.

curl "http://localhost:8080/data/category"

Attenzione: in questo caso ritornato tutti i record dell'entità Categoria. farò vedere in un altro articolo come gestire la paginazione per le entità che hanno un elevato numero di record. L'importante ora è sapere Gemini permette di configurare i vari controller REST delle entità dello schema in modo da abilitare o meno alcune feature interessanti e utili come la paginazione.

Continuiamo con le API, una GET del tipo /data/{entityName}/{recordId} ritornerà soltanto un singolo record.

curl "http://localhost:8080/data/category/smartphone-1"

PUT e DELETE

Put e DELETE seguono i principi CRUD rispettivamente per la modifica e la cancellazione. Lascio a voi provare.

E per le cose complesse? Tipo filtri e paginazione?

Per quanto riguarda le varie ricerche e filtri su entità corpose ne parlerò nei prossimi articoli. Le feature principali sono già state implementate ed in parte anche in produzione su casi d'uso reale che contemplano svariate milioni record. Vi anticipo che in questo caso molto dipende dal driver di memorizzazione e recupero dati e ovviamente dalla tecnologia sottostante. Gemini si limita a processare e validare i vari filtri nelle richieste ed a preparare le query per il recupero dei dati.

Conclusioni

In questo articolo ho cercato di spiegare i punti chiave di Gemini, e come usarlo praticamente senza scrivere uan riga di codice. Tuttavia ho mostrato solo le feature più basilari, in reali contesti di produzione uso feature più avanzate sia di Gemini che di Micronaut, come l'autenticazione. Oppure mi capita di implementare driver di memorizzazioni dati ad-hoc (ad esempio su DB legacy).

Tenete in considerazione che Gemini è si in fase di sviluppo, ma è anche attualmente in produzione in aziende con cui collaboro. Se siete curiosi e volete provare a vedere se Gemini può essere utile per il vostro caso d'uso concreto contattatemi pure, sarò felice di aiutare e magari estendere la piattaforma.