순차적 ID 생성기

Prev Next

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

이 글에서는 저희 라이브러리 개발자 중 한 명인 타마스(Tamás)가 라이브러리에서 제공되는 ‘순차적 ID 생성기(Sequential ID Generator)’ 빌딩 블록 앱의 개발 배경에 대해 설명합니다.

왜 이런 방식으로 만들었는가

저는 이 로직 블록이 매우 실용적인 문제를 해결하기를 원했습니다. 바로 신뢰할 수 있고, 재사용하기 쉬우며, 다양한 앱에 빠르게 배포할 수 있는 방식으로 ID를 생성하는 것이었습니다.

가장 중요한 설계 목표는 단순히 “다음 번호를 생성하는 것”이 아니었습니다. 진정한 목표는 한 번 정의하면 아주 적은 노력으로 복사하여 재사용할 수 있는 것을 만드는 것이었습니다. 버튼을 한 번 깔끔하게 구축했다면, 다른 앱이나 다른 단계에 배치하고 설정만 조정할 수 있어야 합니다. 모든 앱 제작자가 매번 로직을 다시 열고 트리거를 재작성해야 하는 상황을 원치 않았습니다.

이것이 바로 이 로직 블록이 로직보다 구성을 우선시하도록 설계된 이유입니다. 이 로직 블록을 사용할 때 주로 고려해야 할 사항은 다음과 같습니다: * 어떤 접두사가 필요한지 * 숫자 부분의 길이는 얼마여야 하는지 * 버튼이 어떤 테이블에서 데이터를 읽어야 하는지

접두사, 숫자 형식, 테이블을 정의하기만 하면, 로직 블록이 나머지 작업(최신 ID 읽기, 유효성 검사, 다음 값 생성, 숫자가 설정된 길이에 여전히 맞는지 확인)을 자동으로 처리합니다.

테이블 자체가 신뢰할 수 있는 데이터의 원천이 되어야 합니다. 별도의 카운터(예를 들어 변수)는 레코드가 예상치 못한 방식으로 삽입, 삭제 또는 수정될 경우 실제 데이터와 차이가 발생할 수 있습니다. 테이블에서 데이터를 읽어오면 번호 매기기가 실제로 존재하는 데이터와 밀접하게 연동됩니다.

또 다른 중요한 결정은 핵심 계산 로직을 한 곳에 집중시키는 것이었습니다. 첫 번째 ID를 위한 트리거 분기 하나, 다음 ID를 위한 또 다른 분기, 그리고 나중에 추적하기 어려워질 수 있는 추가적인 작은 트리거 조각들이 흩어져 있는 상황을 원치 않았습니다. 동작이 일관되게 유지되고, 나중에 이를 수정하는 사람이 정확히 어디를 살펴봐야 할지 알 수 있도록 메인 로직을 하나의 expression에 포함시키고 싶었습니다.

따라서 이 로직 블록은 다음과 같은 몇 가지 실용적인 결정 사항을 바탕으로 구축되었습니다: * 복사 및 재사용이 용이함 * 잘못된 ID 패턴으로부터 테이블을 보호할 만큼 안전함 * 구성이 간단함 * 유지보수가 용이하도록 로직을 중앙 집중화함

ID의 형태

생성된 각 ID는 두 부분으로 구성됩니다:접두사 + 숫자 시퀀스

예시:* ORD00000125* MAT00000003* TEST00004567

접두사는 현재 보고 있는 레코드의 종류를 알려줍니다. 숫자 부분은 연속된 순서 번호입니다. 저는 숫자 형식 설정을 매우 간단하게 유지하기로 했습니다. 사용자에게 “8자리”와 같은 별도의 숫자를 요청하는 대신, 로직 블록은 다음과 같은 0 패턴을 사용합니다:* 00000000

