Generatore di ID sequenziali

Prev Next

To download the Sequential ID Generator, visit: [tulip](https://<span translate=).co/library/apps/sequential-id-generator---multiple-prefix-support">Library

In questo articolo, Tamás, uno dei nostri sviluppatori della libreria, illustra le scelte di sviluppo alla base dell’app “Sequential ID Generator”, disponibile nella libreria.

Perché l’ho realizzata in questo modo

Volevo che questo blocco logico risolvesse un problema molto pratico: generare ID in modo affidabile, facile da riutilizzare e veloce da implementare in molte app.

L’obiettivo di progettazione più importante non era semplicemente «generare il numero successivo». Il vero obiettivo era creare qualcosa che, una volta definito, potesse essere copiato e riutilizzato con il minimo sforzo. Se un pulsante viene creato una volta in modo chiaro, dovrebbe essere possibile inserirlo in un’altra app o in un altro passaggio, limitandosi a modificare la configurazione. Non volevo che ogni sviluppatore di app dovesse riaprire la logica e riscrivere i trigger ogni volta.

Ecco perché il blocco logico è progettato per dare priorità alla configurazione rispetto alla logica. Quando si utilizza questo blocco logico, occorre principalmente pensare a:* quale prefisso è necessario* quanto deve essere lunga la parte numerica* da quale tabella deve leggere il pulsante

Una volta definiti il prefisso, il formato del numero e la tabella, i blocchi logici dovrebbero già occuparsi del resto: leggere l’ID più recente, convalidarlo, generare il valore successivo e verificare se il numero rientra ancora nella lunghezza configurata.

La tabella stessa dovrebbe essere la fonte di verità. Un contatore separato (che può essere, ad esempio, una variabile) può discostarsi dai dati reali se i record vengono inseriti, eliminati o modificati in modi imprevisti. La lettura dalla tabella mantiene la numerazione legata a ciò che esiste effettivamente.

Un’altra decisione importante è stata quella di mantenere la logica di calcolo principale in un unico posto. Non volevo un ramo del trigger per il primo ID, un altro ramo per l’ID successivo e poi piccoli frammenti di trigger aggiuntivi che in seguito sarebbero diventati difficili da seguire. Volevo che la logica principale risiedesse in un unico expression in modo che il comportamento rimanesse coerente e chiunque lo modificasse in seguito sapesse esattamente dove cercare.

Quindi il blocco logico è in realtà costruito attorno ad alcune decisioni pratiche:* facile da copiare e riutilizzare* sufficientemente sicuro da proteggere la tabella da schemi di ID errati* semplice da configurare* logica centralizzata in modo che la manutenzione rimanga gestibile

Come si presenta l’ID

Ogni ID generato è composto da due parti:prefisso + sequenza numerica

Esempi:* ORD00000125* MAT00000003* TEST00004567

Il prefisso indica di che tipo di record si tratta. La parte numerica è la sequenza progressiva. Ho scelto di mantenere la configurazione del formato numerico molto semplice. Invece di richiedere all’utente un numero specifico come “8 cifre”, il blocco logico utilizza uno schema con zeri, ad esempio:* 00000000

In questo modo è più facile da leggere a colpo d’occhio. Se qualcuno vede otto zeri, capisce immediatamente che la sezione numerica deve essere sempre lunga otto caratteri. Ecco perché ho mantenuto idNumberFormat in questa forma invece di sostituirlo con un campo di lunghezza pura.

Esempi:* 00000000 → numero a 8 cifre* 0000 → numero a 4 cifre

Si tratta di una decisione di poco conto, ma importante. Rende il blocco logico più accessibile a chi non vuole ragionare in termini di parametri tecnici.

Come è strutturata la build

L’implementazione effettiva utilizza tre trigger. Questa struttura è intenzionale. Volevo che il blocco logico fosse facile da seguire dall’inizio alla fine, mantenendo al contempo la logica principale separata e riutilizzabile. Quindi, invece di distribuire la logica su molti trigger modificabili, l’ho organizzata in tre fasi:1. CONFIGURAZIONE UTENTE: FASE DI INPUT 2. MAGIC 3. CONFIGURAZIONE UTENTE: FASE DI OUTPUT

Questo garantisce un buon equilibrio tra usabilità e controllo.* Il primo trigger è dove si definiscono gli input.* Il secondo trigger contiene la logica interna che normalmente non deve essere modificata affinché il blocco logico funzioni.* Il terzo trigger è dove si decide cosa fare con il risultato generato.

Ciò significa che normalmente non è necessario intervenire all’interno del trigger della logica principale. È sufficiente configurare il lato di input e quello di output. Questa è stata una delle decisioni chiave in fase di progettazione: rendere il blocco logico riutilizzabile esponendo solo le parti che solitamente devono essere modificate.

Trigger 1 – CONFIGURAZIONE UTENTE: FASE DI INGRESSO

Questo è il primo trigger con cui si interagisce. Il suo scopo è definire i valori di input necessari al blocco logico prima che possa essere generato qualsiasi ID.In pratica, questo trigger viene utilizzato per definire:* il prefisso* il formato dell’ID (idNumberFormat)* l’ID della tabella passato al connettoreEsempio:prefisso = ORDidNumberFormat = 00000000ID tabella = tabella di destinazione

Questa fase è volutamente modificabile poiché si tratta di valori che normalmente variano a seconda del caso d’uso.

È possibile prendere lo stesso pulsante, copiarlo in un’altra app o fase, aggiornare questi valori di configurazione e riutilizzare il blocco logico senza modificare la logica interna.

Ciò consente un’implementazione più rapida poiché, una volta definiti i valori di input, il resto del flusso funziona automaticamente. Questo trigger esegue anche la chiamata al connettore che legge l’ultimo record corrispondente dalla tabella selezionata.

Perché il connettore fa parte della progettazione

Il connettore non è solo un dettaglio tecnico. Fa parte della strategia di riutilizzo. Utilizzo il connettore per leggere l’ID più recente presente nella tabella di destinazione. Il connettore riceve l’ID della tabella come input, applica il filtro del prefisso, ordina i record in base a _sequenceNumber in ordine decrescente e restituisce l’ultimo record corrispondente. Ciò è importante per due motivi. Innanzitutto, consente al blocco logico di continuare la numerazione partendo dallo stato effettivo della tabella. In secondo luogo, rende il blocco logico molto più facile da riutilizzare. Se si copia il pulsante in un’altra app, non è necessario riscrivere la logica. È sufficiente indirizzare il connettore alla tabella corretta e fornire la configurazione necessaria. Questo è il modello pratico alla base della realizzazione: 1. creare il pulsante una sola volta; 2. impostare l’input della tabella nel connettore; 3. definire il prefisso e il formato del numero; 4. riutilizzarlo ovunque sia necessario

Si è trattato di una scelta deliberata per supportare una creazione più rapida delle app.

Cosa fa il connettore nel blocco logico?

Ecco cosa succede:* il connettore riceve l’ID della tabella* il connettore riceve il prefisso* il connettore filtra i record in base al prefisso* il connettore ordina i record in ordine decrescente in base a _sequenceNumber* il connettore restituisce l’ultimo ID corrispondente

Il connettore supporta anche un caso d’uso molto importante: entità multiple nella stessa tabella.

Se la stessa tabella contiene record come: ORD00000011 MAT00000003, il blocco logico non dovrebbe trattarli come un’unica sequenza condivisa. Ogni prefisso dovrebbe ripartire solo dal proprio valore più recente.

Pertanto, il connettore filtra prima in base al prefisso. Ciò significa che i valori successivi diventano: ORD → ORD00000012, MAT → MAT00000004

Ho scelto questo modello perché nella stessa tabella possono coesistere entità diverse, ma ciò non significa che debbano condividere lo stesso numero progressivo.

Vale anche la pena specificare chiaramente un requisito di configurazione: il connettore richiede l’autenticazione, quindi è necessario generare un token e aggiungerlo alle impostazioni di autenticazione del connettore. Considero questa operazione come parte della configurazione, non come parte della logica del blocco logico stesso, ma è comunque un aspetto importante per rendere il blocco logico utilizzabile.

L’ho mantenuto nella fase di input perché appartiene alla configurazione e alla raccolta del contesto. Prima che la logica interna possa funzionare, il blocco logico deve innanzitutto sapere con cosa sta lavorando.

Trigger 2 – MAGIC

Questo è il trigger della logica principale. L’ho chiamato così di proposito. L’idea è semplice: questa è la parte che normalmente non dovreste dover modificare. Il blocco logico dovrebbe già includere questa logica al suo interno. Questo trigger prende i valori di input configurati e l’ultimo ID restituito dal trigger precedente, quindi esegue il processo decisionale interno. Anche se si tratta di un unico trigger, al suo interno avvengono diverse operazioni.

1. Convalida del formato – la parte “if” del trigger

Il primo controllo verifica che l’ultimo ID restituito dalla tabella corrisponda ancora alla struttura prevista. Struttura prevista: **prefisso + numero a lunghezza fissa

Esempio di valore valido: ORD00000025 Esempi di valori non validi: ORD25 ORD-00025 ABC00000025 ORD00025A

Expression

REGEX_REPLACE( LINK(Variable._stateLastId.id, Variable._configPrefix + Variable._configIdNumberFormat), '^' + Variable._configPrefix + '[0-9]{' + LEN(Variable._configIdNumberFormat) + '}$', 'ok')

Cosa fa questa expression

Questa espressione " expression " verifica se l'ID più recente segue esattamente la struttura prevista dal blocco logico. Lo fa in tre parti:* LINK(...) determina quale valore debba essere verificato. Se il connettore non restituisce un ID, viene utilizzata invece la seconda parte. Tale seconda parte è la struttura iniziale configurata e consente al blocco logico di generare il primo ID per una nuova sequenza di prefissi.* '^' + Variable._configPrefix significa che il valore deve iniziare con il prefisso configurato.* '[0-9]{' + LEN(Variable._configIdNumberFormat) + '}$' significa che il valore deve terminare esattamente con il numero di cifre previsto e che non è consentito nulla in più dopo di esso.

Se l’intero valore corrisponde alla struttura prevista, il risultato diventa "ok". In caso contrario, il risultato " expression " rimane diverso da "ok" e il trigger interrompe il flusso di generazione e mostra un messaggio di errore

Perché ho scelto questa soluzione

Ho aggiunto questo controllo perché non volevo che il blocco logico proseguisse a partire da un ID non valido. Se l’ultimo valore memorizzato è errato e la logica lo accetta, il modello errato potrebbe iniziare a propagarsi nei record futuri. Pertanto, ho voluto che la prima responsabilità del trigger MAGIC fosse quella di interrompere il processo in quella fase iniziale. Se il formato è errato: * _stateOutput viene azzerato * viene visualizzato un messaggio che spiega perché la generazione si è interrotta

2. Convalida dell’overflow

Il secondo controllo esamina il valore numerico successivo e verifica che rientri ancora nella lunghezza numerica configurata.

Esempio: idNumberFormat = 0000

Intervallo consentito: da 0000 a 9999

Se il valore successivo fosse: 10000, allora non rientrerebbe più nella struttura configurata.

Expression

LEN(Variable._configIdNumberFormat) < LEN( TOTEXT( TEXTTOINTEGER( REGEX_REPLACE( LINK(Variable._stateLastId.id, Variable._configPrefix + Variable._configIdNumberFormat), '^' + Variable._configPrefix, '' ) ) + 1 ))

Cosa fa questa espressione « expression »

Questa espressione « expression » confronta due lunghezze:* la lunghezza numerica consentita, ricavata da idNumberFormat* la lunghezza del numero successivo dopo l’incremento del valore corrente

Il lato destro funziona così:* LINK(...) utilizza l’ID restituito dal connettore, se presente. Se il connettore non restituisce un ID, utilizza invece la struttura iniziale configurata. È questo che rende possibile la generazione del primo ID.* REGEX_REPLACE(...) rimuove il prefisso in modo che rimanga solo la parte numerica* TEXTTOINTEGER(...) + 1 calcola il valore numerico successivo* TOTEXT(...) riconverte quel valore successivo in testo* LEN(...) misura quante cifre sono necessarie per quel valore successivo

Se il numero successivo richiede più cifre di quelle consentite dal formato, la condizione diventa vera e il trigger interrompe il processo.

Perché ho scelto questa soluzione

Ho scelto di interrompere il processo a questo punto invece di modificare automaticamente la struttura. Questa decisione è stata importante perché volevo che la lunghezza dell’ID rimanesse stabile a meno che qualcuno non modificasse esplicitamente la configurazione. Se viene rilevato un overflow:* _stateOutput viene azzerato* viene visualizzato un messaggio che spiega che è stata raggiunta la lunghezza massima

3. Generazione dell’ID

Se entrambi i controlli hanno esito positivo, il trigger genera l’ID successivo. Esempio: ORD00000015 → ORD00000016

Questo stesso expression gestisce anche il caso in cui non esista ancora alcun record per quel prefisso. In tal caso, la logica ricorre alla struttura iniziale configurata e produce il primo valore della sequenza. Esempio: ORD00000001

Expression

Variable._configPrefix + RIGHT( Variable._configIdNumberFormat + TOTEXT( TEXTTOINTEGER( REGEX_REPLACE( LINK(Variable._stateLastId.id, Variable._configPrefix + Variable._configIdNumberFormat), '^' + Variable._configPrefix, '' ) ) + 1 ), LEN(Variable._configIdNumberFormat))

Cosa fa questa expression

Questa è l’ expression e che costruisce l’ID finale. Funziona in questo ordine:* LINK(...) utilizza l’ultimo ID restituito dal connettore, se presente. Se il connettore non restituisce un ID, utilizza invece la struttura iniziale configurata. Questo permette allo stesso expression e di gestire anche il primo ID generato.* REGEX_REPLACE(...) rimuove il prefisso in modo che la parte numerica possa essere elaborata* TEXTTOINTEGER(...)+ 1 incrementa il numero* TOTEXT(...) riconverte il nuovo numero in testo* Variable._configIdNumberFormat + ... aggiunge il pattern di zeri all’inizio del nuovo valore* RIGHT(..., LEN(Variable._configIdNumberFormat)) mantiene solo il numero di cifre richiesto* Variable._configPrefix + ... aggiunge nuovamente il prefisso per creare l’ID finale. Il risultato viene memorizzato in _stateOutput.

Perché ho scelto questa soluzione

Ho volutamente mantenuto tutto in un unico « expression » centrale. Non volevo rami interni separati per il primo ID e l’ID successivo, se possibile. Mantenere il calcolo in un unico posto facilita il riutilizzo e rende più sicure le modifiche future. Al termine di questo trigger, il valore generato viene memorizzato in _stateOutput.

Trigger 3 – CONFIGURAZIONE UTENTE: FASE DI OUTPUT

Questo è l’ultimo trigger nel blocco logico. Il suo scopo è definire cosa deve accadere con l’ID generato una volta che _stateOutput contiene un valore valido. Il punto importante qui è che questa fase è stata volutamente lasciata aperta per l’utente. Se _stateOutput non è vuoto, allora è possibile decidere quale azione il blocco logico debba compiere successivamente.Nella configurazione attuale, il trigger mostra un messaggio guida che spiega cosa fare in seguito:* aggiungere la tabella configurata all’app* sostituire questo passaggio del messaggio con un trigger “Crea record ”* utilizzare _stateOutput come ID del nuovo record

L’ho strutturato in questo modo perché il comportamento dell’output può variare a seconda dei casi d’uso. Potresti voler creare un record immediatamente. Oppure potresti voler prima memorizzare il valore in un’altra variabile. Oppure potresti volerlo utilizzare in un flusso di lavoro più ampio prima di scrivere qualsiasi cosa nella tabella. Quindi, invece di impostare in modo rigido un unico comportamento di output, ho mantenuto configurabile la fase di output. Ecco perché la parte visibile all’utente è presente su entrambe le estremità:* l’input può essere configurato* l’output può essere configurato* la logica di base rimane al centro

Perché i trigger sono separati in questo modo

Questa struttura riflette una delle principali scelte progettuali dell’intero blocco logico: mantenere separate le responsabilità, ma centralizzare la logica di base.Ecco perché non ho inserito tutto in un unico trigger enorme. Un singolo trigger gigante potrebbe funzionare, ma diventa più difficile da leggere, più difficile da spiegare e più difficile da debuggare. D’altra parte, suddividere i calcoli matematici effettivi in tanti piccoli pezzi crea anch’esso confusione. Ho quindi scelto una via di mezzo:* trigger separati per passaggi leggibili* un’ expression e centralizzata per il calcolo effettivo dell’ID

Questo mi offre i vantaggi di entrambe le soluzioni:* la struttura è più facile da seguire* la logica è più facile da mantenere* il riutilizzo è più semplice* le modifiche future sono meno rischiose

Perché la convalida e la protezione sono importanti in questo blocco logico

Questo blocco logico non serve solo a generare il numero successivo, ma anche a proteggere la tabella da dati errati. Ci sono due punti di protezione principali:

Protezione del formato

Il blocco logico verifica se l’ultimo ID esistente corrisponda alla struttura prevista prima di proseguire. Ciò impedisce che la sequenza continui sulla base di ID che non corrispondono alla struttura prevista.

Protezione da overflow

Il blocco logico verifica se il numero successivo rientri ancora nella lunghezza numerica consentita. Ciò impedisce che la struttura cambi in modo imprevisto man mano che la sequenza cresce. Ho ritenuto entrambi questi aspetti importanti perché i dati ID errati possono diffondersi rapidamente. Una volta che un modello errato entra in una tabella e il generatore inizia a utilizzarlo, tale problema può influenzare ogni record futuro. Pertanto, il blocco logico è progettato per interrompersi tempestivamente piuttosto che proseguire silenziosamente nella direzione sbagliata.

Approccio alla denominazione delle variabili

Si può notare che i nomi delle variabili utilizzati all’interno del blocco logico seguono uno schema specifico. Esempi:_configPrefix_configIdNumberFormat_stateLastId_sequenceNumber

Ho utilizzato intenzionalmente uno stile di denominazione che sia facile da distinguere dalle variabili tipicamente create all’interno delle app. Lo scopo non è tecnico, ma pratico. Quando si importa o si copia il blocco logico in un’app, dovrebbe essere immediatamente chiaro quali variabili appartengono al blocco logico e quali alla logica dell’app.Le variabili del blocco logico utilizzano una struttura coerente, in modo da essere raggruppate visivamente e ridurre il rischio di conflitti con le variabili definite dall’utente. Ciò rende il blocco logico più facile da riutilizzare e più sicuro da integrare nelle app esistenti, senza conflitti di rinominazione o confusione.

Sintesi delle decisioni di progettazione

Se riduco l’intero blocco logico alle decisioni fondamentali che ne stanno alla base, queste sono:* Volevo un riutilizzo rapido, quindi il blocco logico è guidato dalla configurazione.* Volevo che la tabella fosse la fonte di verità, quindi l’ID successivo si basa sull’ultimo record effettivo.* Volevo che la logica rimanesse gestibile, quindi il calcolo è centralizzato.* Volevo che più entità in un’unica tabella funzionassero in modo pulito, quindi ogni prefisso ha la propria sequenza.* Volevo proteggere la tabella da schemi errati, quindi i controlli di formato e di overflow bloccano i dati errati prima della creazione del record.* Volevo che il flusso del trigger fosse leggibile, quindi le responsabilità sono separate in passaggi chiari.

Ecco perché il blocco logico ha questa struttura. Non è solo un generatore di ID. È un modello riutilizzabile per la creazione controllata di ID che cerca di bilanciare semplicità, riutilizzabilità e protezione dei dati.

Partecipa

Entra a far parte della comunità per condividere miglioramenti, proporre unità aggiuntive, segnalare problemi o discutere le decisioni architetturali.