Project/ERP 프로젝트

"이거 수백 개 다 손으로 바꿔야 해요?" - 폐쇄망에서 그리드 마이그레이션 자동화하기

쉬지마 이굥진 2026. 4. 27. 20:38

필자는 ERP 시스템 유지보수 업무를 하면서 꽤 묵직한 작업을 맡게 되었다. dhtmlxGrid 라이브러리로 구성된 수백 개 화면을 SBGrid v3 라이브러리로 전면 전환하는 마이그레이션 프로젝트였다.

 

이 포스팅은 그 과정에서 반복 작업을 어떻게 자동화했는지, 그리고 폐쇄망 환경이 풀리고 나서 AI 프롬프트 배포까지 어떻게 이어졌는지에 대한 기록이다.

💡이 포스팅에 나오는 라이브러리
dhtmlxGrid — 해외 웹 그리드 UI 라이브러리. 행/열로 구성된 데이터 테이블을 화면에 렌더링하고 편집할 수 있게 해준다. 유료 라이선스 제품이다.

SBGrid v3 — 국내 기업 소프트보울에서 만든 그리드 라이브러리. dhtmlxGrid와 동일한 역할을 하지만 라이선스 비용이 훨씬 저렴했다. 이 프로젝트에서 dhtmlxGrid를 대체한 라이브러리다.

둘 다 '그리드'라는 같은 목적의 도구지만, API 구조가 완전히 달라서 단순 함수명 교체가 아닌 전면 재작성이 필요했다.
🕐 작업 시점 안내
이 작업은 2025년 4월 기준으로 진행됐습니다. 당시는 Cursor, Claude Code와 같은 AI 에이전트 도구들이 정식 출시되지 않았던 시점입니다. AI 활용이라고 하면 채팅 인터페이스에서 직접 프롬프트를 작성해 붙여넣는 방식이었고, 폐쇄망 환경에서는 불가능한 작업이었다는 것 미리 알아주세요!

 

(실무에서 진행한 작업을 다루고 있어, 실제 서버 주소·파일명·메뉴 경로 등 일부 내용은 보안상 예시로 대체했습니다. 함수명도 일부 변경했습니다.)

 

배경 ㅡ 전환이 필요했던 이유

기존 시스템은 dhtmlxGrid 기반이었는데, 유지보수를 진행하면서 라이선스 문제가 수면 위로 올라왔다.

dhtmlxGrid는 사용료가 상당한 반면, SBGrid v3는 그에 비해 훨씬 저렴했다. 수백 개 화면에 걸쳐 쓰이고 있는 라이브러리다 보니 비용 차이가 작지 않았고 결국 SBGrid v3로 전면 전환이 결정됐다.

 

두 라이브러리는 구조 자체가 완전히 다르다.

구분 dhtmlxGrid SBGrid v3
설계 방식 절차적 API 컬럼 중심 구조
데이터 접근 row/col 인덱스 기반 row/col 객체 기반
주요 이벤트 셀 편집 이벤트 (e.g. 함수명 onEditCell) 명령 기반 이벤트 (e.g. 함수명 doCommand)
컬럼 타입 지정 타입 배열 컬럼 객체 배열 (type, field, caption, ...)

 

API가 1:1로 대응되지 않고 구조 자체가 다르다 보니, 단순히 함수명만 바꾸는 수준이 아니라 화면마다 그리드 설정 전체를 새로 작성해야 했다. 비용 때문에 시작한 전환인데 작업 규모는 만만치 않았다.

 

화면이 수백 개였다. 각 팀원들이 일정 갯수의 화면을 맡아서 작업하는 방식이었는데, 처음엔 그냥 눈으로 보면서 하나씩 옮겼다.

 

문제 — 반복 작업이 병목이 되버림

작업을 시작하고 나니 패턴이 보였다.

 

모든 화면에는 공통 구조가 있었다. 그리드 초기화 → 이벤트 연결 → 데이터 조회.  이 틀은 수백 개 화면이 전부 같았다.

 

