← 모듈 센터

03. 프로그램 모듈화 안내 — 어느 프로그램에서든

작성 2026-10-04 · 따르는 표준: 01_모듈_표준.md (표준 1) · 모듈 사이트: 02 이 폴더(docs/module-platform/)를 작업할 프로그램 저장소에 통째로 복사한 뒤 이 문서를 따르세요. 맨 끝 §9에 Claude에게 붙여 넣을 요청문이 작업별로 있습니다.

쉬운 요약 — 하려는 일에 따라 고르세요

하려는 일작업
이 프로그램에 있는 기능을 다른 프로그램도 쓰게 떼어 내기A. 빼내기
새 기능을 처음부터 여러 프로그램용으로 만들기B. 새로 만들기
모듈 사이트에 있는 모듈을 이 프로그램에 넣기C. 가져오기
쓰다가 모듈을 고쳤고, 그 개선을 다른 프로그램도 받게 하기D. 올리기
다른 곳에서 개선된 모듈의 새 번호를 이 프로그램에 받기E. 새 버전 받기

절대 규칙: 이 프로그램 안에 들어온 모듈 사본(node_modules, site-packages, 복사된 폴더)은 고치지 않습니다. 고칠 일이 생기면 작업 D를 따릅니다. 이 프로그램이 고치는 것은 호스트 연결 파일(*.host.ts, *_host.py)뿐입니다.


1. 준비

  1. docs/module-platform/을 이 저장소에 복사합니다.
  2. 이 프로그램의 제품 코드를 확인합니다(라이센스 서버 products.product_code). 예: nahonza-work, zerolabel-saas.
  3. 최상위에 modules.lock.json을 만듭니다. modctl이 있으면 modctl add 때 자동으로 만들어집니다.

``json { "lockVersion": 1, "product": "<제품 코드>", "modules": {}, "patches": [] } ``

  1. 이 프로그램의 형태를 적어 둡니다. 작업 방법이 달라집니다.
프로그램서버모듈 형식DB화면
나혼자워크 (hnbbos/alone_boss)Node·TypeScript·ExpressESM ("type": "module")MariaDB·PrismaReact·Vite·Tailwind
제로라벨 (hnbbos/balb)Node·ExpressCommonJS ("type": "commonjs")DB 패키지 없음(data/ 폴더에 저장)EJS(서버에서 화면 만듦)
바코드 WMS (hnbbos/isolo_barcode)Python·FastAPI—PostgreSQL·SQLAlchemy·AlembicReact·Capacitor 앱
라이센스 서버 (hnbbos/isolo_lic)Node·ExpressCommonJSMySQL/PG 겸용정적 HTML
PadCADNode·Express(확인 필요)SQLite정적 HTML
위 표는 2026-10-04에 각 저장소의 package.json·requirements.txt를 열어 확인한 값입니다. PadCAD는 이번에 열어 보지 않았습니다.
  1. 기본 모듈 확인(01 §2.5): 이 프로그램을 단독 설치로 팔 예정이면 license-guard·installer·update-agent 세 개가 반드시 들어가야 합니다. SaaS로만 운영하면 license-guard만 권장입니다. 이 프로그램 안에 자체 라이센스 확인 코드가 따로 있으면 기본 모듈로 바꾸는 것이 첫 작업입니다(작업 C).
  2. 모듈 사이트·원본 저장소가 아직 없으면, 모듈 폴더를 이 저장소의 modules-out/<id>/에 01 §5 모양으로 만들어 둡니다. 원본 저장소가 생기면 그대로 옮깁니다.

2. 무엇을 모듈로 뺄까

2.1 판단표

질문「예」면
다른 프로그램도 지금 필요하거나 6개월 안에 필요한가?모듈 후보
이 프로그램만의 업무 규칙(견적 계산, 바코드 규격 등)이 핵심인가?빼지 않음 — 프로그램에 둠
이미 두 프로그램에 비슷한 코드가 있는가?우선 대상 (갈라지기 전에 합침)
보안과 관련이 있는가(라이센스·인증·정화·서명)?우선 대상 (구멍을 한 번에 막기 위해)
화면·자기 자료(DB)가 큰가?부품이 아니라 서비스