이렇게 하면 한눈에 파악하기가 더 쉽습니다. 누군가 8개의 0을 보면, 숫자 부분이 항상 8자 길이어야 한다는 것을 즉시 이해할 수 있습니다. 그래서 idNumberFormat을 순수한 길이 필드로 대체하는 대신 이 형식을 유지했습니다.

예시:* 00000000 → 8자리 숫자* 0000 → 4자리 숫자

사소한 결정이지만 중요한 의미가 있습니다. 이를 통해 기술적인 매개변수 개념으로 사고하기를 원치 않는 사람들도 로직 블록을 더 쉽게 이해할 수 있게 됩니다.

빌드 구조

실제 구현에는 세 개의 트리거가 사용됩니다. 이러한 구조는 의도된 것입니다. 저는 로직 블록을 위에서 아래로 쉽게 따라갈 수 있게 하면서도, 주요 로직은 분리하여 재사용할 수 있도록 하고 싶었습니다. 따라서 논리를 여러 개의 편집 가능한 트리거에 분산시키는 대신, 다음 세 단계로 구성했습니다.1. 사용자 구성: 입력 단계 2. 매직 3. 사용자 구성: 출력 단계

이를 통해 사용 편의성과 제어력 사이에서 적절한 균형을 이룹니다. * 첫 번째 트리거에서는 입력값을 정의합니다. * 두 번째 트리거에는 로직 블록이 작동하는 데 일반적으로 편집할 필요가 없는 내부 로직이 포함되어 있습니다. * 세 번째 트리거에서는 생성된 결과물을 어떻게 처리할지 결정합니다.

즉, 일반적으로 핵심 로직 트리거 내부에서 작업할 필요가 없습니다. 사용자는 입력 측과 출력 측만 구성하면 됩니다. 이것이 핵심 설계 결정 사항 중 하나였습니다. 즉, 일반적으로 변경이 필요한 부분만 노출함으로써 로직 블록을 재사용 가능하게 만든 것입니다.

트리거 1 – 사용자 구성: 입력 단계

이 트리거는 사용자가 가장 먼저 상호작용하게 되는 트리거입니다. 이 트리거의 목적은 ID가 생성되기 전에 로직 블록에 필요한 입력 값을 정의하는 것입니다.실제로 이 트리거는 다음을 정의하는 데 사용됩니다: * 접두사 * idNumberFormat * 커넥터로 전달되는 테이블 ID 예시: prefix = ORDidNumberFormat = 00000000 table ID = 대상 테이블

이 단계는 사용 사례마다 일반적으로 변경되는 값들이므로 의도적으로 편집 가능하도록 설계되었습니다.

동일한 버튼을 다른 앱이나 단계로 복사한 다음, 이러한 구성 값을 업데이트하여 내부 로직을 건드리지 않고도 로직 블록을 재사용할 수 있습니다.

입력 값이 한 번 정의되면 나머지 흐름이 자동으로 작동하므로, 이를 통해 더 빠른 구현이 가능합니다. 또한 이 트리거는 선택한 테이블에서 가장 최근의 일치하는 레코드를 읽어오는 커넥터 호출을 실행합니다.

커넥터가 설계의 일부인 이유

커넥터는 단순한 기술적 세부 사항이 아닙니다. 이는 재사용 전략의 일부입니다. 저는 커넥터를 사용하여 대상 테이블에서 가장 최근의 기존 ID를 읽어옵니다. 커넥터는 테이블 ID를 입력으로 받아 접두사 필터를 적용하고, _sequenceNumber 기준 내림차순으로 레코드를 정렬한 후, 가장 최근의 일치하는 레코드를 반환합니다. 이는 두 가지 이유로 중요합니다. 첫째, 로직 블록이 테이블의 실제 상태에서 번호 매기기를 계속할 수 있게 해줍니다. 둘째, 로직 블록을 훨씬 더 쉽게 재사용할 수 있게 해줍니다. 이 버튼을 다른 앱으로 복사하더라도 로직을 다시 작성할 필요가 없습니다. 커넥터를 올바른 테이블로 지정하고 필요한 구성을 제공하기만 하면 됩니다. 이것이 빌드 뒤에 숨겨진 실용적인 패턴입니다: 1. 버튼을 한 번 생성합니다. 2. 커넥터에서 테이블 입력을 설정합니다. 3. 접두사와 번호 형식을 정의합니다. 4. 필요한 곳 어디에서나 재사용합니다.