그리드 설정도 마찬가지였다. dhtmlxGrid의 컬럼 설정 배열들을 보고 SBGrid의 컬럼 배열로 변환하는 작업이 화면마다 반복됐다.

dhtmlxGrid 컬럼 설정 구조 (예시)
──────────────────────────────────────────────────────
columnIds  : ['itemCd', 'itemNm', 'useYn']
hdrLabels  : ['코드', '코드명', '사용여부']
cellType   : ['ro', 'ed', 'co']
cellWidthPX: ['100', '200', '100']
cellAlign  : ['center', 'left', 'center']
──────────────────────────────────────────────────────

↓ 이걸 SBGrid v3 columns 배열로 변환해야 함

{ field: 'itemCd', caption: '코드',    type: 'text',  editable: false, width: '100', align: 'center' }
{ field: 'itemNm', caption: '코드명',  type: 'text',  width: '200', align: 'left' }
{ field: 'useYn',  caption: '사용여부',type: 'combo', items: 배열,  width: '100', align: 'center' }

 

문제는 두 가지였다.

 

하나는 속도. 화면 하나 전환하는 데 컬럼 수가 많으면 꽤 시간이 걸렸다. 수백 개 화면을 이 속도로 하면 일정이 맞지 않았다.

 

다른 하나는 일관성. 여러 사람이 각자 방식으로 변환하다 보면 코드 스타일이 제각각이 된다. 나중에 유지보수할 때 같은 패턴인데도 코드가 다르게 생기면 혼란이 생길 수 있다.

 

1차 해결 — gridConvert 함수 설계 (폐쇄망 환경)

당시 환경이 폐쇄망이었다. 인터넷도 안 되고 (당연히) AI도 못 쓰는 상황이었다 🥲

저 안울어요

 

그래서 직접 만들기로 했다. 두 라이브러리의 API 구조를 분석해서 기존 dhtmlxGrid 객체를 받아 SBGrid v3 설정 코드를 콘솔에 출력해주는 gridConvert 함수를 설계했다.

 

아이디어는 간단했다. 기존 그리드 객체 안에는 컬럼 정보가 이미 다 담겨 있었다. 이걸 읽어서 SBGrid 설정 형태로 변환해주는 함수를 만들면 손으로 하나씩 옮기지 않아도 된다.

// dhtmlxGrid cellType → SBGrid v3 컬럼 타입 매핑
if (cellTypeArr[i] == "ed")          print += "... gStringInpCol, ";   // 편집 가능한 텍스트
if (cellTypeArr[i] == "ro")          print += "... gStringRoCol, ";    // 읽기 전용 텍스트
if (cellTypeArr[i] == "edn")         print += "... gNumInpCol, ";      // 편집 가능한 숫자
if (cellTypeArr[i] == "ron")         print += "... gNumRoCol, ";       // 읽기 전용 숫자
if (cellTypeArr[i] == "ch")          print += "... gCheckInpCol, ";    // 체크박스
if (cellTypeArr[i] == "co")          print += "... gComboInpCol, items: 배열, "; // 콤보박스
if (cellTypeArr[i] == "dhxCalendar") print += "... gCalenderInpCol, "; // 날짜
if (cellTypeArr[i] == "img")         print += "... gSearchIconCol, ";  // 검색 아이콘

dhtmlxGrid의 타입 문자열을 SBGrid의 컬럼 타입으로 대응시키는 컬럼 타입 매핑이 핵심!!

 

함수가 어느 정도 완성되고 나서 사용법도 단순하게 만들었다.

 

🪄 gridConvert 함수 사용법

  1. 브라우저 개발자도구 → 콘솔 탭 열기
  2. 그리드가 있는 프레임으로 컨텍스트 전환
  3. gridConvert(그리드이름) 입력 (e.g. gridConvert(MstGrid) 혹은 gridConvert(DtlGrid) 등)
  4. 출력된 SBGrid v3 설정 코드를 복사해서 붙여넣기
  5. 변수명, 이벤트 부분만 화면에 맞게 수정

콘솔에 gridConvert(MstGrid)를 입력하면 아래처럼 SBGrid v3 설정 코드가 그대로 출력된다.

