워크스루: 업무를 설명하면, 표준을 따르는 스키마가 나온다

갱신

아무 챗봇에나 게시판 스키마를 달라고 하면 하나 나옵니다. 문제는 어떤 스키마를 얻는 게 아니라 팀이 이미 합의한 스키마를 얻는 것입니다: 같은 약어, 같은 접미사, 같은 개념에 같은 단어 — 첫 번째 테이블과 열 번째 테이블에서 똑같이.

이 워크스루는 빈 프로젝트에서 표준을 준수하는 DDL까지 에이전트를 떠나지 않고 갑니다. 예제는 스레드형 게시판입니다 — 따라갈 만큼 작고, 대부분의 도구를 넘어뜨리는 한 가지 경우를 강제합니다: 다른 글에 답하는 글.

아래 출력은 전부 실제 실행에서 복사했습니다.

시작하기 전에

  • Node.js 22 이상, sqemo-mcp 2.2.0 이상
  • 에이전트에 MCP 서버가 등록돼 있고 npx sqemo-mcp login을 마친 상태 — AI 연동(MCP) 참고
  • 팀 표준이 있는 워크스페이스. 이 워크스루는 단어사전에 이미 Customer, Order, Number, Content, Flag 등 십여 개가 들어 있는 것을 씁니다

1. 표준 위에서 프로젝트 시작

새 ERD는 단어사전이 비어 있어 물리명이 논리명 그대로 나옵니다. 생성 시점에 프로젝트를 표준에 묶으면 단어사전·명명 규칙·도메인이 함께 옵니다:

list_workspaces
// → [{ workspaceId: "…", name: "Engineering", role: "owner",
//      glossary: { version: 4, … } }]

create_erd {
  name: "Threaded Discussion Board",
  workspaceId: "…",
  target: { server: true }
}
get_erd_overview
// → { glossaryLinked: true, dictionaryEntryCount: 28, domainCount: 15, … }

glossaryLinked: true가 핵심입니다. 이제부터 에이전트가 만드는 모든 이름은 지어내는 게 아니라 표준을 통해 풀립니다.

2. 업무 용어로 일을 설명

이제 동료에게 말하듯 에이전트에게 말합니다:

회원이 글을 올리고, 글은 다른 글에 대한 답글일 수 있고, 회원이 글에 댓글을 다는 게시판을 모델링해 줘.

에이전트는 논리명으로 upsert_entityupsert_attribute를 호출합니다. 컬럼명은 한 번도 치지 않습니다:

upsert_entity   { logicalName: "Post" }
// → { physicalName: "POST" }

upsert_attribute { logicalName: "Post Content", domain: "Content" }
// → { physicalName: "POST_CNTS" }

upsert_attribute { logicalName: "Delete Flag", domain: "Flag" }
// → { physicalName: "DELETE_YN" }

ContentCNTS, FlagYN은 에이전트의 취향이 아닙니다. 팀 단어사전의 약어이고, 팀이 모델링한 다른 모든 테이블에 적용된 것과 같은 방식으로 적용됐습니다.

도메인이 데이터 타입을 실어 나르므로 Content는 나타나는 모든 곳에서 varchar(1000)입니다 — 테이블마다 컬럼 폭을 정하는 사람은 없습니다.

3. 글에 답하는 글

자기참조 외래키는 기본키의 이름을 그대로 쓸 수 없어 역할 접두사(role prefix) 를 받습니다. 접두사는 명명 규칙이고 — 다른 모든 단어처럼 — 단어사전을 통해 풀립니다:

upsert_relationship {
  sourceEntityId: "<Post>", targetEntityId: "<Post>",
  cardinality: "1:N", relationshipType: "nonIdentifying",
  constraintName: "FK_POST_PARENT"
}
`PARENT_POST_NO` varchar(20) COMMENT 'Parent Post Number'

접두사 기본값은 Parent라 위 FK는 설정 없이 PARENT_POST_NO로 나옵니다. 표준에서 한 번 바꾸면 — 앱에서 명명(Naming) 탭 → 편집(Edit) → 자기참조 접두사(Self-reference prefix) — 연결된 모든 프로젝트의 모든 자기참조 FK가 따릅니다.