2.2 지금 보이는 후보 (2026-10-04 기준, 제안)

후보지금 있는 곳종류이유
라이센스 보호 장치 (기본 모듈)제로라벨 lib/license-guard.js(268줄), 라이센스 서버 lib/vendor/license-guard.js(181줄) — 이미 서로 다름부품 (Node; Python판은 바코드 WMS에 붙일 때)1순위. 두 벌이 갈라졌고, 공개키·강제 여부를 환경변수로 바꿀 수 있는 구멍도 함께 있음. 나혼자워크에도 붙일 예정
설치기 (기본 모듈)나혼자워크 deploy/standalone/run.sh(원형)부품 (shell)단독 설치 제품 공통. 비밀값 생성·.env 보호 규칙이 이미 들어 있음 → 작업 A로 빼서 제품 코드만 바꿔 씀
업데이트 도우미 (기본 모듈)없음 (데스크톱용만 license-update-control/protector에 있음)서비스 (곁붙이)처음부터 모듈로 만듦(작업 B)
메신저나혼자워크 메신저 / 바코드 WMS messenger_module/서비스두 벌 → 하나를 표준으로 정하고 나머지는 끼워 넣기
저장소 추상층나혼자워크 server/src/services/storage/ (로컬·S3·WebDAV·회사별 폴더)부품 (Node)「없음」과 「물어보지 못함」의 구분 등 자료 유실 방지 규칙이 들어 있음
OAuth 2.1 인가 서버 + MCP 창구나혼자워크 server/src/services/oauth/, services/mcp/부품 (Node)다른 프로그램도 ChatGPT·Claude 같은 외부 에이전트를 붙이려면 필요
QR 서명 토큰·짧은 주소나혼자워크 qr.service.ts, shortlink.service.ts부품현장·문서·바코드 라벨에 공통
문서 변환(docservice)나혼자워크 사이드카 이미지 nahonza-docservice서비스이미 별도 컨테이너라 등록만 하면 됨
SaaS/단독 전환·회사 격리·운영 콘솔·설치 마법사나혼자워크 (설치 형태 전환 작업 후)시작 틀새 프로그램의 뼈대

3. 작업 A — 이 프로그램의 기능을 모듈로 빼내기

A1. 경계 조사 — 연결부 후보 찾기

A2. 연결부 정하기

A3. 모듈 폴더 만들기와 코드 옮기기

A4. 형식 맞추기 (스택별 주의)

A5. 검사 옮기기

A6. 원래 프로그램을 모듈 사용으로 바꾸기

A7. 문서와 첫 발행

A가 끝났다는 기준


4. 작업 B — 새 기능을 처음부터 모듈로 만들기

언제: 두 개 이상의 프로그램이 쓸 기능을 새로 만들 때입니다(예: 업데이트 도우미, 설치기).

  1. 이름표부터 씁니다(module.json). 무엇을 연결받고(adapters), 무엇을 설정받고(config), 무엇을 내주는지(provides) 먼저 약속합니다.
  2. 연결부 약속 파일(adapters.ts / adapters.py)을 씁니다.
  3. 가짜 연결부로 개발·검사합니다. 특정 프로그램 없이도 돌아야 합니다.
  4. 첫 사용처에 붙입니다(작업 C). 이때 연결부가 불편하면 발행 전에 고칩니다. 발행 후에 고치면 MAJOR입니다.
  5. 두 번째 사용처까지 붙여 본 뒤 1.0.0을 발행하는 것을 권합니다. 그 전에는 0.x로 둡니다(0.x에서는 MINOR도 깨질 수 있다고 봅니다).

5. 작업 C — 모듈 가져오기

C1. 부품

modctl add license-guard            # 발행된 최신 번호
modctl add license-guard@1.4.0      # 지정 번호

도구가 하는 일

그다음 사람이(또는 Claude가) 할 일

  1. 모듈의 INTEGRATION.md를 읽고 호스트 연결 파일의 연결부를 채웁니다.
  2. 안내서 「확인하는 법」의 항목을 이 프로그램 검사에 추가합니다.
  3. 검사를 돌리고 modctl report를 실행합니다.