실제 출력 화면 (초기 버전)

 

기존 dhtmlxGrid가 살아있는 화면에서 그리드 객체를 그대로 콘솔에 넘기면, 변환된 SBGrid 설정이 출력된다. 복사해서 붙여넣고 주석 처리된 container, height와 이벤트 핸들러 내용만 화면에 맞게 채워넣으면 됐다. 컬럼이 20개짜리 화면도 순식간에 뼈대가 완성된다.

 

그리드 하나를 손으로 옮기던 시간이 크게 줄었고 특히 컬럼 수가 많은 화면일수록 효과가 컸다.

초기 버전에서 피드백을 받아 최종 버전으로 개선하면서 기능도 보강했다.

gridConvert 함수 개선 내역
──────────────────────────────────────────────────────
초기 버전   기본 컬럼 매핑만 지원
           var 키워드 사용 (ES5 스타일)

최종 버전   입력값 검증 추가 (null/undefined 체크)
           dataSource.schema.fields로 데이터 타입 명시
           filters, validators, 날짜 포맷 지원
           canCommand / doCommand 이벤트 핸들러 자동 생성
           let 키워드 사용 (ES6 스타일)
──────────────────────────────────────────────────────
// 최종 버전 콘솔 출력 결과 (예시)
let gridConfig = {
    ... gInsertGridObj,
    //container: '#gridArea',
    //height: '358px',
    alternateCss: 1,
    dataSource: {
        schema: {
            fields: {
                itemCd: { dataType: 'string' },
                itemNm: { dataType: 'string' },
                useYn:  { dataType: 'string' },
            }
        }
    },
    columns: [
        { ...gStringRoCol,  field: 'itemCd', caption: '코드',    visible: true, align: 'center', width: '100', },
        { ...gStringInpCol, field: 'itemNm', caption: '코드명',  visible: true, align: 'left',   width: '200', },
        { ...gComboInpCol,  field: 'useYn',  caption: '사용여부',visible: true, align: 'center', width: '100', items: 배열, },
    ],
    canCommand: { ... },
    doCommand:  { ... },
}
그리드객체 = SBGrid3.createGrid(gridConfig);

 

gridConvert 함수와 함께, 팀 전체가 공통으로 쓸 수 있는 마이그레이션 작업 기본 원칙도 팀원들과 함께 문서화해서 배포했다.

마이그레이션 작업 순서 (팀 공유 문서로)
──────────────────────────────────────────────────────
1. 원본 파일 복사 후 파일명에 _old 붙이기 (예: sampleMenu.jsp → sampleMenu_old.jsp)
2. include 대상 파일을 신규 헤더 파일로 변경
3. 기존 JS 코드 삭제 후 _old에서 하나씩 이식
4. gridConvert 함수로 SBGrid 그리드 설정 추출
5. dhtmlx 관련 코드는 주석이 아닌 완전 삭제
6. 공통 함수 추가는 js_new.js, 스타일은 css_new.css
──────────────────────────────────────────────────────

 

여기에 더해 팀에서 공통으로 쓸 파일에 자주 쓰는 함수들도 정리해뒀다. dhtmlxGrid에서 직접 호출하던 메서드들을 SBGrid 방식으로 래핑한 함수들이다. '변경된 행 목록 조회', '선택된 행 key 반환'처럼 화면마다 반복적으로 쓰이는 패턴들을 함수로 묶어팀원 누구나 일관된 방식으로 쓸 수 있게 했다.

 

2차 해결 — 폐쇄망이 풀렸다, AI 프롬프트 배포

한참 작업이 진행되던 중에 환경이 바뀌었다. 폐쇄망 제한이 풀리면서 AI를 쓸 수 있게 됐다!!!!!!!!

 

그런데 문제가 하나 있었다. 팀원 중에는 오랜 폐쇄망 개발 환경 탓에 ChatGPT나 Claude 같은 AI를 한 번도 써본 적 없는 분들도 계셨다. AI를 쓰더라도 어떻게 프롬프트를 써야 하는지, 어떻게 활용해야 하는지 자체가 낯선 상황이었다.

 

