공정관리 시스템, 공정 단계를 직접 바꾸는 기능이 꼭 필요할까?
제조 고객사의 공정관리 앱을 만들면서 계약서에 이런 한 줄이 들어갔습니다. 「직원이 공정 단계를 직접 정의하고, 진행 상태를 기록한다.」 진행 상태 기록은 이미 넘치게 만들어 둔 뒤였습니다. 남은 것은 「정의」 쪽이었고, 처음에는 설정 화면 하나면 끝날 일로 보였습니다.
단계 코드를 따라 코드베이스를 뒤져 보니 그렇지 않았습니다. 이 글은 그 기능을 어디까지 만들지 정한 과정과, 이런 기능을 실제로 만든다면 어떤 원리로 짜고 무엇을 조심해야 하며 기존 데이터는 어떻게 옮기는지를 정리한 것입니다.
단계 코드 하나에 매달려 있던 것들
설명을 위해 흔한 가공 공장의 흐름으로 바꿔 쓰면, 공정은 자재 → 절단 → 가공 → 도장 → 건조 → 검사 → 출고 → 완료처럼 흐릅니다. 단계는 코드 상수로 박혀 있었고, 각 공정은 지금 단계를 CUT 같은 코드로 들고 있었습니다. 문제는 그 코드에 매달린 것이 생각보다 많았다는 점입니다.
공정마다 지금 단계와 모든 이동 이력(어느 단계에서 어느 단계로, 언제, 누가)이 코드로 남아 있었습니다. 품목 종류마다 거치는 순서가 달랐고(전 공정을 거치는 품목, 중간 단계부터 시작하는 재작업 품목, 외주로 돌리는 품목), 단계마다 넘길 수 있는 사람이 정해져 있었습니다. 원래 쓰던 엑셀의 「가공일」·「출고일」 같은 칸은 단계 이동 이력에서 날짜를 읽어 만들었고, 대시보드는 단계별 평균 소요일과 병목, 불량이 난 단계를 같은 코드로 셌습니다.
그 상태에서 직원이 단계를 마음대로 고치면 이렇게 됩니다.
| 직원이 한 일 | 일어나는 일 |
|---|---|
| 단계 삭제 | 그 단계에 있던 공정이 갈 곳을 잃고, 지난 이력의 단계 이름이 깨집니다 |
| 순서 바꾸기 | 지난 이동이 「앞으로 넘김」이었는지 「되돌림」이었는지 판단이 뒤집히고, 평균 소요일이 틀어집니다 |
| 단계 더하기 | 누가 넘기는지, 엑셀 어느 칸에 날짜가 들어가는지를 같이 정하지 않으면 주인 없는 단계가 생깁니다 |
| 이름 바꾸기 | 아무 일도 일어나지 않습니다 |
마지막 줄만 안전한 데는 이유가 있었습니다. 데이터에는 코드만 들어 있고, 화면은 코드를 이름으로 바꿔 보여 주고 있었기 때문입니다.
설정 가능한 단계를 짜는 다섯 가지 원리
이런 기능은 처음부터 새로 생각할 필요가 없습니다. 업무 흐름을 다루는 시스템들이 오래 써 온 원리가 있고, 저는 지하철 노선도에 빗대면 가장 잘 이해된다고 봅니다.
번호와 이름을 떼어 둡니다. 역에는 바뀌지 않는 번호가 있고 안내판의 이름은 따로 있습니다. 데이터에는 번호(코드)만 저장하고 화면은 코드표를 보고 이름을 그립니다. 국내 SI에서 「공통코드」라고 부르는 그것입니다. 이름 바꾸기가 안전했던 이유가 이것입니다.
단계 목록을 코드가 아니라 표에 둡니다. 코드, 이름, 맡는 역할, 사용 여부를 DB 표 한 장에 두고 화면에서 그 표를 고칩니다. 메타데이터 기반 설계라고 부릅니다.
노선을 따로 정의합니다. 같은 역이라도 2호선과 5호선이 다르게 지나갑니다. 품목 종류마다 「거치는 단계와 순서」를 따로 둡니다. 생산관리(MES) 쪽에서는 라우팅이라고 부르고, 단계와 그 사이 이동을 형식으로 보면 상태 기계(상태 전이표)입니다.
각 품목은 지금 역과 지나온 기록만 갖습니다. 공정마다 지금 단계 코드 하나와 이동 이력을 남깁니다. 이력만으로 지금 상태를 만들어 내는 극단형을 이벤트 소싱이라 부르는데, 실무에서는 지금 상태도 같이 저장하는 절충형이 다루기 쉽습니다. 이 앱도 그렇게 했고, 지난 날짜의 화면은 이력으로 「그날 끝의 단계」를 다시 그립니다.
노선을 바꿀 때 이미 탄 사람을 지킵니다. 이게 핵심입니다. 노선을 개편해도 이미 열차에 탄 승객은 원래 노선으로 종점까지 가야 합니다. 업무 흐름 엔진들은 정의에 버전을 붙여 이 문제를 풉니다. Camunda 문서는 이렇게 적습니다.
Running process instances will continue to run in the version they were started in.
(Camunda: Process Versioning) 버전 대신 「옛 단계 → 새 단계」 매핑 표를 받아 진행 중인 건을 한 번에 옮기는 방법도 있고, 가장 단순하게는 단계를 지우지 않고 「사용 안 함」으로 끄는 방법(논리 삭제)이 있습니다.
통계는 단계 이름이 아니라 성격에 걸어야 하는 이유
다섯 가지를 다 갖춰도 하나가 남습니다. 대시보드가 「도장」이라는 특정 단계를 찾아 병목을 세고 있으면, 직원이 도장을 둘로 나누는 순간 계산이 조용히 틀립니다.
Jira가 이 문제를 푸는 방식이 깔끔합니다. 상태는 마음대로 만들 수 있지만 모두 셋 중 하나의 분류에 속해야 합니다.
All statuses, even custom statuses you create yourself, must belong to one of three status categories – To do, In progress, or Done.
(Jira: What is a workflow status?) 목록과 보고서는 이 분류로 그립니다. 공정이라면 「대기·가공·검사·출고·완료」처럼 성격 태그를 단계에 붙이고, 통계는 태그를 셉니다. 단계가 몇 개로 늘든 「출고까지 며칠」은 그대로 계산됩니다.
이런 설계에서 조심할 것들
만들어 보기 전에 짚어 두면 좋은 함정들이 있습니다. 하나씩 경험으로 겪으면 비쌉니다.
- 단계의 키를 자동 증가 번호로 바꾸지 않습니다. 이미
CUT같은 글자 코드로 쌓인 데이터가 있다면 그 코드를 그대로 표의 키로 씁니다. 숫자 id 로 바꾸면 모든 이력을 번역해야 하고, 그 번역이 마이그레이션에서 가장 위험한 단계가 됩니다. - 이동 이력에 「방향」을 그때 적어 둡니다. 이력에 출발·도착 코드만 있으면 「앞으로 넘겼나」는 단계 순서로 판단하게 되고, 순서를 바꾸는 순간 과거 판단이 바뀝니다. 이동하는 순간의 방향(넘김·되돌림)을 칸으로 남기면 순서 변경에 흔들리지 않습니다.
- 이름은 지금 이름으로, 감사 기록은 그때 이름으로. 화면의 이력은 지금 이름으로 보이는 편이 읽기 쉽고, 누가 무엇을 바꿨는지 남기는 감사 기록은 그때의 이름을 복사해 둬야 나중에 이름이 바뀌어도 기록이 읽힙니다.
- 주인 없는 단계를 만들지 못하게 합니다. 새 단계를 저장할 때 「누가 넘기는지」를 필수로 받습니다. 권한 표가 단계를 모르면 아무도 그 단계를 넘기지 못하고, 공정이 거기서 멈춥니다.
- 경로를 저장할 때 검사합니다. 시작 단계가 하나인지, 모든 경로가 완료 분류의 단계로 끝나는지, 숨긴 단계가 경로 중간에 남아 있지 않은지 확인합니다. 잘못된 경로는 진행 중인 공정을 길 없는 곳에 세웁니다.
- 숨긴 단계에 있던 건의 출구를 둡니다. 이미 그 단계에 있는 공정은 숨긴 뒤에도 「다음」을 누르면 경로의 다음 단계로 나갈 수 있어야 합니다.
- 다국어라면 단계 이름도 번역 대상입니다. 고객 화면에 진행 상황을 보여 준다면 사전에 단계 이름이 들어가야 하고, 직원이 이름을 바꾸면 번역이 따라가지 못합니다.
기존 데이터를 옮기는 순서
이미 코드 상수로 돌아가는 시스템을 표 기반으로 바꾼다면, 저는 Martin Fowler가 Parallel Change라 부른 늘리기 → 옮기기 → 줄이기 순서를 따르겠습니다. 단계마다 배포해도 안전하다는 것이 이 순서의 장점입니다.
1. 늘리기: 표를 만들고 지금 값으로 채웁니다. 단계 표(코드를 키로, 이름·순서·성격 태그·맡는 역할·사용 여부)와 경로 표(종류별 단계 순서)를 새로 만들고, 코드 상수에 있던 값을 그대로 넣습니다. 기존 공정과 이력은 한 줄도 건드리지 않습니다. 표를 새로 만들고 행을 넣기만 하므로 되돌리기도 표를 지우면 끝입니다.
2. 두 곳을 견줍니다. 앱은 아직 상수를 읽게 두고, 스크립트로 「표의 값과 상수가 같은가」를 확인합니다. 같아야 다음으로 갑니다.
3. 옮기기: 읽는 곳을 표로 바꿉니다. 경로, 권한, 색, 대시보드, 대장을 하나씩 표를 읽게 바꿉니다. 이때 이동 이력에 방향 칸을 더하고, 지금의 단계 순서로 지난 이력의 방향을 채워 둡니다. 순서를 바꿀 수 있게 되기 전에 해야 하는 일입니다. 저장된 값을 바꾸는 작업이라 직전 백업을 뜨고, 백업이 실제로 되살아나는지 확인한 뒤에 진행합니다. 저희 쪽은 이런 마이그레이션에 백업 기록이 없으면 배포 빌드가 멈추도록 해 두었습니다.
4. 진행 중인 건을 붙잡습니다. 경로에 버전을 쓴다면, 모든 기존 공정에 「1판」을 적어 둡니다. 그 뒤로 경로를 고치면 2판이 생기고, 새 공정만 2판을 탑니다.
5. 줄이기: 상수를 지웁니다. 며칠 동안 표로 문제없이 돈 것을 본 뒤에 코드 상수를 걷어 냅니다. 한 번에 지우지 않는 것이 이 순서의 전부입니다.
가장 완벽한 안에서 물러서게 한 질문
여기까지 정리하고 나서 저는 처음에 「다 만들면 가장 완벽하다」 쪽으로 기울었습니다. 이름 바꾸기, 숨기기, 더하기, 순서 바꾸기를 모두 지원하는 안이었고, 품은 2~3일로 봤습니다.
그때 받은 질문이 「공정 단계가 실제로 그렇게 자주 바뀌나」였습니다. 답은 「아니다」였습니다. 공정 단계는 설비와 작업 순서를 옮긴 것이라, 새 설비를 들이거나 공정을 나누거나 외주로 돌릴 때나 바뀝니다. 몇 년에 한 번이고, 그마저 대개 이름입니다. 자주 바뀌는 것은 전체 단계 목록이 아니라 품목마다 어떤 단계를 거치느냐였고, 그건 이미 「종류마다 다른 경로」로 처리하고 있었습니다.
이 프로젝트에서도 단계가 일주일 사이 두 번 바뀌긴 했습니다. 한 단계를 둘로 나눴고, 외주 단계를 더했습니다. 하지만 그건 고객사의 공정을 알아 가던 설계 단계의 일이었고, 두 번 모두 대시보드 계산과 권한, 엑셀 칸까지 함께 고쳐야 했습니다. 직원이 화면에서 했다면 오히려 사고가 났을 변경이었습니다.
그래서 드물게 일어나는 일에 며칠과 복잡도를 쓰지 않기로 했습니다. 이름은 직원이 언제든 고칠 수 있게 하고, 단계를 더하거나 빼는 일은 요청을 받아 개발로 반영합니다. 계약 문구는 고객사가 알려 준 공정 순서를 반영한 것으로 갈음하자고 서면으로 합의하는 쪽으로 정했습니다.
모든 업종에 맞는 결론은 아닙니다. 주문마다 공정이 새로 짜이는 시제품 공장이나, 고객사가 수십 곳이라 단계가 회사마다 다른 서비스라면 위의 다섯 원리를 다 갖춘 표 기반 설계가 맞습니다. 제가 기준으로 삼은 질문은 하나였습니다. 「이 목록은 누가, 얼마나 자주 바꾸는가.」 직원이 매달 바꾼다면 화면을 만들고, 몇 년에 한 번 개발자와 함께 바꾼다면 코드에 두는 편이 더 안전합니다.
함께 읽기
- 감사 로그와 변경 이력 설계거래처의 결제 조건이 「월말 마감 30일」에서 「월말 마감 60일」로 바뀌어 있습니다. 거래처 테이블에는 updatedAt 이 있어서 어제 오후 3시에 바뀌었다는 것은 압니다.
- 금액·수량 자료형 선택: 정수, numeric, 부가세 끝수 처리견적서의 품목 세 줄에 부가세를 줄마다 계산해서 더한다고 해 보겠습니다. 같은 견적을 합계에서 부가세를 한 번 계산한 화면과 나란히 놓으면 부가세가 1원 다릅니다.
- N+1 쿼리 문제와 해결 방법거래처 목록 화면에 거래처별 미수금을 한 칸 더 보여 주기로 했다고 해 보겠습니다. 코드는 자연스럽게 이렇게 나옵니다.
- 소프트 삭제(deletedAt)의 비용과 대안품목 코드 RM-001 을 잘못 만들어서 지웠다고 해 보겠습니다. 소프트 삭제라 행은 남고 deletedAt 에 시각이 찍힙니다. 이제 같은 코드로 품목을 다시 만들려고 하면 오류가 납니다.
- 복합 인덱스 칸 순서 정하는 법여러 회사가 함께 쓰는 업무 시스템에 전표 테이블이 있고, 인덱스가 이렇게 걸려 있다고 해 보겠습니다.