이는 앱 구축 속도를 높이기 위해 의도적으로 선택한 방식입니다.

로직 블록에서 커넥터는 어떤 역할을 할까요?

여기서 일어나는 과정은 다음과 같습니다: * 커넥터가 테이블 ID를 수신합니다 * 커넥터가 접두사를 수신합니다 * 커넥터가 접두사를 기준으로 레코드를 필터링합니다 * 커넥터가 _sequenceNumber를 기준으로 레코드를 내림차순으로 정렬합니다 * 커넥터가 일치하는 최신 ID를 반환합니다

커넥터는 또한 동일한 테이블 내의 여러 엔티티라는 매우 중요한 사용 사례도 지원합니다.

동일한 테이블에 다음과 같은 레코드가 포함되어 있다면:ORD00000011MAT00000003이 경우 로직 블록은 이를 하나의 공유 시퀀스로 취급해서는 안 됩니다. 각 접두사는 해당 접두사의 최신 값부터만 이어져야 합니다.

따라서 커넥터는 먼저 접두사를 기준으로 필터링합니다. 즉, 다음 값은 다음과 같이 됩니다: ORD → ORD00000012, MAT → MAT00000004

제가 이 모델을 선택한 이유는 서로 다른 엔티티가 같은 테이블에 존재할 수는 있지만, 그렇다고 해서 동일한 연속 번호를 공유해야 한다는 의미는 아니기 때문입니다.

설정 요건 중 하나도 명확히 언급할 필요가 있습니다. 커넥터에는 인증이 필요하므로, 토큰을 생성하여 커넥터 인증 설정에 추가해야 합니다. 저는 이를 로직 블록의 논리 자체의 일부가 아닌 설정 작업으로 간주하지만, 로직 블록을 사용 가능하게 만드는 데 있어 여전히 중요한 부분입니다.

이 작업은 설정 및 컨텍스트 수집에 해당하므로 입력 단계에 포함시켰습니다. 내부 로직이 작동하기 전에, 로직 블록은 먼저 어떤 데이터를 다루고 있는지 파악해야 합니다.

트리거 2 – MAGIC

이것이 핵심 로직 트리거입니다. 저는 의도적으로 이렇게 이름을 지었습니다. 아이디어는 간단합니다. 이 부분은 일반적으로 사용자가 직접 수정할 필요가 없는 부분입니다. 로직 블록은 이미 이 로직을 내장하고 있어야 합니다. 이 트리거는 구성된 입력 값과 이전 트리거에서 반환된 최신 ID를 받아 내부 의사 결정을 수행합니다. 비록 하나의 트리거이지만, 내부적으로는 여러 가지 작업이 이루어집니다.

1. 형식 유효성 검사 - 트리거의 ‘if’ 부분

첫 번째 검사는 테이블에서 반환된 최신 ID가 여전히 예상된 구조와 일치하는지 확인합니다. 예상된 구조: **접두사 + 고정 길이 숫자

유효한 값 예시: ORD00000025 유효하지 않은 값 예시: ORD25, ORD-00025, ABC00000025, ORD00025A

Expression

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

이 expression 의 기능

이 expression 는 최신 ID가 로직 블록이 기대하는 정확한 구조를 따르는지 확인합니다. 이는 세 단계로 이루어집니다: * LINK(...)는 어떤 값을 확인할지 결정합니다. 커넥터가 ID를 반환하지 않는 경우, 대신 두 번째 부분이 사용됩니다. 이 두 번째 부분은 구성된 시작 구조이며, 이를 통해 로직 블록은 새로운 접두사 시퀀스에 대한 첫 번째 ID를 생성할 수 있습니다.* '^' + Variable._configPrefix는 값이 구성된 접두사로 시작해야 함을 의미합니다.* '[0-9]{' + LEN(Variable._configIdNumberFormat) + '}$'는 값이 예상된 자릿수만큼 정확히 끝나야 하며, 그 뒤에는 추가 문자가 허용되지 않음을 의미합니다.

