Gerador de IDs sequenciais

Prev Next

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

Neste artigo, um de nossos desenvolvedores da biblioteca, Tamás, explica as decisões de desenvolvimento por trás do aplicativo de bloco de construção “Gerador Sequencial de IDs”, disponível na biblioteca.

Por que eu o criei dessa forma

Eu queria que esse bloco lógico resolvesse um problema muito prático: gerar IDs de maneira confiável, fácil de reutilizar e rápida de implementar em muitos aplicativos.

O objetivo de design mais importante não era apenas “gerar o próximo número”. O verdadeiro objetivo era criar algo que, uma vez definido, pudesse ser copiado e reutilizado com muito pouco esforço. Se um botão for criado uma vez de maneira organizada, deve ser possível colocá-lo em outro aplicativo ou em outra etapa e apenas ajustar a configuração. Eu não queria que cada criador de aplicativos tivesse que reabrir a lógica e reescrever os gatilhos todas as vezes.

É por isso que o bloco de lógica foi projetado para priorizar a configuração em detrimento da lógica. Ao usar esse bloco de lógica, você precisa pensar principalmente no seguinte:* qual prefixo é necessário* qual deve ser o comprimento da parte numérica* de qual tabela o botão deve ler

Depois de definir o prefixo, o formato numérico e a tabela, os blocos de lógica já devem cuidar do resto: ler o ID mais recente, validá-lo, gerar o próximo valor e verificar se o número ainda se encaixa no comprimento configurado.

A própria tabela deve ser a fonte de verdade. Um contador separado (que pode ser uma variável, por exemplo) pode se desviar dos dados reais se registros forem inseridos, excluídos ou editados de maneiras inesperadas. A leitura da tabela mantém a numeração vinculada ao que realmente existe.

Outra decisão importante foi manter a lógica central de cálculo em um único lugar. Eu não queria um ramo de trigger para o primeiro ID, outro ramo para o próximo ID e, em seguida, pequenos fragmentos extras de trigger que mais tarde se tornariam difíceis de acompanhar. Eu queria que a lógica principal ficasse em um único expression, para que o comportamento permanecesse consistente e qualquer pessoa que a modificasse posteriormente soubesse exatamente onde procurar.

Portanto, o bloco de lógica foi realmente construído com base em algumas decisões práticas:* fácil de copiar e reutilizar* seguro o suficiente para proteger a tabela contra padrões inadequados de ID* simples de configurar* lógica centralizada para que a manutenção continue gerenciável

Como é a estrutura do ID

Cada ID gerado tem duas partes:prefixo + sequência numérica

Exemplos:* ORD00000125* MAT00000003* TEST00004567

O prefixo indica de que tipo de registro se trata. A parte numérica é a sequência contínua. Optei por manter a configuração do formato numérico bem simples. Em vez de solicitar ao usuário um número específico, como “8 dígitos”, o bloco lógico usa um padrão de zeros, como:* 00000000

Isso é mais fácil de ler à primeira vista. Se alguém vir oito zeros, compreenderá imediatamente que a seção numérica deve ter sempre oito caracteres. É por isso que mantive o idNumberFormat nesse formato, em vez de substituí-lo por um campo de comprimento puro.

Exemplos:* 00000000 → número de 8 dígitos* 0000 → número de 4 dígitos

Essa decisão é pequena, mas faz diferença. Ela torna o bloco lógico mais acessível para pessoas que não querem pensar em termos de parâmetros técnicos.

Como a construção está estruturada

A implementação propriamente dita utiliza três gatilhos. Essa estrutura é intencional. Eu queria que o bloco lógico fosse fácil de acompanhar de cima para baixo, mantendo ao mesmo tempo a lógica principal separada e reutilizável. Portanto, em vez de distribuir a lógica por vários gatilhos editáveis, organizei-a em três etapas:1. CONFIGURAÇÃO DO USUÁRIO: ETAPA DE ENTRADA 2. MAGIA 3. CONFIGURAÇÃO DO USUÁRIO: ETAPA DE SAÍDA

Isso proporciona um bom equilíbrio entre usabilidade e controle. * O primeiro gatilho é onde você define as entradas. * O segundo gatilho contém a lógica interna que normalmente não precisa ser editada para que o bloco lógico funcione. * O terceiro gatilho é onde você decide o que fazer com o resultado gerado.

Isso significa que, normalmente, você não precisa trabalhar dentro do gatilho de lógica principal. Basta configurar o lado da entrada e o lado da saída. Essa foi uma das principais decisões de design: tornar o bloco lógico reutilizável, expondo apenas as partes que geralmente precisam ser alteradas.