C2. 서비스

  1. 소스를 가져오지 않습니다. 서비스 주소에 연결합니다.
  2. 로그인 연결: 서비스 안내서대로 OAuth 앱을 등록하거나(나혼자워크 인가 서버는 앱 자동 등록 지원), 서비스가 주는 짧은 토큰을 받는 경로를 만듭니다.
  3. 화면 끼워 넣기 — 셋 중 하나를 고릅니다.
  4. iframe: <iframe src="https://<서비스 주소>/embed/…"> — 반드시 이 프로그램과 다른 주소(01 §10-9)
  5. 스크립트 위젯: <script src="https://<서비스 주소>/widget.js"> + mount(…)
  6. React 컴포넌트: @hnbbos/<id>-react (React 프로그램만)
  7. 단독 설치 묶음에 포함: 서비스의 compose.fragment.yml을 이 프로그램의 단독 설치 구성에 넣습니다. 이미지는 지문(@sha256:)으로 고정합니다.
  8. modules.lock.json에 kind: service, 번호, 이미지 지문을 기록합니다.

C3. 시작 틀 (새 프로그램을 만들 때)

modctl new saas-onprem-node ./my-new-app --var PRODUCT_CODE=my-app --var PRODUCT_NAME="새 프로그램" --var PORT=6601

6. 작업 D — 고친 것을 모듈에 올리기 (다른 프로그램도 좋아지게)

D1. 평소 절차

  1. 이 프로그램에서 문제를 재현합니다. 검사 하나로 만들면 가장 좋습니다.
  2. 원본 저장소(isolo_modules)에 가지(branch)를 만들고 모듈 원본을 고칩니다.
  3. 재현 검사를 모듈 검사에 추가합니다. 사용처 특유의 상황이면 가짜 연결부로 재현합니다.
  4. 변경 크기를 정합니다(01 §7). 연결부·설정·표가 바뀌면 MAJOR입니다.
  5. CHANGELOG.md에 새 번호 항목을 쓰고, 「사용처가 할 일」을 반드시 적습니다.
  6. modctl lint → modctl test → PR → 리뷰 → 머지 → modctl bump → 태그 push.
  7. CI가 모듈 사이트에 「검사 중」으로 등록합니다. 각 사용처의 검사 결과가 모입니다(3단계 전까지는 각 프로그램에서 수동으로 modctl check-report).
  8. 모든 사용처가 통과하면 관리자가 발행합니다.
  9. 이 프로그램은 작업 E로 새 번호를 받습니다.

D2. 이 프로그램에만 필요한 변경이라면

D3. 긴급(보안·장애)일 때 — 임시 덧댐

  1. 원본 저장소에 이슈를 먼저 엽니다.
  2. 이 프로그램에서만 임시로 고칩니다. Node는 patch-package, Python은 연결부 쪽 우회를 씁니다.
  3. modules.lock.json의 patches에 이유·이슈 주소·제거 기한을 적습니다. 적지 않은 덧댐은 금지입니다.
  4. 같은 날 D1을 시작해 PATCH 번호를 냅니다. 받으면 덧댐을 지우고 patches에서도 뺍니다.
  5. 기한이 지나면 modctl outdated와 모듈 사이트가 경고합니다.

7. 작업 E — 새 버전 받기

modctl outdated                       # 무엇이 뒤처졌나 (patch / minor / major, 회수 경고)
modctl update license-guard           # 같은 MAJOR 안의 최신으로
modctl update license-guard --to 2.0.0 --yes-major
  1. update가 출력하는 「사용처가 할 일」을 먼저 처리합니다(새 설정 칸, 연결부 변경 등).
  2. 이 프로그램 검사를 전부 돌립니다. 결과 줄이 없으면 실패로 봅니다.
  3. 커밋합니다. 메시지에 모듈과 번호를 적습니다. 예: chore(modules): license-guard 1.4.0 → 1.5.0
  4. modctl report를 실행합니다(CI가 하면 생략).
  5. 회수된 번호를 쓰고 있다고 나오면 최우선으로 처리합니다. 다음 고침 번호로 올리거나 직전 번호로 내립니다.