전체 값이 예상된 구조와 일치하면 결과는 'ok'가 됩니다. 일치하지 않으면 ' expression ' 결과는 'ok'와 다르게 유지되며, 트리거는 생성 흐름을 중지하고 오류 메시지를 표시합니다.

이 방식을 선택한 이유

이 검사를 추가한 이유는 형식이 잘못된 ID로 인해 로직 블록이 계속 진행되는 것을 원치 않았기 때문입니다. 가장 최근에 저장된 값이 잘못되었는데도 로직이 이를 허용하면, 잘못된 패턴이 향후 레코드까지 전파될 수 있습니다. 따라서 MAGIC 트리거의 첫 번째 역할은 이러한 문제를 조기에 차단하는 것으로 정했습니다. 형식이 잘못된 경우: * _stateOutput이 초기화됩니다 * 생성 중단 사유를 설명하는 메시지가 표시됩니다

2. 오버플로 유효성 검사

두 번째 검사는 다음 숫자 값을 확인하여, 해당 값이 설정된 숫자 길이 내에 여전히 포함되는지 검증합니다.

예시: idNumberFormat = 0000

허용 범위: 0000 ~ 9999

다음 값이 10000이 된다면, 더 이상 구성된 구조에 맞지 않게 됩니다.

Expression

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

이 expression 의 기능

이 expression 는 다음 두 길이를 비교합니다:* idNumberFormat에서 가져온 허용되는 숫자 길이* 현재 값을 증가시킨 후 나오는 다음 숫자의 길이

우측 부분은 다음과 같이 작동합니다:* LINK(...)는 커넥터가 존재할 경우 커넥터가 반환한 ID를 사용합니다. 커넥터가 ID를 반환하지 않으면 대신 구성된 시작 구조를 사용합니다. 이것이 첫 번째 ID 생성을 가능하게 합니다.* REGEX_REPLACE(...) 는 접두사를 제거하여 숫자 부분만 남깁니다.* TEXTTOINTEGER(...) + 1은 다음 숫자 값을 계산합니다.* TOTEXT(...)는 그 다음 값을 다시 텍스트로 변환합니다.* LEN(...)은 다음 값에 필요한 자릿수를 측정합니다

다음 숫자가 형식이 허용하는 것보다 더 많은 자릿수를 필요로 하는 경우, 조건이 참이 되어 트리거가 프로세스를 중지합니다.

이 방식을 선택한 이유

구조를 자동으로 변경하는 대신, 이 단계에서 프로세스를 중지하기로 결정했습니다. 누군가가 명시적으로 구성을 변경하지 않는 한 ID 길이가 일정하게 유지되기를 원했기 때문에 이 결정은 중요했습니다. 오버플로가 감지되면:* _stateOutput이 지워집니다* 최대 길이에 도달했음을 설명하는 메시지가 표시됩니다

3. ID 생성

두 검사 모두 통과하면 트리거가 다음 ID를 생성합니다. 예: ORD00000015 → ORD00000016

이 동일한 expression는 해당 접두사에 대한 레코드가 아직 존재하지 않는 경우도 처리합니다. 이 경우 로직은 구성된 시작 구조로 되돌아가 시퀀스의 첫 번째 값을 생성합니다. 예: 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))

이 expression 의 기능