Gatilho 1 – CONFIGURAÇÃO DO USUÁRIO: ESTÁGIO DE ENTRADA

Este é o primeiro gatilho com o qual você interage. Seu objetivo é definir os valores de entrada de que o bloco lógico precisa antes que qualquer ID possa ser gerado.Na prática, esse gatilho é usado para definir:* o prefixo* o formato do número de identificação (idNumberFormat)* o ID da tabela passado para o conectorExemplo:prefixo = ORDidNumberFormat = 00000000ID da tabela = tabela de destino

Este estágio é intencionalmente editável, pois esses são os valores que normalmente variam de caso de uso para caso de uso.

Você pode pegar o mesmo botão, copiá-lo para outro aplicativo ou etapa, atualizar esses valores de configuração e reutilizar o bloco lógico sem alterar a lógica interna.

Isso permite uma implementação mais rápida, pois, uma vez definidos os valores de entrada, o restante do fluxo funciona automaticamente. Esse gatilho também executa a chamada do conector que lê o registro correspondente mais recente da tabela selecionada.

Por que o conector faz parte do projeto

O conector não é apenas um detalhe técnico. Ele faz parte da estratégia de reutilização. Eu uso o conector para ler o ID mais recente existente na tabela de destino. O conector recebe o ID da tabela como entrada, aplica o filtro de prefixo, ordena os registros por _sequenceNumber em ordem decrescente e retorna o registro correspondente mais recente. Isso é importante por dois motivos. Primeiro, permite que o bloco lógico continue a numeração a partir do estado real da tabela. Segundo, torna o bloco lógico muito mais fácil de reutilizar. Se você copiar o botão para outro aplicativo, não precisará reescrever a lógica. Basta apontar o conector para a tabela correta e fornecer a configuração necessária. Esse é o padrão prático por trás da construção: 1. crie o botão uma vez; 2. defina a entrada da tabela no conector; 3. defina o prefixo e o formato do número; 4. reutilize-o sempre que necessário

Essa foi uma escolha deliberada para facilitar a criação mais rápida de aplicativos.

O que o conector faz no bloco de lógica?

O que acontece aqui: * o conector recebe o ID da tabela * o conector recebe o prefixo * o conector filtra os registros pelo prefixo * o conector ordena os registros por _sequenceNumber em ordem decrescente * o conector retorna o ID correspondente mais recente

O conector também oferece suporte a um caso de uso muito importante: múltiplas entidades na mesma tabela.

Se a mesma tabela contiver registros como: ORD00000011 MAT00000003, então o bloco lógico não deve tratá-los como uma sequência compartilhada. Cada prefixo deve continuar apenas a partir de seu próprio valor mais recente.

Portanto, o conector filtra primeiro por prefixo. Isso significa que os próximos valores passam a ser: ORD → ORD00000012, MAT → MAT00000004

Escolhi esse modelo porque entidades diferentes podem coexistir na mesma tabela, mas isso não significa que devam compartilhar o mesmo número sequencial.

Também vale a pena deixar claro um requisito de configuração: o conector precisa de autenticação, portanto, um token deve ser gerado e adicionado às configurações de autenticação do conector. Trato isso como um trabalho de configuração, não como parte da lógica do próprio bloco lógico, mas ainda assim é uma parte importante para tornar o bloco lógico utilizável.

Mantive isso no estágio de entrada porque pertence à configuração e à coleta de contexto. Antes que a lógica interna possa funcionar, o bloco lógico precisa primeiro saber com o que está trabalhando.

Gatilho 2 – MAGIC

Este é o gatilho da lógica central. Eu o nomeei assim de propósito. A ideia é simples: essa é a parte que você normalmente não precisa editar. O bloco lógico já deve trazer essa lógica consigo. Esse gatilho pega os valores de entrada configurados e o ID mais recente retornado pelo gatilho anterior e, então, realiza a tomada de decisão interna. Embora seja um único gatilho, várias coisas acontecem dentro dele.

1. Validação de formato – a parte “if” do gatilho

A primeira verificação confirma se o ID mais recente retornado da tabela ainda corresponde à estrutura esperada. Estrutura esperada: **prefixo + número de comprimento fixo

Exemplo de valor válido: ORD00000025 Exemplos de valores inválidos: ORD25 ORD-00025 ABC00000025 ORD00025A

Expression

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

O que esta função expression faz

Este expression verifica se o ID mais recente segue exatamente a estrutura esperada pelo bloco lógico. Ele faz isso em três partes:* LINK(...) determina qual valor deve ser verificado. Se o conector não retornar um ID, a segunda parte é usada em seu lugar. Essa segunda parte é a estrutura inicial configurada e permite que o bloco lógico gere o primeiro ID para uma nova sequência de prefixos.* '^' + Variable._configPrefix significa que o valor deve começar com o prefixo configurado.* '[0-9]{' + LEN(Variable._configIdNumberFormat) + '}$' significa que o valor deve terminar com exatamente o número esperado de dígitos, e nada a mais é permitido depois disso.

