Gemini Backend: LowCode REST API - parte 2
8 minuti di lettura
Nel primo articolo delle serie Gemini Backend: LowCode REST API abbiamo visto come è semplice creare delle API REST CRUD partendo da un modello dati tipizzato. Si può dire che con Gemini la parte più difficile (e che ha più valore) diventa quella di definizione del dominio dei dati. Ma ovviamente questo dipende da voi e dal vostro caso d'uso, Gemini vi spinge a pensare a quello che conta ( Dominio ) piuttosto che concentrarsi da subito sulla tecnologia o soluzioni a contorno (che è un errore che vedo fare di frequente).
In questo articolo vedremo come usare l'API GET per filtrare i record delle varie entità/collezioni del nostro dominio ed anche come usare l'API di count che diventa molto utile quando non vi servono subito i record ma si ha necessità di sapere se esistono.
Due parole su Gemini
Gemini è un framework che sto sviluppando per velocizzare la parte di sviluppo/utilizzo di API REST che mi capita di dover scrivere nel mio lavoro.
Il realtà è composto da una serie di moduli che backend e frontend che consentono di creare in poco tempo applicazioni web complete (fullstack) di stile gestionale, senza togliere al programmatore la possibilità di customizzare ed inserire il proprio codice sia lato backend che frontend.
In questo articolo ci concentriamo sulla parte backend, con il generatore di API REST, ma potete trovare tutti i dettagli ed i casi d'uso principali del progetto sul repository github ufficiale.
Step 0 - Schema e Run dell'immagine
Nel primo articolo viene spiegato in modo più approfondito come avviare l'immagine Docker del backend. Dateci un occhiata se vi è sfuggito, in ogno caso riporto anche qua lo schema che abbiamo utilizzato ed il comando di avvio. L'immagine è pubblica, non avete bisogno di installare nulla (a parte Docker se non lo avete).
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
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
Bene con lo schema fornito e l'avvio del servizio avrete in ascolto sulla porta 8080 il modulo backend di Gemini.
Step 1 - Inserimento dati
Nel primo articolo abbiamo inserito solo le categorie, ora invece conviene popolare il database con un pò di prodotti diversi, in modo da essere in grado di filtrare in base ai vari campi dell'entità Prodotti e soprattutto in base al tipo dei vari campi.
Inseriamo un pò di prodotti tecnologici, in particolare Iphone 13 con differenti colori e parametri.
curl --request POST "http://localhost:8080/data/product" \
--header "Content-Type: application/json" \
-d '{"data": [{
"id": "iphone-13-128-blue",
"name": "Iphone 13 128 GB Blue",
"description": "Apple Iphone 13 - Memory: 128 GB - Color: Blue",
"available": true,
"status": "PUBLISH",
"regular_price": 799,
"sale_price": 699,
"categories": ["tech-1", "smartphone-1"]
},
{
"id": "iphone-13-128-starlight",
"name": "Iphone 13 128 GB Starlight",
"description": "Apple Iphone 13 - Memory: 128 GB - Color: Starlight",
"available": true,
"status": "PUBLISH",
"regular_price": 799,
"sale_price": 699,
"categories": ["tech-1", "smartphone-1"]
},
{
"id": "iphone-13-128-midnight",
"name": "Iphone 13 128 GB Midnight",
"description": "Apple Iphone 13 - Memory: 128 GB - Color: Midnight",
"available": true,
"status": "PUBLISH",
"regular_price": 799,
"sale_price": 699,
"categories": ["tech-1", "smartphone-1"]
},
{
"id": "iphone-13-128-pink",
"name": "Iphone 13 128 GB Pink",
"description": "Apple Iphone 13 - Memory: 128 GB - Color: Pink",
"available": true,
"status": "PUBLISH",
"regular_price": 799,
"sale_price": 699,
"categories": ["tech-1", "smartphone-1"]
},
{
"id": "iphone-13-128-red",
"name": "Iphone 13 128 GB Red",
"description": "Apple Iphone 13 - Memory: 128 GB - Color: Product RED",
"available": true,
"status": "PUBLISH",
"regular_price": 799,
"sale_price": 699,
"categories": ["tech-1", "smartphone-1"]
},
{
"id": "iphone-13-256-blue",
"name": "Iphone 13 256 GB Blue",
"description": "Apple Iphone 13 - Memory: 256 GB - Color: Blue",
"available": true,
"status": "PUBLISH",
"regular_price": 899,
"sale_price": 799,
"categories": ["tech-1", "smartphone-1"]
},
{
"id": "iphone-13-256-starlight",
"name": "Iphone 13 256 GB Starlight",
"description": "Apple Iphone 13 - Memory: 256 GB - Color: Starlight",
"available": true,
"status": "PUBLISH",
"regular_price": 899,
"sale_price": 799,
"categories": ["tech-1", "smartphone-1"]
},
{
"id": "iphone-13-256-midnight",
"name": "Iphone 13 256 GB Midnight",
"description": "Apple Iphone 13 - Memory: 256 GB - Color: Midnight",
"available": true,
"status": "PUBLISH",
"regular_price": 899,
"sale_price": 799,
"categories": ["tech-1", "smartphone-1"]
},
{
"id": "iphone-13-256-pink",
"name": "Iphone 13 256 GB Pink",
"description": "Apple Iphone 13 - Memory: 256 GB - Color: Pink",
"available": true,
"status": "PUBLISH",
"regular_price": 899,
"sale_price": 799,
"categories": ["tech-1", "smartphone-1"]
},
{
"id": "iphone-13-256-red",
"name": "Iphone 13 256 GB Red",
"description": "Apple Iphone 13 - Memory: 256 GB - Color: Product RED",
"available": true,
"status": "PUBLISH",
"regular_price": 899,
"sale_price": 799,
"categories": ["tech-1", "smartphone-1"]
}]
}'
{
"status": "success",
"data": [
{
"regular_price": 799.0,
"name": "Iphone 13 128 GB Blue",
"available": true,
"description": "Apple Iphone 13 - Memory: 128 GB - Color: Blue",
"id": "iphone-13-128-blue",
"categories": [
"tech-1",
"smartphone-1"
],
"sale_price": 699.0,
"status": "PUBLISH"
},
// ....
// ....
// ....
{
"regular_price": 899.0,
"name": "Iphone 13 256 GB Red",
"available": true,
"description": "Apple Iphone 13 - Memory: 256 GB - Color: Product RED",
"id": "iphone-13-256-red",
"categories": [
"tech-1",
"smartphone-1"
],
"sale_price": 799.0,
"status": "PUBLISH"
}
],
"meta": {
"lastUpdateTimeUnix": 1640703311085,
"lastUpdateTimeISO": "2021-12-28T14:55:11.085Z",
"elapsedTime": "479ms"
}
}
Step 2 - Filtri e Count
Se con una GET alla root dell'entità /data/{entityName}
possiamo recuperare tutti i suoi record, con una GET a /entity/{entityName}/recordCounts
è possibile contarli.
Count prodotti
curl "http://localhost:8080/entity/product/recordCounts"
{
"status": "success",
"data": {
"count": 10
},
"meta": {
"elapsedTime": "48ms"
}
}
Filtri
Veniamo ora alla parte interessante, ovvero filtrare le entità in base ai valori dei campi contenuti nei record.
Stringhe
Il campo name
dell'entità Product
è una stringa. Il driver mongoDb attualmente supporta il CONTAINS
e ovviamente l' EQUALS
.
# Count - Iphone 13 128 GB Blue - NB: the space is %20 in curl url
curl "http://localhost:8080/entity/product/recordCounts?name=Iphone%2013%20128%20GB%20Blue"
{"status":"success","data":{"count":1},"meta":{"elapsedTime":"38ms"}}
# Get Record - Iphone 13 128 GB Blue
curl "http://localhost:8080/data/product?name=Iphone%2013%20128%20GB%20Blue"
{"status":"success","data":[{"regular_price":799.0,"name":"Iphone 13 128 GB Blue","available":true,"description":"Apple Iphone 13 - Memory: 128 GB - Color: Blue","id":"iphone-13-128-blue","categories":["tech-1","smartphone-1"],"sale_price":699.0,"status":"PUBLISH"}],"meta":{"elapsedTime":"49ms"}}
Per usare CONTAINS
la sintassi diventa /data/{entityName}?fieldName[CONTAINS]=value
. Ad esempio contiamo tutti i record
che contengono 256 nel campo name
e recuperiamo tutti i record che contengono Blue.
# Count - Name CONTAINS 256 - result is 5
curl "http://localhost:8080/entity/product/recordCounts?name[CONTAINS]=256"
{"status":"success","data":{"count":5},"meta":{"elapsedTime":"40ms"}}
# Get Records - Name CONTAINS 256 - result is 2 records
curl "http://localhost:8080/data/product?name[CONTAINS]=Blue"
{"status":"success","data":[{"regular_price":799.0,"name":"Iphone 13 128 GB Blue","available":true,"description":"Apple Iphone 13 - Memory: 128 GB - Color: Blue","id":"iphone-13-128-blue","categories":["tech-1","smartphone-1"],"sale_price":699.0,"status":"PUBLISH"},{"regular_price":899.0,"name":"Iphone 13 256 GB Blue","available":true,"description":"Apple Iphone 13 - Memory: 256 GB - Color: Blue","id":"iphone-13-256-blue","categories":["tech-1","smartphone-1"],"sale_price":799.0,"status":"PUBLISH"}],"meta":{"elapsedTime":"41ms"}}
Campi numerici
Il campo regular_price
è un DOUBLE. Per semplicità riporto solo esempi con l'API di COUNT, ma il recupero dei record segue lo stesso
principio.
Dato che si tratta di un campo numerico abbiamo a disposizione, la solita uguaglianza, ma anche operatori comuni come > < >= <=
rispettivamente
GT LT GTE LTE
. Abbiamo inserito 5 prodotti con regular_price
di 799 ed altri 5 con regular_price
di 899.
# 10 products > 700
curl "http://localhost:8080/entity/product/recordCounts?regular_price[GT]=700"
{"status":"success","data":{"count":10},"meta":{"elapsedTime":"40ms"}}
# 5 products > 799
curl "http://localhost:8080/entity/product/recordCounts?regular_price[GT]=799"
{"status":"success","data":{"count":5},"meta":{"elapsedTime":"40ms"}}
# 10 production >= 799
curl "http://localhost:8080/entity/product/recordCounts?regular_price[GTE]=799"
{"status":"success","data":{"count":10},"meta":{"elapsedTime":"39ms"}}
Una feature interessante è la possibilità di avere più logiche su uno stesso campo in modo
da poter esprimere anche intervalli in cui fare la ricerca. Ed ovviamente nessuno vieta di
fare ricerche su campi diversi con la medesima logica implicita di AND
.
# 5 products with 750 < regular_price < 850
curl "http://localhost:8080/entity/product/recordCounts?regular_price[GT]=750®ular_price[LT]=850"
{"status":"success","data":{"count":5},"meta":{"elapsedTime":"44ms"}}
# 1 products with 750 < regular_price < 850 and name containing
curl "http://localhost:8080/entity/product/recordCounts?regular_price[GT]=750®ular_price[LT]=850&name[CONTAINS]=Blue"
{"status":"success","data":{"count":1},"meta":{"elapsedTime":"39ms"}}
Campi booleani
Il campo available
è un BOOL. Dato che abbiamo inserito solo record con available a true il count a false darà 0
mentre true sarà 10.
# 10 products available
curl "http://localhost:8080/entity/product/recordCounts?available=true"
{"status":"success","data":{"count":10},"meta":{"elapsedTime":"39ms"}}
# 0 products not available
curl "http://localhost:8080/entity/product/recordCounts?available=false"
{"status":"success","data":{"count":0},"meta":{"elapsedTime":"42ms"}}
Conclusioni
In questo articolo abbiamo fatto un piccolo passo avanti nell'esplorazione delle feature del modulo backend di Gemini. Abbiamo visto in particolare come usare i filtri (anche complessi) sia per contare i record che per recuperarne il contenuto.
Tuttavia l'esempio in generale è molto basilare. In reali contesti di produzione uso feature più avanzate sia di Gemini che di Micronaut, come l'autenticazione, la paginazione sulle entità corpose e tipi di dato più complessi da gestire come ARRAY ed OGGETTI annidati.
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.