이 expression 는 최종 ID를 생성합니다. 작동 순서는 다음과 같습니다:* LINK(...)는 커넥터가 반환한 마지막 ID가 있는 경우 이를 사용합니다. 커넥터가 ID를 반환하지 않으면, 대신 구성된 시작 구조를 사용합니다. 이를 통해 동일한 expression 가 처음 생성된 ID도 처리할 수 있습니다.* REGEX_REPLACE(...)는 접두사를 제거하여 숫자 부분을 처리할 수 있게 합니다.* TEXTTOINTEGER(...)+ 1은 숫자를 1씩 증가시킵니다.* TOTEXT(...)는 새로운 숫자를 다시 텍스트로 변환합니다.* Variable._configIdNumberFormat + ... 는 새 값 앞에 0 패턴을 추가합니다.* RIGHT(..., LEN(Variable._configIdNumberFormat))는 필요한 자릿수만 유지합니다.* Variable._configPrefix + ...는 접두사를 다시 추가하여 최종 ID를 생성합니다. 결과는 _stateOutput에 저장됩니다.

이 방식을 선택한 이유

의도적으로 이를 하나의 중앙 집중식 expression 로 유지했습니다. 가능하다면 첫 번째 ID와 다음 ID를 위한 별도의 내부 분기를 만들고 싶지 않았습니다. 계산을 한 곳에 모아두면 재사용이 쉬워지고 향후 변경 시에도 안전합니다. 이 트리거의 마지막에 생성된 값은 _stateOutput에 저장됩니다.

트리거 3 – 사용자 구성: 출력 단계

이 트리거는 로직 블록의 마지막 트리거입니다. 이 트리거의 목적은 _stateOutput에 유효한 값이 포함되면 생성된 ID로 어떤 작업을 수행할지 정의하는 것입니다. 여기서 중요한 점은 이 단계가 의도적으로 사용자에게 맡겨져 있다는 것입니다. _stateOutput이 비어 있지 않다면, 로직 블록이 다음에 어떤 작업을 수행할지 결정할 수 있습니다.현재 설정에서 이 트리거는 다음 단계에 대해 설명하는 안내 메시지를 표시합니다: * 구성된 테이블을 앱에 추가 * 이 메시지 단계를 ‘레코드 생성(Create Record )’ 트리거로 대체 * _stateOutput을 새 레코드 ID로 사용

사용 사례에 따라 출력 동작이 달라질 수 있으므로 이렇게 구성했습니다. 레코드를 즉시 생성하고 싶을 수도 있고, 먼저 값을 다른 변수에 저장하고 싶을 수도 있습니다. 혹은 테이블에 아무것도 기록하기 전에 더 큰 워크플로우에서 해당 값을 사용하고 싶을 수도 있습니다. 따라서 하나의 고정된 출력 동작을 강제로 적용하는 대신, 출력 단계를 구성 가능하게 유지했습니다. 이것이 바로 사용자 인터페이스 부분이 양쪽 끝에 모두 존재하는 이유입니다:* 입력을 구성할 수 있습니다* 출력을 구성할 수 있습니다* 핵심 로직은 중간에 위치합니다

트리거가 왜 이런 방식으로 분리되어 있는지

이 구조는 전체 로직 블록의 주요 설계 선택 사항 중 하나를 반영합니다. 바로 책임은 분리하되, 핵심 로직은 중앙에 집중시키는 것입니다.이것이 바로 모든 것을 하나의 거대한 트리거에 넣지 않은 이유입니다. 단일한 거대한 트리거도 작동할 수는 있겠지만, 가독성이 떨어지고 설명하기 어렵며 디버깅하기도 더 어려워집니다. 반면, 실제 수학적 연산을 여러 작은 조각으로 나누면 코드가 복잡해집니다. 그래서 저는 중간적인 방법을 선택했습니다:* 가독성을 높이기 위해 각 단계를 별도의 트리거로 분리* 실제 ID 계산을 위한 하나의 중앙 집중식 expression

이를 통해 두 가지 장점을 모두 얻을 수 있습니다:* 빌드 과정을 더 쉽게 파악할 수 있습니다* 로직을 유지보수하기가 더 쉬워집니다* 재사용이 더 간편해집니다* 향후 수정 시 위험이 줄어듭니다