바꾸기 전에 알아 둘 두 가지:

  • 접두사를 줄이고 싶으면 먼저 단어로 등록하세요(ParentPRNT이면 PARENT_POST_NO 대신 PRNT_POST_NO).
  • 보통 컬럼명과 달리 기존 자기참조 FK는 다음 편집 때 이름이 바뀝니다. 이름이 저장된 것이 아니라 파생되기 때문입니다. 나머지는 이미 가진 이름을 유지합니다.

이 기본값이 바뀌기 전에 만든 프로젝트는 발밑에서 움직이지 않습니다. 불러올 때 미설정 접두사가 명명 규칙에 기록됩니다: 이미 자기참조 관계가 있으면 상위(그래서 그 컬럼명이 그대로 유지됨), 없으면 Parent.

4. 표준에 아직 없는 단어

실제 모델링은 아무도 등록하지 않은 단어에 부딪힙니다. Sqemo는 조용히 약어를 지어내지 않고 표시합니다:

lint_erd
// → { code: "unknown-word", severity: "warning",
//      objectName: "Delete Flag",
//      message: "'Delete Flag' contains words not registered in the word list." }

Delete가 없어서 DELETE_YN이 줄여지지 않고 나왔습니다. 고치는 방법은 이름을 하드코딩하는 게 아니라 단어를 제안하는 것입니다:

propose_dictionary_word {
  logicalWord: "Delete", physicalWord: "DELETE", abbreviation: "DEL",
  note: "Found while modelling the discussion board"
}
// → { proposalId: "…", status: "pending", baseVersion: 4 }

표준 소유자가 앱에서 승인하면 약어가 연결된 모든 프로젝트로 전파됩니다. 챗봇이 할 수 없는 부분이 이것입니다: 에이전트는 제안자이지 권한자가 아닙니다.

5. 준수 검사

check_naming은 이미 있는 물리명을 표준이 생성했을 이름과 비교합니다:

check_naming {
  logicalName: "Customer Phone Number",
  physicalName: "CUSTOMER_PHONE_NUMBER"
}
// → { generatedPhysicalName: "CUST_TEL_NO",
//      compliant: false, providedMatches: false }

CI에 넣을 검사가 이것입니다. 에이전트 없이 프로젝트 파일에서 동작합니다:

npx sqemo-mcp lint schema.erd.json   # 위반이 있으면 종료 코드 1

6. 내보내기

export_sql { dialect: "mysql" }
CREATE TABLE `POST` (
  `POST_NO`        varchar(20)   NOT NULL COMMENT 'Post Number',
  `POST_CNTS`      varchar(1000) NOT NULL COMMENT 'Post Content',
  `DELETE_YN`      char(1)       NOT NULL DEFAULT 'N' COMMENT 'Delete Flag',
  `PARENT_POST_NO` varchar(20)            COMMENT 'Parent Post Number',
  PRIMARY KEY (`POST_NO`)
) COMMENT='An article on a board; may reply to another post';

ALTER TABLE `POST` ADD CONSTRAINT `FK_POST_PARENT`
  FOREIGN KEY (`PARENT_POST_NO`) REFERENCES `POST` (`POST_NO`);

논리명이 컬럼 코멘트로 살아남아, 업무 의미가 다이어그램에서 죽지 않고 데이터베이스까지 갑니다.

함정 하나

defaultValue는 원시 SQL이며 DEFAULT 뒤에 그대로 나갑니다. 문자열 리터럴은 직접 따옴표로 감싸세요:

upsert_attribute { logicalName: "Delete Flag", defaultValue: "'N'" }  // ✅
upsert_attribute { logicalName: "Delete Flag", defaultValue: "N" }    // ❌ DEFAULT N

따옴표 없는 값은 표현식으로 취급됩니다. 0이나 CURRENT_TIMESTAMP에는 원하는 동작이고 — N에는 원하지 않는 동작입니다.

실제로 일어난 일

에이전트는 타이핑을 했습니다. 중요한 것은 아무것도 결정하지 않았습니다:

결정결정한 쪽
어떤 테이블과 관계가 존재하는가당신, 업무 용어로
약어·구분자·대소문자팀 표준
데이터 타입도메인
표준에 들어가는 새 단어표준 소유자, 승인으로

스키마를 생성하는 것과 팀이 유지할 수 있는 스키마를 키우는 것의 차이가 그것입니다. 다음은: 단어사전과 규칙의 동작은 명명 표준, 제안 목록과 역할은 공유와 협업 — 제안 목록은 2분 영상으로도 있으니, 단어가 승인되는 과정을 읽기보다 보고 싶다면 그쪽으로.