- 발행일
ISO 14817-2 기반 7단계 워크플로우 — 기록부터 배포까지 상태 설계
ISO 14817-2 기반 7단계 워크플로우 — 기록부터 배포까지 상태 설계
표준데이터 등록 시스템(SDMS)을 설계하면서 가장 먼저 맞닥뜨린 질문은 "등록 요청을 어떤 상태들로 쪼갤 것인가" 였습니다. 승인 한 번, 반려 한 번만 있으면 될 것 같았지만, 실제 운영에서는 **관리자(Steward)**와 **위원회(Registra)**가 서로 다른 역할로 끼어들고, 각 단계마다 "사전 검토 → 본 심의" 가 반복되는 구조였습니다.
임의로 상태를 잡지 않고 ISO 14817-2(Intelligent transport systems — ITS central data dictionaries — Part 2: Governance of the Central ITS Data Concept Registry) 가 제시하는 데이터 개념 라이프사이클을 그대로 차용했습니다. 그 결과가 아래의 7단계 상태 머신입니다.
1. ISO 14817-2 개요 — 왜 이 표준인가
ISO 14817-2는 지능형 교통 시스템(ITS)에서 사용되는 데이터 개념(Data Concept)을 중앙 레지스트리(CIDCR, Central ITS Data Concept Registry) 에 등록·관리하는 절차를 규정한 국제표준입니다. 상위 표준인 ISO/IEC 11179(메타데이터 레지스트리) 프레임워크를 ITS 도메인에 맞춰 특화한 문서이고, 핵심은 다음 세 가지입니다.
- 데이터 개념을 등록 상태(registration status) 로 분류하고, 상태 간 이동을 거버넌스 절차로 통제한다.
- 각 상태 이동은 역할(Role) 이 부여된 주체가 승인(Approve) 하거나 반려(Deny) 하는 방식으로만 일어난다.
- 반려 시에는 특정 이전 상태로 되돌아가며, 모든 이력은 감사 추적(audit trail)이 가능해야 한다.
SDMS는 표준데이터 등록뿐 아니라 OID 등록, 메타데이터 등록을 같은 엔진 위에 올려야 했기 때문에, 도메인별 커스텀 상태를 매번 새로 설계하는 대신 국제표준이 이미 검증한 상태 모델을 뼈대로 삼는 게 맞다고 판단했습니다.
2. 7단계 상태 — 기록에서 배포까지
상태 이름은 표준의 영문 용어를 한국어 운영 용어로 매핑했습니다. 코드 상에서는 workflow/iso14817_factory.py의 StateType 상수로 정의돼 있습니다.
# workflow/iso14817_factory.py
class StateType:
"""Constants for workflow state names"""
RECORD = "기록단계"
SUBMIT = "제출단계"
PRE_VERIFY = "사전검증단계"
VERIFY = "검증단계"
PRE_CERTIFY = "사전인증단계"
CERTIFY = "인증단계"
DEPLOY = "배포단계"
각 단계의 의미와 담당 역할은 다음 표와 같습니다.
| # | 상태 | 역할에서 보면 | 다음 단계로 가는 조건 |
|---|---|---|---|
| 1 | 기록단계 (RECORD) | 제출자가 폼 작성 중 — 아직 심사 대상 아님 | 제출자가 "제출" |
| 2 | 제출단계 (SUBMIT) | 관리자 1차 검토 대기열 | 관리자(Steward) 승인 |
| 3 | 사전검증단계 (PRE_VERIFY) | 위원회 검증 대기열 | 위원회(Registra) 승인 |
| 4 | 검증단계 (VERIFY) | 관리자 2차 검토 대기열 | 관리자(Steward) 승인 |
| 5 | 사전인증단계 (PRE_CERTIFY) | 위원회 인증 대기열 | 위원회(Registra) 승인 |
| 6 | 인증단계 (CERTIFY) | 관리자 최종 승인 대기열 | 관리자(Steward) 승인 |
| 7 | 배포단계 (DEPLOY) | 최종 상태 — 공식 등록 완료 | — (완료) |
핵심은 "사전(Pre-) 단계 - 본(Main-) 단계" 가 쌍을 이루며 관리자 ↔ 위원회 가 번갈아 검토한다는 점입니다. 사전검증/사전인증은 위원회가 바로 보기 전에 관리자가 한 번 걸러주는 게이트 역할을 합니다.
3. 3개 역할 — Submitter / Steward / Registra
ISO 14817-2는 상태 이동 권한을 역할에 귀속시킵니다. SDMS에서는 아래 세 역할로 정리했습니다.
class RoleType(Enum):
"""Role types for workflow participants"""
SUBMITTER = "submitter" # 제출자 (관계자그룹)
STEWARD = "steward" # 관리자 (관리자그룹)
REGISTRA = "registra" # 위원회 (위원회그룹)
ETC = "etc"
그룹 이름 역시 한국어 운영 용어와 매핑합니다.
class GroupType:
STAKEHOLDER_GROUP = "관계자그룹" # Submitter
STEWARD_GROUP = "관리자그룹" # Steward
REGISTRA_GROUP = "위원회그룹" # Registra
어떤 역할이 어떤 단계를 봐야 하는지는 리스트 필터로 관리합니다. 예컨대 위원회 사용자가 로그인하면 "내가 할 일"은 사전검증과 사전인증 두 개, "내가 끝낸 일"은 검증·인증·배포 세 개입니다.
STATE_FILTERS = {
RoleType.REGISTRA.value: {
ListType.TODO.value: [StateType.PRE_VERIFY, StateType.PRE_CERTIFY],
ListType.DONE.value: [StateType.VERIFY, StateType.CERTIFY, StateType.DEPLOY]
},
RoleType.STEWARD.value: {
ListType.TODO.value: [StateType.SUBMIT, StateType.VERIFY, StateType.CERTIFY],
ListType.DONE.value: [StateType.PRE_VERIFY, StateType.PRE_CERTIFY, StateType.DEPLOY]
}
}
이 필터가 있으면 화면단에서는 "내 대기열" 을 뽑을 때 역할만 주면 끝이고, 상태 추가 시 코드가 퍼지지 않습니다.
4. 상태 전이 — 승인 경로와 반려 경로
전이(transition)는 9개로 정의됩니다. 7개 상태를 잇는 전이 6개 + 반려 전이 3개입니다.
workflow_stages = [
# 기록 -> 제출 (제출자가 제출 버튼)
{'current': RECORD, 'next': SUBMIT, 'role': SUBMITTER, 'is_start': True},
# 제출 -> 사전검증 (관리자 승인) / 제출 -> 기록 (관리자 반려)
{'current': SUBMIT, 'next': PRE_VERIFY, 'role': STEWARD},
{'current': SUBMIT, 'next': RECORD, 'role': STEWARD, 'is_denial': True},
# 사전검증 -> 검증 (위원회 승인) / 사전검증 -> 기록 (위원회 반려)
{'current': PRE_VERIFY, 'next': VERIFY, 'role': REGISTRA},
{'current': PRE_VERIFY, 'next': RECORD, 'role': REGISTRA, 'is_denial': True},
# 검증 -> 사전인증 (관리자 승인)
{'current': VERIFY, 'next': PRE_CERTIFY, 'role': STEWARD},
# 사전인증 -> 인증 (위원회 승인) / 사전인증 -> 검증 (위원회 반려)
{'current': PRE_CERTIFY, 'next': CERTIFY, 'role': REGISTRA},
{'current': PRE_CERTIFY, 'next': VERIFY, 'role': REGISTRA, 'is_denial': True},
# 인증 -> 배포 (관리자 최종 승인, 완료)
{'current': CERTIFY, 'next': DEPLOY, 'role': STEWARD, 'is_complete': True},
]
반려 경로의 설계 포인트는 "얼마나 멀리 되돌리느냐" 입니다.
- 제출단계·사전검증단계 반려 → 기록단계까지 되돌림 (작성 자체를 다시 해야 할 수준의 문제)
- 사전인증단계 반려 → 검증단계로만 되돌림 (이미 위원회 검증을 통과했으므로 앞 단계 성과는 보존)
이 비대칭이 처음에는 어색했는데, 실제로 위원회 논의를 보면 "인증 단계에서 발견된 이슈는 대부분 표기 문제" 여서 검증 결과까지 뒤집을 필요는 없더군요. 표준 자체도 이런 단계별 되돌림 깊이 차이를 허용합니다.
5. 팩토리 패턴으로 구성한 이유
9개 전이 × 각 전이마다 (상태, 액션, 액션 타깃, 액티비티, 트랜지션 액션·액티비티) 를 일일이 만들면 금방 수백 줄이 됩니다. 그래서 ISO14817Factory 로 한 덩어리를 만들고, 각 전이를 create_process_line 한 번 호출로 압축했습니다.
def create_process_line(self,
process,
current_state_name: str,
next_state_name: str,
role_type: RoleType = RoleType.ETC,
is_denial: bool = False,
is_start: bool = False,
is_complete: bool = False) -> None:
"""Create a complete process line with states, transition, actions, and activities"""
transition = self.create_state_transition(
process, current_state_name, next_state_name,
is_start, is_complete
)
self.create_transition_action_lines(process, transition, role_type, is_denial)
self.create_transition_activity_lines(process, transition)
캐싱도 같이 들어갔습니다. 같은 프로세스의 같은 그룹·액션을 반복 생성하지 않도록 _process_group_cache, _action_cache, _activity_cache 를 두었습니다.
def create_process_group_cached(self, process, name: str):
cache_key = f"{process.id}_{name}"
if cache_key not in self._process_group_cache:
self._process_group_cache[cache_key] = self.create_process_group(process, name)
return self._process_group_cache[cache_key]
전이가 9개인데 그룹이 3개뿐이면 같은 그룹을 27번 조회하게 되므로, 이 캐시가 마이그레이션/초기화 속도를 눈에 띄게 줄여줬습니다.
6. Simple Factory — 5단계 간소화 버전
모든 데이터가 7단계를 거칠 필요는 없습니다. OID 같은 식별자 등록은 "인증" 개념 자체가 약하고, 빠른 반영이 중요합니다. 그래서 ISO14817SimpleFactory 에 5단계(RECORD → SUBMIT → PRE_VERIFY → VERIFY → DEPLOY) 변형을 만들었습니다.
class ISO14817SimpleFactory(ISO14817Factory):
def create(self, process_type: str, process_name: str):
process = self.create_process(process_type, process_name)
self.create_process_group_line(process)
workflow_stages = [
{'current': RECORD, 'next': SUBMIT, 'role': SUBMITTER, 'is_start': True},
{'current': SUBMIT, 'next': PRE_VERIFY, 'role': STEWARD},
{'current': SUBMIT, 'next': RECORD, 'role': STEWARD, 'is_denial': True},
{'current': PRE_VERIFY, 'next': VERIFY, 'role': REGISTRA},
{'current': PRE_VERIFY, 'next': RECORD, 'role': REGISTRA, 'is_denial': True},
{'current': VERIFY, 'next': DEPLOY, 'role': STEWARD, 'is_complete': True},
]
...
핵심은 7단계를 상속하고 create() 만 오버라이드했다는 점입니다. 상태 상수·역할·그룹·캐시 로직은 그대로 재사용하고, "어떤 전이를 포함할지" 만 다시 쓰면 됩니다. 덕분에 OID용으로 별도 엔진을 깎지 않아도 됐습니다.
7. 전이 표를 다시 세어보니 — 반려할 수 없는 단계가 둘 있다
4절의 전이 9개를 반려 가능 여부로 다시 정리하면 이렇게 됩니다.
| 상태 | 담당 | 승인 | 반려 |
|---|---|---|---|
| SUBMIT | 관리자 | → PRE_VERIFY | → RECORD |
| PRE_VERIFY | 위원회 | → VERIFY | → RECORD |
| VERIFY | 관리자 | → PRE_CERTIFY | 없음 |
| PRE_CERTIFY | 위원회 | → CERTIFY | → VERIFY |
| CERTIFY | 관리자 | → DEPLOY | 없음 |
관리자가 담당하는 두 단계(검증·인증)에는 반려 경로가 없습니다. 2절 표에서 이 둘을 "관리자 2차 검토 대기열", "관리자 최종 승인 대기열" 이라고 적어놨는데, 검토인데 통과만 시킬 수 있어요. 관리자가 문제를 발견해도 되돌릴 수단이 없습니다.
반려 3개가 전부 "위원회가 보기 전" 또는 "위원회가 보고 나서" 에 걸려 있고, 관리자의 본 단계는 사실상 통과 도장만 찍는 구조예요. 3절에서 관리자를 "위원회 앞에 한 번 걸러주는 게이트" 라고 설명한 것과도 어긋납니다. 게이트라면 막을 수 있어야 하니까요.
의도한 설계일 수도 있습니다 — 사전 단계에서 이미 걸렀으니 본 단계는 형식적 확인이라고 보는 해석이요. 그렇다면 2절의 "검토 대기열"이라는 표현을 고쳐야 하고, 아니라면 전이 두 개를 추가해야 합니다. 어느 쪽이든 표를 세어보기 전에는 몰랐다는 게 문제입니다.
이 글이 "반려는 일괄적으로 처음으로 되돌리지 않는다" 를 교훈으로 내세우면서, 정작 반려가 아예 없는 단계는 짚지 않았어요. 되돌림 깊이를 설계할 때 "되돌릴 수 있는가" 부터 표로 확인했어야 했습니다.
8. 그 밖에 남은 것
① STATE_FILTERS에 제출자가 없습니다.
STATE_FILTERS = {
RoleType.REGISTRA.value: {...},
RoleType.STEWARD.value: {...},
# SUBMITTER 없음
}
3절에서 "이 필터가 있으면 역할만 주면 끝이고, 상태 추가 시 코드가 퍼지지 않는다" 고 했는데, 세 역할 중 하나가 이 구조 밖에 있습니다. 제출자의 목록은 다른 방식으로 만들어지고 있다는 뜻이고, 그러면 상태를 추가할 때 결국 두 군데를 봐야 해요. 필터의 장점이 절반만 실현된 상태입니다.
② 5단계 버전이 전이 목록을 복제합니다.
ISO14817SimpleFactory는 create()만 오버라이드하는데, 그 안에 workflow_stages 리스트가 통째로 다시 쓰여 있습니다. 7단계와 5단계가 공유하는 전이(RECORD→SUBMIT, SUBMIT→PRE_VERIFY, SUBMIT→RECORD, PRE_VERIFY→VERIFY)가 두 곳에 존재해요.
"선언만 바꿔서 만들 수 있다" 는 7절의 주장은 맞지만, 선언이 복제되는 방식입니다. 전이를 상수로 빼두고 팩토리는 어떤 것을 포함할지 만 고르게 했으면 진짜 선언형이 됐을 거예요. 상속으로 푼 것도 다시 보면 애매합니다 — 두 팩토리의 관계는 부모-자식이 아니라 같은 부품으로 만든 다른 조합에 가깝습니다.
③ 표준 준수를 확인할 장치가 없습니다.
ISO 14817-2의 상태를 한국어 운영 용어로 매핑했는데, 원문 용어와의 대응표가 코드에 없습니다. 나중에 누가 상태를 하나 추가하거나 이름을 바꿔도 "이게 표준을 벗어났는지" 알 방법이 없어요.
상태 상수 옆에 표준 원문 용어를 주석으로 달아두거나, 매핑을 별도 상수로 두는 것만으로도 충분했을 겁니다. 표준을 기준선으로 삼은 것이 이 설계의 핵심 근거인데, 그 기준선이 코드에 남아 있지 않으면 시간이 지나며 조용히 멀어집니다.
9. 정리
- ISO 14817-2는 "정해진 절차대로 데이터 개념을 등록하라" 는 거버넌스 표준이다. 상태 이름만 빌려오는 게 아니라, 역할·승인·반려 구조까지 통째로 가져오는 게 이득이다.
- 7단계(기록·제출·사전검증·검증·사전인증·인증·배포) 는 "사전(Pre-) ↔ 본(Main-)" 쌍으로 읽으면 훨씬 이해가 쉽다. 관리자 게이트가 위원회 앞에 한 번씩 붙는 구조다.
- 반려는 일괄적으로 처음으로 되돌리지 않는다. 단계마다 되돌림 깊이를 다르게 설계해야 실제 검토 흐름과 맞는다.
- 팩토리 + 캐싱으로 전이 선언을 데이터처럼 다루면, 5단계 간소화 버전이나 OID 전용 변형을 선언만 바꿔서 만들 수 있다.
- 역할별 TODO/DONE 필터를 팩토리 상수로 묶어두면 화면단에서 상태를 하드코딩할 일이 사라진다.
표준이 이미 다듬어 놓은 상태 모델을 그대로 따를 수 있다면, 팀 내부에서 "왜 이 단계가 필요한가" 를 매번 재논의할 필요가 없다는 점이 가장 큰 장점이었습니다. 커스텀 상태는 자주 "어느 팀이 더 목소리가 큰가"에 따라 흔들리는데, 국제표준을 기준선으로 두면 그 논의 자체가 줄어듭니다.