Se o valor completo corresponder à estrutura esperada, o resultado será “ok”. Caso não corresponda, o resultado “ expression ” permanecerá diferente de “ok”, e o gatilho interromperá o fluxo de geração e exibirá uma mensagem de erro

Por que escolhi isso

Adicionei essa verificação porque não queria que o bloco lógico continuasse a partir de um ID malformado. Se o último valor armazenado estiver errado e a lógica aceitá-lo, o padrão incorreto poderá começar a se propagar para registros futuros. Por isso, quis que a primeira responsabilidade do gatilho MAGIC fosse interromper isso logo no início. Se o formato estiver errado: * _stateOutput é zerado * uma mensagem é exibida para explicar por que a geração foi interrompida

2. Validação de estouro

A segunda verificação analisa o próximo valor numérico e verifica se ele ainda se encaixa no comprimento numérico configurado.

Exemplo: idNumberFormat = 0000

Intervalo permitido: 0000 a 9999

Se o próximo valor fosse: 10000, ele não se encaixaria mais na estrutura configurada.

Expression

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

O que esta função expression faz

Este expression compara dois comprimentos:* o comprimento numérico permitido, obtido de idNumberFormat* o comprimento do próximo número após o incremento do valor atual

O lado direito funciona assim:* LINK(...) usa o ID retornado pelo conector, quando houver um. Se o conector não retornar um ID, ele usa a estrutura inicial configurada. É isso que torna possível a geração do primeiro ID.* REGEX_REPLACE(...) remove o prefixo para que apenas a parte numérica permaneça* TEXTTOINTEGER(...) + 1 calcula o próximo valor numérico* TOTEXT(...) converte esse próximo valor de volta em texto* LEN(...) mede quantos dígitos esse próximo valor precisa

Se o próximo número precisar de mais dígitos do que o formato permite, a condição se torna verdadeira e o gatilho interrompe o processo.

Por que escolhi isso

Optei por interromper o processo aqui, em vez de alterar automaticamente a estrutura. Essa decisão foi importante porque eu queria que o comprimento do ID permanecesse estável, a menos que alguém alterasse explicitamente a configuração. Se for detectado estouro:* _stateOutput é zerado* é exibida uma mensagem explicando que o comprimento máximo foi atingido

3. Geração de ID

Se ambas as verificações forem aprovadas, o gatilho gera o próximo ID. Exemplo: ORD00000015 → ORD00000016

Esse mesmo expression também lida com o caso em que ainda não existe nenhum registro para esse prefixo. Nesse caso, a lógica recorre à estrutura inicial configurada e produz o primeiro valor da sequência. Exemplo: ORD00000001

Expression

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

O que este expression faz

Esta é a função expression que constrói o ID final. Ela funciona nesta ordem:* **LINK(...)** usa o último ID retornado pelo conector, quando houver um. Se o conector não retornar um ID, ela usa a estrutura inicial configurada. É isso que permite que a mesma função expression também lide com o primeiro ID gerado.* REGEX_REPLACE(...) remove o prefixo para que a parte numérica possa ser processada* TEXTTOINTEGER(...)+ 1 incrementa o número* TOTEXT(...) converte o novo número de volta em texto* Variable._configIdNumberFormat + ... acrescenta o padrão de zeros à frente do novo valor* RIGHT(..., LEN(Variable._configIdNumberFormat)) mantém apenas o número necessário de dígitos* Variable._configPrefix + ... acrescenta o prefixo de volta para criar o ID final. O resultado é armazenado em _stateOutput.

Por que escolhi isso

Mantive isso intencionalmente como um único “ expression ” central. Não queria ramificações internas separadas para o primeiro ID e o próximo ID, se pudesse evitar. Manter o cálculo em um único lugar facilita a reutilização e torna as alterações futuras mais seguras. Ao final deste gatilho, o valor gerado é armazenado em _stateOutput.

Gatilho 3 – CONFIGURAÇÃO DO USUÁRIO: ESTÁGIO DE SAÍDA

Este é o último gatilho no bloco lógico. Seu objetivo é definir o que deve acontecer com o ID gerado assim que _stateOutput contiver um valor válido. O ponto importante aqui é que esta etapa foi deixada intencionalmente em aberto para você. Se _stateOutput não estiver em branco, você pode decidir o que o bloco lógico deve fazer a seguir.Na configuração atual, o gatilho exibe uma mensagem de orientação que explica o que fazer a seguir:* adicionar a tabela configurada ao aplicativo* substituir esta etapa de mensagem por um gatilho “Criar Registro ”* usar _stateOutput como o ID do novo registro