고객 서버(단독 설치)에는 모듈을 하나씩 보내지 않습니다. 이 프로그램의 새 버전을 만들 때 모듈 사이트에서 묶음(프로그램 이미지 지문 + 모듈 번호)을 만들어 서명하고, 업데이트 도우미가 그 묶음을 받아 갑니다(02 §8).


8. 프로그램별 출발점 (2026-10-04 기준 제안)

나혼자워크

  1. 라이센스 보호 장치를 처음부터 모듈로 붙입니다(작업 B, 제로라벨·라이센스 서버판을 합쳐 구멍을 막은 1.0).
  2. 이미 고친 것: 다른 제품 키 거절(PRODUCT_CODE = 'nahonza-work' 코드 고정), 플랫폼 라이센스는 운영 콘솔만 관리. 이 두 가지는 모듈에서도 유지합니다.
  3. 설치 형태 전환(EDITION=saas|onprem) 작업을 끝낸 뒤 그것을 시작 틀의 원본으로 정리합니다.
  4. 저장소 추상층, OAuth·MCP는 두 번째 사용처가 생길 때 작업 A로 뺍니다.

제로라벨

  1. lib/license-guard.js를 모듈 license-guard로 교체합니다(작업 C). CommonJS로 가져옵니다.
  2. 환경변수로 공개키·강제 여부를 받던 부분은 모듈에서 없어지므로, 운영 환경변수 정리가 필요한지 확인합니다.
  3. 화면이 EJS이므로 메신저 등 화면 모듈은 스크립트 위젯이나 iframe으로 붙입니다.

바코드 WMS

  1. messenger_module/은 이미 연결부 3개(UserProvider/FileStorage/Notifier) + 끼워 넣기 위젯 + MANIFEST.txt(sha256)를 갖췄습니다. 이것을 표준 모듈로 바꾸는 작업은 다음과 같습니다.
  2. module.json 추가: 번호는 위젯 번호 1.1.0을 이어받는 것을 권합니다.
  3. 원본 위치를 정합니다(이 저장소 그대로 둘지, isolo_modules로 옮길지).
  4. export_messenger_module.sh의 복사 방식을 「발행」으로 바꿉니다.
  5. 표준 메신저를 이것으로 정하면 서비스 모듈(own-db, PostgreSQL)로 등록합니다.

라이센스 서버

  1. lib/vendor/license-guard.js도 모듈 license-guard로 교체합니다. 서명하는 쪽이라 정규화(canonical)가 바이트 단위로 같아야 하므로, 약속 검사를 먼저 통과시킵니다.
  2. 모듈 사이트(02)를 이 저장소에 만듭니다.

9. Claude 요청문 (복사해서 붙여 넣기)

모든 요청문 앞에: 「docs/module-platform/ 의 README, 01, 03 을 먼저 끝까지 읽어.」

A. 기능 빼내기

이 프로그램의 「<기능 이름>」을 모듈로 빼 줘.
docs/module-platform/01_모듈_표준.md 와 03_프로그램_모듈화.md 「작업 A」를 그대로 따라.

[순서]
1. A1 경계 조사 결과를 먼저 보여 줘: 기능 파일 목록 / 바깥 import 목록 / 읽는 환경변수(보안 판단 여부 표시) /
   만드는 DB 표·주소·화면 / 내가 제안하는 연결부(01 §6 표준 이름 우선).
   → 내가 확인하기 전에는 코드를 옮기지 마.
2. 확인 후 A2~A7. 원본 저장소가 없으면 modules-out/<id>/ 에 만들어.

[지킬 것]
- 모듈 안에서 이 프로그램 경로를 import 하지 마. 필요한 건 연결부로 받아.
- 보안 판단(공개키·강제 여부·제품 코드·권한)은 환경변수로 바꿀 수 없게 해.
- 연결부는 「없음(null)」과 「못 물어봄(예외)」을 구분해.
- 원래 프로그램의 검사가 줄지 않고 전부 통과해야 한다. 새로 빌드해서 돌리고, 도는 코드의 모듈 번호를 출력해 대조해.
- 끝나면 보고: 연결부 목록, 옮긴 파일, 모듈 검사·원래 검사 결과(통과/실패 수), A 완료 기준 체크, 남은 일.

