vai al contenuto principale

Scrivere dati

I servizi di scrittura consentono di creare o aggiornare grafi di oggetti a partire da una certa classe.

La API GraphQL Livebase consente di scrivere oggetti sul database mediante operazioni di tipo mutation. Sono supportati tutti gli scenari CRUD:

Oltre ai servizi di scrittura veri e propri, che come già affermato sono di tipo mutation (vedi convenzioni sulla nomenclatura dei servizi), esistono due tipologie di servizi, di tipo query, che coprono due scenari “trasversali”, entrambi di supporto alle scritture:

  • Validate: servizi che consentono di simulare una scrittura verificandone la correttezza a livello di Data Validation, richiedendo eventuali Issue di questo tipo, ma senza sollevare errori lato server. Maggiori informazioni sono disponibili in fondo: type ValidationResult.
  • Preview: servizi che consentono di ottenere un’anteprima del risultato della scrittura, utile per ottenere informazioni calcolate dal server, come attributi derivati e oggetti associabili.

I servizi di scrittura restituiscono in output le strutture type <ClassName> e type <ClassName>Page, descritte per i servizi di lettura. A differenza di questi ultimi, le strutture di input usate nelle scritture sono molteplici e altamente specializzate. Per questo motivo, nei seguenti paragrafi abbiamo raggruppato input, servizi di scrittura e servizi di supporto in base allo scenario di cui fanno parte, tra quelli elencati sopra.

Modello di riferimento #

Tutti gli esempi in questa pagina fanno riferimento al modello in figura:

Employee

La classe Employee, al centro, come modellata nel Tutorial.

La classe Employee ha un ruolo per ogni tipologia di relazione supportata, nonchĂŠ attributi nativi, query, math e platform dichiarati su essa.

Creare nuovi oggetti #

I seguenti servizi e strutture consentono di creare nuovi oggetti o testarne la creazione.

Servizio create #

Il servizio Mutation.<ClassName>___create consente di creare un grafo di oggetti a partire da una certa classe.

type Mutation {
  ClassName___create(data: ClassNameCreate!, forceWarnings: ForceWarnings): ClassName
}

Il servizio richiede in input la struttura <ClassName>Create, e restituisce in output la struttura <ClassName> contenente l’oggetto appena creato. È possibile aggirare eventuali warning restituiti dal server causati da Class Warning non bloccanti includendo l’argomento forceWarnings, di tipo ForceWarnings.

Ad esempio, il servizio per creare un oggetto Employee è il seguente:

type Mutation {
  Employee___create(data: EmployeeCreate!, forceWarnings: ForceWarnings): Employee
}

Servizio previewCreate #

Il servizio Query.<ClassName>___previewCreate consente di ottenere un’anteprima del risultato della creazione di un grafo di oggetti, utile per ottenere informazioni calcolate dal server, come attributi derivati e oggetti associabili al grafo.

type Query {
  ClassName___previewCreate(data: ClassNameDraftCreate!): ClassName
}

Il servizio richiede in input la struttura <ClassName>DraftCreate, e restituisce in output la struttura <ClassName>, i cui campi calcolati sono pre-popolati, se possibile, a partire dalle informazioni inviate al server; gli associabili sono sempre disponibili per tutti i ruoli.

Ad esempio, il servizio per ottenere una preview di un oggetto Employee è il seguente:

type Query {
  Employee___previewCreate(data: EmployeeDraftCreate!): Employee
}

Servizio validateCreate #

Il servizio Query.<ClassName>___validateCreate riflette il servizio create, ma con lo scopo di verificare se il grafo di oggetti creato sia corretto a livello di Data Validation.

type Query {
  ClassName___validateCreate(data: ClassNameDraftCreate!): ValidationResult
}