이 로직 블록에서 유효성 검사와 보호가 중요한 이유

이 로직 블록은 단순히 다음 숫자를 생성하는 것뿐만 아니라, 테이블을 잘못된 데이터로부터 보호하는 역할도 합니다. 주요 보호 지점은 두 가지입니다:

형식 보호

이 로직 블록은 진행을 계속하기 전에, 기존 마지막 ID가 예상된 구조와 일치하는지 확인합니다. 이를 통해 예상된 구조와 일치하지 않는 ID를 기반으로 시퀀스가 계속되는 것을 방지합니다.

오버플로우 방지

이 로직 블록은 다음 번호가 허용된 숫자 길이 내에 들어가는지 확인합니다. 이를 통해 시퀀스가 확장될 때 구조가 예기치 않게 변경되는 것을 방지합니다. 잘못된 ID 데이터는 빠르게 확산될 수 있으므로, 저는 이 두 가지 모두를 중요하게 고려했습니다. 한 번 잘못된 패턴이 테이블에 유입되어 생성기가 이를 사용하기 시작하면, 그 문제는 향후 모든 레코드에 영향을 미칠 수 있습니다. 따라서 로직 블록은 잘못된 방향으로 조용히 진행하기보다는 조기에 중단되도록 설계되었습니다.

변수 명명 방식

로직 블록 내에서 사용되는 변수 이름이 특정 패턴을 따르는 것을 눈치채셨을 것입니다. 예시:_configPrefix_configIdNumberFormat_stateLastId_sequenceNumber

저는 앱 내에서 일반적으로 생성되는 변수와 쉽게 구별될 수 있는 명명 스타일을 의도적으로 사용했습니다. 그 목적은 기술적인 것이 아니라 실용적인 것입니다. 로직 블록을 앱으로 가져오거나 복사할 때, 어떤 변수가 로직 블록에 속하고 어떤 변수가 앱 로직에 속하는지 한눈에 파악할 수 있어야 합니다.로직 블록 변수는 일관된 구조를 사용하므로 시각적으로 한 그룹으로 묶여 보이고, 사용자 정의 변수와 충돌할 가능성이 줄어듭니다. 이를 통해 로직 블록을 더 쉽게 재사용할 수 있을 뿐만 아니라, 이름 변경 충돌이나 혼란 없이 기존 앱에 더 안전하게 통합할 수 있습니다.

구현 결정 사항 요약

전체 로직 블록을 그 이면에 있는 핵심 결정 사항으로 요약하면 다음과 같습니다. * 빠른 재사용을 원했기 때문에 로직 블록은 구성 기반입니다. * 테이블이 신뢰할 수 있는 정보의 원천이 되기를 원했기 때문에 다음 ID는 최신 실제 레코드를 기반으로 합니다. * 로직의 유지 관리 용이성을 원했기 때문에 계산은 중앙 집중화되었습니다. * 하나의 테이블에 여러 엔티티가 깔끔하게 작동하기를 원했기 때문에, 각 접두사마다 고유한 시퀀스를 갖도록 했습니다.* 테이블을 잘못된 패턴으로부터 보호하기 위해, 형식 및 오버플로 검사를 통해 레코드 생성 전에 잘못된 데이터를 차단합니다.* 트리거 흐름을 가독성 있게 만들고자 했기 때문에, 각 책임을 명확한 단계로 분리했습니다.

이것이 바로 해당 로직 블록이 현재와 같은 형태를 띠게 된 이유입니다. 이는 단순한 ID 생성기가 아닙니다. 단순성, 재사용성, 데이터 보호의 균형을 맞추려는, 통제된 ID 생성을 위한 재사용 가능한 패턴입니다.

참여하기

커뮤니티에 참여하여 개선 사항을 공유하고, 추가 유닛을 제안하며, 문제를 보고하거나 아키텍처 결정 사항에 대해 논의해 보세요.