그래서 그냥 AI를 쓸 수 있게 됐다고 끝내는 게 아니라 팀 전체가 바로 써먹을 수 있는 프롬프트를 직접 만들어서 txt 파일로 배포하기로 했다.

 

앞서 말했듯 2025년 4월 시점이었다. 지금처럼 AI가 코드베이스를 직접 읽고 자율적으로 전환해주는 환경이 아니라 채팅창에 코드를 붙여넣고 변환을 요청하는 방식이었다. (이 포스팅 쓰면서 AI 발전속도를 다시 체감하게 됨..) 그래도 gridConvert 함수가 컬럼 설정 변환을 자동화해줬다면, AI는 함수 전체의 변환 흐름을 잡아주는 것까지 가능했다.

 

프롬프트의 핵심은 두 라이브러리의 구조적 차이를 AI에게 충분히 설명해주는 것이었다. (AI가 dhtmlx 코드를 보고 SBGrid로 올바르게 변환하려면 두 라이브러리가 어떻게 다른지 맥락을 알아야 하니)

프롬프트 핵심 내용 (팀 배포용 txt 파일, 일부 발췌)
📌 기본 전제
- 기존 시스템은 dhtmlXGrid 기반, 현재는 SBGrid3 기반으로 마이그레이션 중
- dhtmlX는 절차적 API 사용
- SBGrid는 컬럼 중심 구조, 함수 기반 이벤트 시스템, row/col 객체 중심 데이터 접근

📌 핵심 구조 차이
- row 접근:  cells(row, col)   → getRowData(grid, rowKey)
- 이벤트:    onEditCell        → doCommand.updated
- 컬럼 타입: cellType 배열     → columns[].type

📌 그리드 이벤트 흐름
- canCommand → 명령 전 검사 (edit, focus 등 차단 가능)
- doCommand  → 명령 후 처리 (edit 완료 후, updated 후, loaded 후 후처리)
- edit 이벤트:   enter === true  → 편집 시작 / false → 편집 종료
- updated 이벤트: command.values[]로 변경된 필드 확인

📌 주요 변환 패턴
- onEditCell(stage==2) → doCommand.edit (enter==false)
- onCheck              → doCommand.updated (cNm == 'chk')
- getColIndexById      → 공통 함수로 래핑

//..생략

 

실제 프롬프트는 이보다 훨씬 길었는데, AI가 변환 중에 막히는 케이스들을 하나씩 만날 때마다 프롬프트에 추가하면서 점점 두꺼워졌다 😅

AI에게 "이 화면의 dhtmlx 코드를 SBGrid v3로 변환해줘" 라고 하면 어느 정도 뼈대가 나왔고, gridConvert 함수로 컬럼 설정을 맞추면 실제 작업량이 크게 줄었다.

폐쇄망 환경에서는 gridConvert 함수와 가이드 문서로, 환경이 풀린 후에는 AI 프롬프트를 더해서 두 단계로 도구를 발전시킨 셈이다.

 

결과

프로젝트 기간은 2025년 4월부터 6월까지였다. 수백 개 화면을 팀이 함께 전환했다.

 

gridConvert 함수 하나가 없었다면 컬럼 수십 개짜리 그리드를 손으로 하나씩 옮겼을 것이다. 팀원들이 각자 다른 방식으로 변환하면서 코드 스타일이 제각각이 됐을 확률도 매우 크다고 생각한다.

 

자동화 도구와 가이드 문서 덕분 새로 합류하는 팀원도 원칙에 맞게 일관된 방식으로 작업할 수 있었고, 마이그레이션 완료 후 불필요한 dhtmlx 코드 제거와 리팩토링도 깔끔하게 마무리할 수 있었다.

 

개인적으로 이 작업에서 제일 크게 배운것은 이거다. "반복되는 패턴이 보이면 자동화부터 생각하자". 도구 만드는 데 시간이 좀 들더라도 수백 번 반복될 작업이라면 결국 그게 빠르다.