Il servizio richiede in input la struttura <ClassName>DraftCreate (la stessa del servizio previewCreate e restituisce in output la struttura ValidationResult, che indica se il grafo è valido o meno e riporta eventuali Issue a livello di Data Validation.

Ad esempio, il servizio per validare la creazione di un oggetto Employee è il seguente:

type Query {
  Employee___validateCreate(data: EmployeeDraftCreate!): ValidationResult
}

input <ClassName>Create #

L’input type <ClassName>Create permette di specificare i dati necessari alla creazione di un oggetto della classe <ClassName>. Questo input ricalca la struttura type <ClassName>, con alcune importanti differenze.

Gli attributi vengono mappati come segue:

  • sono presenti solamente campi relativi ad attributi nativi (editabili), mentre sono esclusi attributi derivati e platform;
  • i campi relativi ad attributi required sono obbligatori (!).

Ad esempio, per la classe Employee, si ha la seguente struttura EmployeeCreate:

input EmployeeCreate {
# attributi
first_name: String! # è required
last_name: String! # è required
date_of_birth: Date! # è required
email_address: String
date_joined: Date
hourly_cost: Real
username: String

# ruoli uscenti
# ...
}

I ruoli uscenti vengono mappati come segue:

  • associazioni a uno: ID;
  • associazioni a molti: [ID];
  • part a uno: <PartClassName>Create (analogo a <ClassName>Create);
  • part a molti: [<PartClassName>Create] (analogo a [<ClassName>Create]).

Gli ID specificati per le associazioni devono far riferimento a istanze pre-esistenti per le classi puntate. I ruoli part consentono invece la creazione contestuale di un oggetto part attraverso il relativo input (per questo motivo si parla di creazione di un grafo di oggetti).

Per Employee, i ruoli uscenti vengono mappati come segue:

input EmployeeCreate {
  # attributi
  # ...

  # ruoli uscenti
  team: ID # associazione a 1
  supervisor: ID # associazione riflessiva a 1
  address: AddressCreate # composizione a 1
  qualification_: [ID] # associazione a N
  assignments: [Project_assignmentCreate] # composizione a N
}

input <ClassName>DraftCreate #

L’input type <ClassName>Create permette di specificare alcuni dati necessari alla creazione di un oggetto della classe <ClassName>; si tratta della controparte permissiva di <ClassName>Create, in cui nessun campo è required (!). Questo consente maggiore libertà ai servizi che la utilizzano (previewCreate e validateCreate).

Ad esempio, per la classe Employee, si ha la seguente struttura EmployeeDraftCreate:

input EmployeeCreate {
# attributi
first_name: String
last_name: String
date_of_birth: Date
email_address: String
date_joined: Date
hourly_cost: Real
username: String

# ruoli uscenti
team: ID # associazione a 1
supervisor: ID # associazione riflessiva a 1
address: AddressCreate # composizione a 1
qualification_: [ID] # associazione a N
assignments: [Project_assignmentCreate] # composizione a N
}

Aggiornare oggetti esistenti #

I seguenti servizi e strutture consentono di aggiornare oggetti pre-esistenti o testarne la modifica.

Servizio update #

Il servizio Mutation.<ClassName>___update consente di aggiornare uno o piĂš oggetti a partire da una certa classe, tramite un grafo di modifiche.

type Mutation {
  ClassName___update(data: ClassNameUpdate!, forceWarnings: ForceWarnings): ClassName
}

Il servizio richiede in input la struttura <ClassName>Update, e restituisce in output la struttura <ClassName> contenente l’oggetto appena aggiornato. È possibile aggirare eventuali warning restituiti dal server causati da Class Warning non bloccanti includendo l’argomento forceWarnings, di tipo ForceWarnings.

Ad esempio, il servizio per aggiornare un oggetto Employee è il seguente:

type Mutation {
  Employee___update(data: EmployeeUpdate!, forceWarnings: ForceWarnings): Employee
}

Un esempio di mutation con questo servizio è disponibile qui.

Servizio previewUpdate #

Il servizio Query.<ClassName>___previewUpdate consente di ottenere un’anteprima del risultato dell’aggiornamento di un grafo di oggetti, utile per ottenere informazioni calcolate dal server, come attributi derivati e oggetti associabili al grafo.

type Query {
  ClassName___previewUpdate(data: ClassNameDraftUpdate!): ClassName
}

Il servizio richiede in input la struttura <ClassName>DraftUpdate, e restituisce in output la struttura <ClassName>, i cui campi calcolati sono pre-popolati, se possibile, a partire dalle informazioni inviate al server; gli associabili sono sempre disponibili per tutti i ruoli.

Ad esempio, il servizio per ottenere una preview di un oggetto Employee aggiornato è il seguente:

type Query {
  Employee___previewUpdate(data: EmployeeDraftUpdate!): Employee
}

Servizio validateUpdate #

Il servizio Query.<ClassName>___validateUpdate riflette il servizio update, ma con lo scopo di verificare se il grafo di modifiche sia corretto a livello di Data Validation.

type Query {
  ClassName___validateUpdate(data: ClassNameDraftUpdate!): ValidationResult
}

Il servizio richiede in input la struttura <ClassName>DraftUpdate (la stessa del servizio previewUpdate e restituisce in output la struttura ValidationResult, che indica se il grafo è valido o meno e riporta eventuali Issue a livello di Data Validation.

Ad esempio, il servizio per validare l’aggiornamento di un oggetto Employee pre-esistente è il seguente:

type Query {
  Employee___validateUpdate(data: EmployeeDraftUpdate!): ValidationResult
}

input <ClassName>Update #

L’input type <ClassName>Update permette di specificare i dati necessari alla modifica di un oggetto pre-esistente della classe <ClassName>. Questo input ricalca la struttura type <ClassName>, con alcune importanti differenze.

Gli attributi vengono mappati come segue:

  • sono presenti solamente campi relativi ad attributi nativi (editabili), mentre sono esclusi attributi derivati e platform, a eccezione del campo _id: ID!;
  • a differenza di <ClassName>Create, i campi relativi ad attributi required non sono obbligatori;
  • l’unico campo obbligatorio è _id, necessario infatti a specificare quale istanza della classe si vuole modificare.

Ad esempio, per la classe Employee, si ha la seguente struttura EmployeeUpdate:

input EmployeeUpdate {
_id: ID!

# attributi
first_name: String
last_name: String
date_of_birth: Date
email_address: String
date_joined: Date
hourly_cost: Real
username: String

# ruoli uscenti
# ...
}

I ruoli uscenti vengono mappati come segue:

Per Employee, i ruoli uscenti vengono mappati come segue:

input EmployeeUpdate {
  # attributi
  # ...

  # ruoli uscenti
  team: TeamRoleRef # associazione a 1
  supervisor: EmployeeRoleRef # associazione riflessiva a 1
  address: AddressRoleObject # composizione a 1
  qualification_: QualificationRoleRefs # associazione a N
  assignments: Project_assignmentRoleObjects # composizione a N
}

input <ClassName>RoleRef(s) #

Gli input type <ClassName>RoleRef e <ClassName>RoleRefs hanno lo scopo di gestire la modifica dell’associazione verso la classe <ClassName> nel contesto del servizio update. Vengono generati per tutte le classi che hanno almeno un’associazione entrante managed; come suggerisce il nome, <ClassName>RoleRef viene generato per associazioni a uno, mentre <ClassName>RoleRefs viene generato per associazioni a molti.

input ClassNameRoleRef {
  set: ID
  remove: Boolean
}

input ClassNameRoleRefs {
  add: [ID]
  remove: [ID]
  removeAll: Boolean
}

Contestualmente alla modifica di un oggetto della classe Source, è possibile specificare, per campi relativi ai ruoli di associazione uscenti, quali oggetti delle corrispondenti classi Target si vogliono associare o rimuovere:

  • <ClassName>RoleRef consente di associarsi a un oggetto esistente (specificandone l’ID mediante set), oppure di rimuovere l’associazione pre-esistente (valorizzando remove: true);
  • <ClassName>RoleRefs consente di specificare contemporaneamente nuove associazioni (specificando una lista di ID mediante add) e la rimozione di associazioni pre-esistenti (specificando una lista di ID mediante remove), oppure consente di rimuovere tutte le associazioni pre-esistenti (valorizzando removeAll: true).

Come mostrato sopra, per le classi Team e Qualification vengono generati, rispettivamente, gli input TeamRoleRef e QualificationRoleRefs; questi compaiono nell’input EmployeeUpdate, rispettivamente nei campi dei ruoli team (a uno) e qualification_ (a molti), nell’ambito del servizio Mutation.Employee___update.

In modo analogo, osservando il modello di riferimento, possiamo dedurre che nello schema GraphQL generato compaiono gli input ProjectRoleRef ed EmployeeRoleRef (anche quest’ultimo usato da EmployeeUpdate).

input <ClassName>RoleObject(s) #

Gli input type <ClassName>RoleObject e <ClassName>RoleObjects hanno lo scopo di gestire la creazione / modifica degli oggetti Part della classe <ClassName>, nel contesto del servizio update. Vengono generati per tutte le classi che hanno almeno un ruolo Part entrante managed; come suggerisce il nome, <ClassName>RoleObject viene generato per composizioni a uno, mentre <ClassName>RoleRefs viene generato per composizioni a molti.

input ClassNameRoleObject {
  create: ClassNameCreate
  update: ClassNameUpdate
  delete: Boolean
}

input ClassNameRoleObjects {
  create: [ClassNameCreate]
  update: [ClassNameUpdate]
  delete: [ID]
  deleteAll: Boolean
}

Contestualmente alla modifica di un oggetto della classe Main, è possibile specificare, per campi relativi ai ruoli di composizione uscenti, quali oggetti delle corrispondenti classi Part si vogliono creare, modificare o distruggere:

  • <ClassName>RoleObject consente di creare un nuovo oggetto part (popolando il campo create, di tipo <ClassName>Create), aggiornare l’oggetto part pre-esistente (popolando il campo update, di tipo <ClassName>Update), oppure di eliminare l’oggetto part pre-esistente (valorizzando delete: true) ;
  • <ClassName>RoleObjects consente di specificare contemporaneamente la creazione contestuale di nuovi oggetti part (specificando una lista di <ClassName>Create mediante add), la modifica contestuale di oggetti part pre-esistenti (specificando una lista di <ClassName>Update mediante update), l’eliminazione contestuale di oggetti part pre-esistenti (specificando una lista di ID mediante delete), oppure consente di eliminare tutti gli oggetti part pre-esistenti (valorizzando deleteAll: true).

Come mostrato sopra, per le classi Address e Project_assignment vengono generati, rispettivamente, gli input AddressRoleObject e Project_assignmentRoleObjects; questi compaiono nell’input EmployeeUpdate, rispettivamente nei campi dei ruoli address (a uno) e assignments (a molti), nell’ambito del servizio Mutation.Employee___update.

input <ClassName>DraftUpdate #

L’input type <ClassName>DraftUpdate è la variante di <ClassName>Update usata dai servizi previewUpdate e validateUpdate. Non ha sostanziali differenze con la sua controparte, se non per le strutture usate come valore dei campi relativi a ruoli part uscenti.

Ad esempio, per la classe Employee, si ha la seguente struttura EmployeeDraftUpdate:

input EmployeeDraftUpdate {
  _id: ID! # come per EmployeeUpdate
  # attributi (stessi di EmployeeUpdate)
  first_name: String
  last_name: String
  date_of_birth: Date
  email_address: String
  date_joined: Date
  hourly_cost: Real
  username: String

  # ruoli uscenti
  #...
}

I ruoli uscenti vengono invece mappati come segue:

Per Employee, i ruoli uscenti vengono mappati come segue:

input EmployeeDraftUpdate {
  # attributi (stessi di EmployeeUpdate)
  # ...

  # ruoli uscenti
  team: TeamDraftUpdateRoleRef # associazione a 1
  supervisor: EmployeeDraftUpdateRoleRef # associazione riflessiva a 1
  address: AddressDraftUpdateRoleObject # composizione a 1
  qualification_: QualificationDraftUpdateRoleRefs # associazione a N
  assignments: Project_assignmentDraftUpdateRoleObjects # composizione a N
}

input <ClassName>DraftUpdateRoleRef(s) #

Gli input type <ClassName>DraftUpdateRoleRef e <ClassName>DraftUpdateRoleRefs sono rispettivamente le varianti di <ClassName>RoleRef e <ClassName>RoleRefs annidate all’input <ClassName>DraftUpdate usato dai servizi previewUpdate e validateUpdate. Al netto del nome diverso, queste strutture coincidono con le loro controparti, come possiamo vedere dal seguente confronto:

input ClassNameDraftUpdateRoleRef {
  set: ID
  remove: Boolean
}

input ClassNameDraftUpdateRoleRefs {
  add: [ID]
  remove: [ID]
  removeAll: Boolean
}
input ClassNameRoleRef {
  set: ID
  remove: Boolean
}

input ClassNameRoleRefs {
  add: [ID]
  remove: [ID]
  removeAll: Boolean
}

input <ClassName>DraftUpdateRoleObject(s) #

Gli input type <ClassName>DraftUpdateRoleObject e <ClassName>DraftUpdateRoleObjects sono rispettivamente le varianti di <ClassName>RoleObject e <ClassName>RoleObjects annidate all’input <ClassName>DraftUpdate usato dai servizi previewUpdate e validateUpdate.

Rispetto a quanto detto per le strutture dei ruoli di associazione, queste differiscono anche semanticamente dalle loro controparti. Dal momento che gli input per oggetti part consentono la creazione contestuale, gli input DraftUpdate e Update usano rispettivamente <ClassName>DraftCreate (che non ha campi required) <ClassName>Create (che può avere campi required).

Di seguito vediamo il confronto con <ClassName>RoleObject/<ClassName>RoleObjects:

input ClassNameDraftUpdateRoleObject {
create: ClassNameDraftCreate
update: ClassNameDraftUpdateUpdate
delete: Boolean
}

input ClassNameDraftUpdateRoleObjects {
create: [ClassNameDraftCreate]
update: [ClassNameDraftUpdateUpdate]
delete: [ID]
deleteAll: Boolean
}
input ClassNameRoleObject {
  create: ClassNameCreate
  update: ClassNameUpdate
  delete: Boolean
}

input ClassNameRoleObjects {
  create: [ClassNameCreate]
  update: [ClassNameUpdate]
  delete: [ID]
  deleteAll: Boolean
}

Questa differenza nelle strutture annidate per i part giustifica, di fatto, la necessitĂ  di avere una struttura DraftUpdate dedicata per i servizi previewUpdate e validateUpdate, per garantire la stessa libertĂ  di cui godono i servizi previewCreate e validateCreate.

Servizio updateBulk #

Il servizio Mutation.<ClassName>___updateBulk, controparte a molti del servizio update, consente di aggiornare uno o piĂš oggetti a partire da una certa classe, tramite un grafo di modifiche che viene applicato in modo bulk a ciascun oggetto selezionato.

type Mutation {
  ClassName___updateBulk(
    options: ClassNamePageOptions!
    data: ClassNameUpdateBulk!
    forceWarnings: ForceWarnings
  ): ClassNameBulkResult
}

Il servizio richiede in input la struttura <ClassName>PageOptions, opportunamente valorizzata per identificare l’insieme di oggetti che si vuole modificare. È richiesto inoltre un input <ClassName>UpdateBulk, che descrive le modifiche bulk che verranno applicate a ciascun oggetto selezionato.

È possibile aggirare eventuali warning restituiti dal server causati da Class Warning non bloccanti includendo l’argomento forceWarnings, di tipo ForceWarnings.

Il servizio restituisce in output un oggetto <ClassName>BulkResult, che contiene, nel campo items, la lista degli oggetti modificati dalla mutation.

type ClassNameBulkResult {
  items: [ClassName!]!
  totalCount: Int!
}

È possibile specificare quali campi si vogliono recuperare per ciascun record della lista, in modo analogo alla struttura <ClassName>Page. Il campo totalCount riporta il conteggio dei record

Ad esempio, il servizio per modificare diversi oggetti Employee è il seguente:

type Mutation {
  Employee___updateBulk(
    options: EmployeePageOptions!
    data: EmployeeUpdateBulk!
    forceWarnings: ForceWarnings
  ): EmployeeBulkResult
}

Servizio validateUpdateBulk #

Il servizio Query.<ClassName>___validateUpdateBulk riflette il servizio updateBulk, ma con lo scopo di verificare se la modifica bulk degli oggetti non violi vincoli a livello di Data Validation.

type Query {
  ClassName___validateUpdateBulk(options: ClassNamePageOptions!): ValidationResult
}

Il servizio richiede in input la struttura <ClassName>DraftUpdateBulk e restituisce in output la struttura ValidationResult, che indica se la modifica bulk è valida o meno e riporta eventuali Issue a livello di Data Validation.

Ad esempio, il servizio per validare la modifica bulk di diversi oggetti Employee è il seguente:

type Query {
  Employee___validateUpdateBulk(options: ClassNamePageOptions!): ValidationResult
}

input <ClassName>UpdateBulk #

L’input type <ClassName>UpdateBulk permette di specificare i dati necessari alla modifica bulk di oggetti pre-esistente della classe <ClassName>. Questo input ricalca l’input <ClassName>Update, con un’unica differenza: il campo _id è assente.

Il grafo di modifiche descritto da questa struttura non fa infatti riferimento a un oggetto pre-esistente della classe, bensĂŹ rappresenta le modifiche che verranno applicate in modo bulk a tutti gli oggetti selezionati dal servizio updateBulk.

Ad esempio, per la classe Employee, si ha la seguente struttura EmployeeUpdateBulk:

input EmployeeUpdateBulk {
  # _id è assente

  # attributi (stessi di EmployeeUpdate)
  first_name: String
  last_name: String
  date_of_birth: Date
  email_address: String
  date_joined: Date
  hourly_cost: Real
  username: String

  # ruoli uscenti (stessi di EmployeeUpdate)
  team: TeamRoleRef # associazione a 1
  supervisor: EmployeeRoleRef # associazione riflessiva a 1
  address: AddressRoleObject # composizione a 1
  qualification_: QualificationRoleRefs # associazione a N
  assignments: Project_assignmentRoleObjects # composizione a N
}

input <ClassName>DraftUpdateBulk #

L’input type <ClassName>DraftUpdateBulk è la variante di <ClassName>UpdateBulk usata dal servizio validateUpdateBulk. Non ha sostanziali differenze con la sua controparte, se non per le strutture usate come valore dei campi relativi a ruoli part uscenti. Valgono infatti le stesse considerazioni fatte per l’input <ClassName>DraftUpdate.

Ad esempio, per la classe Employee, si ha la seguente struttura EmployeeDraftUpdateBulk:

input EmployeeDraftUpdateBulk {
  # _id è assente (come per EmployeeUpdateBulk)

  # attributi (stessi di EmployeeDraftUpdate)
  first_name: String
  last_name: String
  date_of_birth: Date
  email_address: String
  date_joined: Date
  hourly_cost: Real
  username: String

  # ruoli uscenti (stessi di EmployeeDraftUpdate)
  team: TeamDraftUpdateRoleRef # associazione a 1
  supervisor: EmployeeDraftUpdateRoleRef # associazione riflessiva a 1
  address: AddressDraftUpdateRoleObject # composizione a 1
  qualification_: QualificationDraftUpdateRoleRefs # associazione a N
  assignments: Project_assignmentDraftUpdateRoleObjects # composizione a N
}

Effettuare una scrittura generica #

Come visto finora, i servizi e le strutture offerti dallo schema per coprire gli scenari di Create e Update sono sostanzialmente molto simili tra loro; questo approccio è in linea col principio di prevedibilità dello schema GraphQL.

Nel progettare applicazioni che utilizzano intensivamente le API GraphQL (come un client web) può tuttavia non risultare necessario dover distinguere tra una scrittura di tipo Create o una Update; in questi casi, la tipizzazione rigida delle strutture viste finora può rappresentare un limite, in quanto lo sviluppatore è costretto a gestire separatamente questi due scenari.

Per questo motivo, a partire dalla versione di Livebase 5.8, lo schema offre dei servizi di scrittura generici (e relativi servizi di supporto Preview e Validate) che mettono a fattor comune queste differenze, che usano input flessibili in grado di assumere il significato di una Create o Update a seconda dei campi valorizzati.

Servizio save #

Il servizio Mutation.<ClassName>___create consente di creare un grafo di oggetti di una certa classe, oppure di aggiornare uno o piĂš oggetti della stessa classe, tramite un grafo di modifiche. Raggruppa i servizi create e update.

type Mutation {
  ClassName___save(data: ClassNameDraft!, forceWarnings: ForceWarnings): ClassName
}

Il servizio richiede in input la struttura <ClassName>Draft, e restituisce in output la struttura <ClassName> contenente l’oggetto modificato. È possibile aggirare eventuali warning restituiti dal server causati da Class Warning non bloccanti includendo l’argomento forceWarnings, di tipo ForceWarnings.

Ad esempio, il servizio per aggiornare un oggetto Employee è il seguente:

type Mutation {
  Employee___save(data: EmployeeDraft!, forceWarnings: ForceWarnings): Employee
}

Servizio preview #

Il servizio Query.<ClassName>___previewCreate consente di ottenere un’anteprima del risultato della creazione o dell’aggiornamento di un grafo di oggetti, utile per ottenere informazioni calcolate dal server, come attributi derivati e oggetti associabili al grafo. Raggruppa i servizi previewCreate e previewUpdate.

type Query {
  ClassName___preview(data: ClassNameDraft!): ClassName
}

Il servizio richiede in input la struttura <ClassName>Draft, e restituisce in output la struttura <ClassName>, i cui campi calcolati sono pre-popolati, se possibile, a partire dalle informazioni inviate al server; gli associabili sono sempre disponibili per tutti i ruoli.

Ad esempio, il servizio per ottenere una preview di un oggetto Employee creato o aggiornato è il seguente:

type Query {
  Employee___preview(data: EmployeeDraft!): Employee
}

Servizio validate #

Il servizio Query.<ClassName>___validate riflette il servizio save, ma con lo scopo di verificare se il grafo di oggetti creato o modificato sia corretto a livello di Data Validation. Raggruppa i servizi validateCreate e validateUpdate.

type Query {
  ClassName___validate(data: ClassNameDraft!): ValidationResult
}

Il servizio richiede in input la struttura <ClassName>Draft e restituisce in output la struttura ValidationResult, che indica se il grafo è valido o meno e riporta eventuali Issue a livello di Data Validation.

Ad esempio, il servizio per validare la creazione o l’aggiornamento di un oggetto Employee pre-esistente è il seguente:

type Query {
  Employee___validate(data: EmployeeDraft!): ValidationResult
}

input <ClassName>Draft #

L’input type <ClassName>Draft è la struttura di input più flessibile tra quelle disponibili, ed è usata indistintamente dai servizi save, preview e validate.

Come affermato qui, questo input flessibile è in grado di assumere il significato di una Create o Update a seconda dei campi valorizzati.

Gli attributi vengono mappati come segue:

  • sono presenti solamente campi relativi ad attributi nativi (editabili), mentre sono esclusi attributi derivati e platform, a eccezione del campo _id: ID!;
  • a differenza di <ClassName>Create, i campi relativi ad attributi required non sono obbligatori;
  • a differenza di <ClassName>Update, anche il campo _id non è obbligatorio. La presenza o meno di un valore per _id distingue se l’operazione è logicamente una Create o un Update;

Ad esempio, per la classe Employee, si ha la seguente struttura EmployeeDraft:

input EmployeeUpdate {
_id: ID # non è obbligatorio
# attributi
first_name: String
last_name: String
date_of_birth: Date
email_address: String
date_joined: Date
hourly_cost: Real
username: String

# ruoli uscenti
# ...
}

I ruoli uscenti vengono mappati come segue:

  • associazioni a uno: ID. Nel contesto di una Update, è possibile rimuovere l’associazione esistente valorizzando questo campo a null;
  • associazioni a molti: <TargetClassName>DraftRoleRefs (analogo a <ClassName>DraftRoleRefs;
  • part a uno: <PartClassName>Draft (analogo a <ClassName>Create). Nel contesto di una Update, è possibile aggiornare un oggetto part esistente includendo l’_id nella struttura annidata; è possibile inoltre eliminare il part esistente valorizzando questo campo a null;
  • part a molti: <PartClassName>DraftRoleObjects (analogo a <ClassName>DraftRoleObjects).

Per Employee, i ruoli uscenti vengono mappati come segue:

input EmployeeCreate {
  # attributi
  # ...

  # ruoli uscenti
  team: ID # associazione a 1
  supervisor: ID # associazione riflessiva a 1
  address: AddressDraft # composizione a 1
  qualification_: QualificationDraftRoleRefs # associazione a N
  assignments: Project_assignmentDraftRoleObjects # composizione a N
}

input <ClassName>DraftRoleRefs #

L’input type <ClassName>DraftRoleRefs ha lo scopo di gestire la modifica dell’associazione verso la classe <ClassName> nel contesto del servizio save. Come la controparte <ClassName>RoleRefs, viene generato per tutte le classi che hanno almeno un’associazione a molti entrante managed. Al netto del nome diverso, questa struttura coincide con la sua controparte come possiamo vedere dal seguente confronto:

input ClassNameDraftRoleRefs {
  add: [ID]
  remove: [ID]
  removeAll: Boolean
}
input ClassNameRoleRefs {
  add: [ID]
  remove: [ID]
  removeAll: Boolean
}

input <ClassName>DraftRoleObjects #

L’input type <ClassName>RoleObjects ha lo scopo di gestire la creazione / modifica degli oggetti Part della classe <ClassName>, nel contesto del servizio save. Come la controparte <ClassName>RoleObjects, viene generato per tutte le classi che hanno almeno un ruolo Part a molti entrante managed.

A differenza della sua controparte, questa struttura offre un unico campo save (contenente una lista di <ClassName>Draft) che accorpa i campi create e update e permette di specificare sia la creazione di nuovi oggetti part che la modifica di oggetti part esistenti. La semantica di ciascuna operazione è determinata dalla presenza di un valore nel campo _id annidato di ciascun oggetto part.

Di seguito vediamo il confronto con <ClassName>RoleObjects:

input ClassNameDraftRoleObjects {
save: [ClassNameDraft]
delete: [ID]
deleteAll: Boolean
}
input ClassNameRoleObjects {
  create: [ClassNameCreate]
  update: [ClassNameUpdate]
  delete: [ID]
  deleteAll: Boolean
}

Eliminare oggetti #

I seguenti servizi e strutture consentono di eliminare oggetti o testarne l’eliminazione.

Servizio delete #

Il servizio Mutation.<ClassName>___delete consente di eliminare un oggetto di una certa classe.

type Mutation {
  ClassName___delete(_id: ID!, forceWarnings: ForceWarnings): DeleteResult
}

Il servizio richiede in input l’ID dell’oggetto di <ClassName> che si vuole eliminare, e restituisce in output un oggetto <DeleteResult>; quest’ultimo contiene semplicemente il campo deleted: Boolean, che vale true se l’oggetto stato cancellato o false altrimenti (a esempio, se non è stato trovato).

type DeleteResult {
  deleted: Boolean
}

È possibile aggirare eventuali warning restituiti dal server causati da Class Warning non bloccanti includendo l’argomento forceWarnings, di tipo ForceWarnings.

Ad esempio, il servizio per eliminare un oggetto Employee è il seguente:

type Mutation {
  Employee___delete(_id: ID!, forceWarnings: ForceWarnings): DeleteResult
}

che può essere utilizzato come segue:

mutation {
  Employee___delete(_id: "10101") {
    deleted
  }
}
{
  "deleted": true
}

Servizio validateDelete #

Il servizio Query.<ClassName>___validateDelete riflette il servizio delete, ma con lo scopo di verificare se l’eliminazione dell’oggetto non violi vincoli a livello di Data Validation.

type Query {
  ClassName___validateDelete(_id: ID!): ValidationResult
}

Il servizio richiede in input l’ID dell’oggetto di <ClassName> che si vuole eliminare, e restituisce in output la struttura ValidationResult, che indica se l’eliminazione è valida o meno e riporta eventuali Issue a livello di Data Validation.

Ad esempio, il servizio per validare l’eliminazione di un oggetto Employee è il seguente:

type Query {
  Employee___validateDelete(_id: ID!): ValidationResult
}

Servizio deleteBulk #

Il servizio Mutation.<ClassName>___deleteBulk, controparte a molti del servizio delete, consente di eliminare uno o piĂš oggetti di una certa classe.

type Mutation {
  ClassName___deleteBulk(
    options: ClassNamePageOptions!
    forceWarnings: ForceWarnings
  ): DeleteBulkResult
}

Il servizio richiede in input la struttura <ClassName>PageOptions, opportunamente valorizzata per identificare l’insieme di oggetti che si vogliono eliminare, e restituisce in output un oggetto <DeleteBulkResult>; quest’ultimo contiene semplicemente il campo deleted: Int, a indicare il numero di oggetti eliminati.

type DeleteBulkResult {
  deleted: Boolean
}

È possibile aggirare eventuali warning restituiti dal server causati da Class Warning non bloccanti includendo l’argomento forceWarnings, di tipo ForceWarnings.

Ad esempio, il servizio per eliminare diversi oggetti Employee è il seguente:

type Mutation {
  Employee___deleteBulk(
    options: EmployeePageOptions!
    forceWarnings: ForceWarnings
  ): DeleteBulkResult
}

Servizio validateDeleteBulk #

Il servizio Query.<ClassName>___validateDeleteBulk riflette il servizio deleteBulk, ma con lo scopo di verificare se l’eliminazione degli oggetti non violi vincoli a livello di Data Validation.

type Query {
  ClassName___validateDeleteBulk(options: ClassNamePageOptions!): ValidationResult
}

Il servizio richiede in input l’ID dell’oggetto di <ClassName> che si vuole eliminare, e restituisce in output la struttura ValidationResult, che indica se l’eliminazione è valida o meno e riporta eventuali Issue a livello di Data Validation.

Ad esempio, il servizio per validare l’eliminazione di diversi oggetti Employee è il seguente:

type Query {
  Employee___validateDeleteBulk(options: ClassNamePageOptions!): ValidationResult
}

type ValidationResult #

Tutti i servizi di tipo Validate menzionati finora (come Query.<ClassName>___validate) ritornano la seguente struttura comune ValidationResult:

type ValidationResult {
  isValid: Boolean
  issues: [Issue]
}

Il campo isValid vale true se, nel verificare la correttezza della chiamata a livello di Data Validation, il server ha sollevato Issue di questo tipo. Le stesse Issue sono riportate nel campo issues.

È importante notare che i servizi di tipo Validate sono gli unici in grado di ottenere Issue dal server GraphQL nel campo data, e non annidate nel campo errors; questo perché, nel comunicare le Issue, il server non va in eccezione. Tuttavia, questi servizi sono in grado di “rilevare” solamente Issue a livello di Data Validation. Maggiori informazioni sono disponibili qui: Reference: tipi di Issue.

input ForceWarnings #

Come affermato in Lo schema GraphQL: Errori lato server, di default il server ritorna un errore GraphQL sia che nell’applicazione si verifichino errori (bloccanti) che warning (non bloccanti), come accade quando un Class warning è impostato per non impedire all’utente di eseguire un’azione.

Nel client web, l’utente può accettare il warning e procedere comunque; in GraphQL, alcuni servizi consentono di emulare questo comportamento “forzando” i warning a non bloccare l’esecuzione.

I servizi di scrittura di tipo mutation elencati sono forzabili mediante l’argomento forceWarnings:

L’input type ForceWarnings consiste di due opzioni:

input ForceWarnings {
  actionVeto: Boolean = false
  dataValidation: Boolean = false
}

Queste consentono di forzare i warning sollevati dal server in risposta alle due categorie di eventi (GUI events e Database events) per cui è possibile impostare un Class Warning.

Class warning editor

Sostanzialmente, actionVeto si riferisce ai seguenti eventi:

  • Edit: avviene quando un utente del Client web clicca sul bottone “edit” o “save”; GraphQL “simula” questa azione quando viene invocato un servizio che modifica dati esistenti (update, save, delete, …);
  • Create: avviene quando un utente del Client clicca sul bottone “create”; GraphQL “simula” questa azione quando viene invocato un servizio che crea nuovi dati (create, save);
  • Open: avviene quando un utente del Client web apre un oggetto da una Livetable; in GraphQL non viene mai simulato.

D’altra parte, dataValidation si riferisce ai Database events in figura, ovvero eventi relativi al persist dei dati nell’applicazione generata.

Valorizzando uno di questi due campi a true, è possibile aggirare eventuali Class Warning non bloccanti definiti definiti sulla nostra classe di interesse.

Vediamo un esempio. Supponiamo di avere definito il seguente Class Warning non bloccante su Employee:

Example class warning

La seguente chiamata fallisce:

mutation {
  Employee___save(
    data: {
      first_name: "Reiner"
      last_name: "Galliard"
      date_of_birth: "15/08/1990"
      date_joined: "10/10/2018"
      hourly_cost: "12.0"
    }
  ) {
    full_name
    age
  }
}
{
  "errors": [
    {
      "message": "Hourly cost is too low with respect to the employee's experience.",
      "extensions": {
        "sourceRequestReference": "Employee___save",
        "userMessage": "Hourly cost is too low with respect to the employee's experience.",
        "issueLevel": "WARNING",
        "issueReferenceType": "ENTITY",
        "issueType": "ENTITY_DOMAIN",
        "entityName": "Employee",
        "entityID": "117010",
        "_clientId": "",
        "attributeNames": [],
        "roleNames": [],
        "applicationName": "Administration",
        "profileName": "Administrator",
        "traceId": "fd2eab81a5ea1ec2c6c7687ad5d3bf080e14c130",
        "classification": "DataFetchingException"
      }
    }
  ],
  "data": {
    "Employee___save": null
  }
}

Il server ha restituito una sola Issue di livello WARNING. Invece di cambiare il valore del campo hourly_cost, modifichiamo la chiamata affinchĂŠ forzi il Class warning e verifichiamo che la successiva chiamata vada a buon fine:

mutation {
Employee___save(
data: {
first_name: "Reiner"
last_name: "Galliard"
date_of_birth: "15/08/1990"
date_joined: "10/10/2018"
hourly_cost: "12"
}
forceWarnings: {dataValidation: true}
) {
full_name
age
}
}
{
  "data": {
    "Employee___save": {
      "full_name": "Reiner Leonhart",
      "age": 30
    }
  }
}