B. 새 기능을 모듈로

「<기능 설명>」을 처음부터 모듈로 만들어 줘(03 「작업 B」).
쓸 프로그램: <프로그램 목록>. 종류는 01 §2.4 판단 순서대로 정하고 이유를 말해 줘.
1. module.json 과 연결부 약속(adapters)을 먼저 보여 주고 내 확인을 받아.
2. 가짜 연결부로 개발·검사한 뒤 첫 사용처(<프로그램>)에 붙여.
3. 두 번째 사용처에 붙이기 전까지 번호는 0.x 로 둬.

C. 가져오기

이 프로그램에 모듈 「<id>」(번호 <버전 또는 최신>)를 붙여 줘(03 「작업 C」).
- 모듈의 INTEGRATION.md 를 끝까지 읽고, 호스트 연결 파일만 이 프로그램에 맞게 작성해. 모듈 사본은 고치지 마.
- 버전은 정확한 번호로 고정하고 modules.lock.json 에 기록해.
- 안내서 「확인하는 법」 항목을 이 프로그램 검사에 추가하고 전부 돌려 결과를 보고해.
- 서비스 모듈이면 iframe 주소가 이 프로그램과 다른 원본인지 확인하고, 단독 설치 구성에 compose 조각을 지문(@sha256)으로 넣어.

D. 고친 것 올리기

모듈 「<id>」에서 이 문제를 찾았어: <증상>. 03 「작업 D」대로 처리해 줘.
1. 이 프로그램에서 재현하는 검사를 먼저 만들어.
2. 이 프로그램에만 필요한 변경인지(D2) 모두에게 필요한 고침인지(D1) 판단하고 이유를 말해 줘.
3. D1 이면 원본 저장소(isolo_modules, 없으면 modules-out/)에서 고치고, 변경 크기(patch/minor/major)와
   CHANGELOG 「사용처가 할 일」까지 써. 이 프로그램의 모듈 사본은 고치지 마.
4. 긴급이라 임시 덧댐이 필요하면 D3 대로 modules.lock.json patches 에 이유·이슈·기한을 적어.

E. 새 버전 받기

modctl outdated 결과를 보여 주고, <id> 를 <번호> 로 올려 줘(03 「작업 E」).
- 그사이 CHANGELOG 「사용처가 할 일」을 먼저 모아서 보여 주고 처리해.
- MAJOR 면 바꿔야 할 곳 목록을 먼저 보여 주고 내 확인을 받아.
- 검사를 전부 돌려 결과를 보고하고, 커밋 메시지에 모듈과 번호를 적어.

10. 자주 막히는 곳

상황해결
모듈은 Node인데 이 프로그램은 Python그 모듈이 부품이면 Python판을 만들고 약속 검사로 같은 답을 내는지 확인합니다(01 §11.2). 크면 서비스로 돌리고 연결합니다
DB가 다름 (MariaDB / PostgreSQL / SQLite)서비스는 자기 DB를 가집니다(own-db). 부품이 표를 가져야 하면 .sql·.pg.sql을 둘 다 두고, SQLite가 필요하면 그 사용처가 연결부로 저장을 맡습니다
이 프로그램은 회사(테넌트) 구분이 없음TenantResolver가 상수(예: 'default')를 돌려줍니다
화면이 EJS·정적 HTML이라 React 모듈을 못 씀스크립트 위젯이나 iframe으로 붙입니다
실시간(SSE) 모듈인데 서버가 여러 대이벤트 전달용 연결부(Redis 발행·구독 또는 PostgreSQL NOTIFY)를 사용처가 채웁니다. 나혼자워크는 Redis를 이미 씁니다
모듈이 사용처의 사용자 표를 직접 읽어야 할 것 같음직접 읽지 않고 UserProvider 연결부로 받습니다
「이 프로그램에서만 다르게」가 필요모듈을 분기하지 말고 연결부나 선택 설정 칸으로 해결합니다(D2)
검사는 통과했는데 실제로는 옛 코드였음새로 빌드하고, 검사 시작 시 지금 도는 모듈 번호를 출력해 대조합니다(01 §11.3)