Eu criei dessa forma porque o comportamento da saída pode variar entre os casos de uso. Você pode querer criar um registro imediatamente. Você pode querer primeiro armazenar o valor em outra variável. Ou talvez você queira usá-lo em um fluxo de trabalho maior antes de gravar qualquer coisa na tabela. Portanto, em vez de definir um comportamento de saída fixo, mantive o estágio de saída configurável. É por isso que a parte voltada para o usuário existe em ambas as extremidades:* a entrada pode ser configurada* a saída pode ser configurada* a lógica central permanece no meio

Por que os gatilhos estão separados dessa forma

Essa estrutura reflete uma das principais escolhas de design em todo o bloco lógico: manter as responsabilidades separadas, mas manter a lógica centralizada.É por isso que não coloquei tudo em um único gatilho enorme. Um único gatilho gigante pode funcionar, mas fica mais difícil de ler, mais difícil de explicar e mais difícil de depurar. Por outro lado, dividir os cálculos matemáticos em muitas partes pequenas também se torna confuso. Por isso, optei por um caminho intermediário:* gatilhos separados para etapas legíveis* um expression centralizado para o cálculo real do ID

Isso me proporciona os benefícios de ambos: * a compilação fica mais fácil de acompanhar * a lógica fica mais fácil de manter * a reutilização é mais simples * edições futuras apresentam menos riscos

Por que a validação e a proteção são importantes neste bloco lógico

Este bloco lógico não se resume apenas a gerar o próximo número. Trata-se também de proteger a tabela contra dados incorretos. Existem dois pontos principais de proteção:

Proteção de formato

O bloco lógico verifica se o último ID existente corresponde à estrutura esperada antes de continuar. Isso impede que a sequência continue com base em IDs que não correspondam à estrutura esperada.

Proteção contra estouro

O bloco lógico verifica se o próximo número ainda se encaixa no comprimento numérico permitido. Isso evita que a estrutura mude inesperadamente à medida que a sequência cresce. Considerei ambos os pontos importantes porque dados de ID incorretos podem se espalhar rapidamente. Assim que um padrão incorreto entra em uma tabela e o gerador começa a usá-lo, esse problema pode afetar todos os registros futuros. Portanto, o bloco lógico foi projetado para interromper o processo antecipadamente, em vez de continuar silenciosamente na direção errada.

Abordagem para nomenclatura de variáveis

Você pode notar que os nomes das variáveis usados dentro do bloco lógico seguem um padrão específico. Exemplos:_configPrefix_configIdNumberFormat_stateLastId_sequenceNumber

Utilizei intencionalmente um estilo de nomenclatura que seja fácil de distinguir das variáveis normalmente criadas dentro dos aplicativos. O objetivo não é técnico, mas prático. Ao importar ou copiar o bloco lógico para um aplicativo, deve ficar imediatamente claro quais variáveis pertencem ao bloco lógico e quais pertencem à lógica do aplicativo.As variáveis do bloco lógico utilizam uma estrutura consistente, de modo que ficam visualmente agrupadas e são menos propensas a entrar em conflito com variáveis definidas pelo usuário. Isso torna o bloco lógico mais fácil de reutilizar e mais seguro para integração em aplicativos existentes, sem conflitos de renomeação ou confusão.

Resumo das decisões de desenvolvimento

Se eu resumir todo o bloco lógico às decisões centrais por trás dele, elas são estas:* Eu queria reutilização rápida, por isso o bloco lógico é orientado por configuração.* Eu queria que a tabela fosse a fonte de verdade, por isso o próximo ID é baseado no registro real mais recente.* Eu queria que a lógica permanecesse fácil de manter, por isso o cálculo é centralizado.* Eu queria que várias entidades em uma única tabela funcionassem de maneira organizada, por isso cada prefixo tem sua própria sequência.* Eu queria proteger a tabela contra padrões inadequados, por isso as verificações de formato e estouro impedem a entrada de dados incorretos antes da criação do registro.* Eu queria que o fluxo do gatilho fosse legível, por isso as responsabilidades são separadas em etapas claras.

É por isso que o bloco de lógica tem essa aparência. Não se trata apenas de um gerador de IDs. É um padrão reutilizável para a criação controlada de IDs que busca equilibrar simplicidade, reutilização e proteção de dados.

Participe

Junte-se à comunidade para compartilhar melhorias, propor unidades adicionais, relatar problemas ou discutir decisões arquitetônicas.