Saige Vision V2

Saige
React Electron TypeScript Xstate Vite Emotion Storybook

SaigeVision의 후속 제품 개발 프로젝트에 참여하여, 검사 자동화를 위한 올인원 솔루션을 개발했습니다.

  • 아키텍처 개선: 기존 제품의 데이터셋과 프로젝트 간 의존성을 제거하여, 각 부분을 독립적으로 관리할 수 있도록 만들었습니다.
  • 사용자 경험 향상: 라벨링 편의 기능을 강화하여 데이터 구축에 들어가는 리소스를 줄이는 데 기여했습니다.
  • 확장성 설계: Developer 에디션부터 Enterprise 에디션까지 확장할 수 있는 유연한 구조를 고민했습니다. 특히 딥러닝 기반 프로젝트에서도 핵심 기능을 재사용할 수 있도록 라벨링 기능을 모듈로 만들어 개발했습니다.
  • 상태 관리 최적화: 7개의 주요 모듈로 이루어진 복잡한 사용자 흐름을 안정적으로 관리하기 위해, State Management 설계에도 많은 고민과 노력을 담았습니다.

회사 여러 제품군에 일관된 UI/UX를 제공하고 개발 생산성을 높이기 위해 디자인 시스템을 만들었습니다. 이를 통해 중복 개발을 최대한 줄이고 유지보수 비용을 절감할 수 있었으며, 디자이너와 개발자 사이에 편하게 의견을 주고받는 피드백 창구를 만들어 프로젝트 효율을 높였습니다.

  • 일관된 UI/UX: 다양한 제품군에 일관된 UI/UX를 적용하여 중복 개발을 최대한 줄이고 유지보수 비용을 절감할 수 있도록 했습니다.
  • 효율적인 소통: 디자이너와 개발자 사이에 편하게 의견을 주고받는 피드백 창구를 만들어 프로젝트 효율을 높였습니다.
  • 브랜드 정체성: 개발 속도와 안정성을 높이기 위해 MUI를 한번 감싸서 사용했고, style override로 회사 브랜드 정체성을 담은 컴포넌트를 만들었습니다.
  • 체계적인 설계: Atomic Design 방법론을 도입하여, 가장 작은 단위인 Atom 레벨부터 디자인 토큰을 정하고 이를 상위 컴포넌트까지 일관되게 적용했습니다. 덕분에 컴포넌트를 재사용하고 유지보수하기 훨씬 편하게 만들 수 있었습니다.

개발 속도와 안정성을 높이기 위해 MUI를 한번 감싸서 사용했으며, style override로 회사 브랜드 정체성을 담은 컴포넌트를 만들었습니다. Atomic Design 방법론을 사용하여 atoms, molecules, organisms 단위로 체계적인 구조를 설계했습니다.

가장 작은 단위인 Atoms 단계에서 디자인 토큰을 정하고 상위 컴포넌트까지 일관되게 적용하여, 컴포넌트를 재사용하고 유지보수하기 훨씬 편하게 만들었습니다.

제품 배포 자동화 파이프라인을 구축하여 개발자 경험(DX)을 향상시켰습니다. 수동으로 진행하던 과정들을 자동화하여 반복적인 작업을 줄이고 실수를 줄였습니다.

덕분에 배포 시간을 30분 이상 단축했고, 특정 인원만 수행하던 배포 작업을 표준화하여 팀원 누구나 쉽게 배포할 수 있도록 개선했습니다. 이를 통해 개발 생산성을 높이고, 신규 팀원이 OJT 문서를 보며 코드 레벨 문서까지 정리하는 데 도움을 주어 배포 부담을 줄이고 핵심 기능 개발에 더 집중할 수 있게 되었습니다.

디자인 시스템

1. 디자인시스템의 시작: 왜 만들어야 했는가?

  • UI/UX 파편화: 여러 제품군에 일관되지 않은 사용자 경험이 문제였습니다. 이로 인해 사용자의 혼란과 브랜드 이미지가 통일되지 않는다는 피드백을 받았습니다.
  • 개발 생산성 저하: 비슷한 컴포넌트를 프로젝트마다 중복 개발하며 발생하는 비효율을 해결하고자 했으며, 이는 유지보수 비용과 큰 관련이 있었습니다.
  • 디자인-개발 협업의 어려움: 디자이너의 의도와 실제 개발 결과물 사이의 간극을 줄이고, 명확한 기준 없이 구두로 소통하는 커뮤니케이션 비용이 비효율적으로 사용되었습니다.

2. 구축의 효율성: 시간의 제약

새로운 제품과 병행하여 만들어야 하는 상황이었습니다. 그에 따라 모든 컴포넌트를 직접 만드는 데에는 일정상 무리가 있다고 생각했습니다. 그에 따라 이미 만들어진 MUI, AntD 등의 UI 라이브러리를 점검했고, 제품에서 가장 많이 사용하게 되는 DataGrid의 지원 범위를 감안 해 MUI + MUI X(유료 버전)을 채택하게 되었습니다. 하지만, 이 선택에 후회도 있었습니다. MUI는 Headless UI 등에 비해 UX를 변경하는 커스텀이 어렵다는 점이 있었습니다. 디자이너 분이 표현하고 싶은 디자인 요구사항을 들어드리기 위해선 MUI의 기존 UX를 변경해야 하는 경우가 있었고, 이에 따라 협의가 이루어져야 했습니다. 기존 MUI의 UX를 따라가지 않았을 때의 리스크(UI 깨짐, 예상치 못한 동작) 등을 실제 화면에서 동작하게끔 하여 보여드렸고, MUI의 UX 패턴을 따라가면서 UI적인 부분은 최대한 맞춰보는걸로 협의 후 진행하였습니다.

3. 문서화와 협업: Storybook, Chromatic 도입

디자이너의 인원과 개발자 인원이 많지 않은 상황에서 대부분 구두와 문서로만 이루어지는 커뮤니케이션은 불필요한 코스트가 많이 들어간다고 판단했습니다. 또한, 스타트업의 특성 상 실무를 배제하고 디자인시스템을 따로 관리하는 인원을 두기 힘들었기 때문에 다른 개발자들의 참여가 필요한 상황에 빠른 온보딩을 위해 협업을 위한 도구를 도입해야된다고 생각했습니다. 이에 따라 Storybook을 도입해 컴포넌트를 시각적으로 문서화 하고, Chromatic을 연동해 디자이너 분들이 빠르게 UI 리뷰를 하고, Visual Regression Test를 하여 제품에 어떻게 적용되는지 확인할 수 있는 시스템을 구축했습니다.

DX 개선: “어떻게 하면 개발에만 집중할 수 있을까?”

개발자의 가장 큰 미덕은 ‘게으름’이라고 생각합니다. 반복적인 작업을 줄이고, 개발 프로세스의 비효율을 제거해야만 핵심 가치인 코드 작성에 집중할 수 있기 때문입니다. 저희 팀 역시 몇 가지 해결해야 할 숙제를 안고 있었습니다.

1. 번들링 도구 개선: Vite로의 전환

프로젝트 규모가 커지면서 Webpack 기반의 개발 환경은 점차 한계를 드러냈습니다. 코드 수정 후 변경 사항을 확인하기까지 수십 초가 걸리는 콜드 스타트 시간과 느린 빌드 속도는 개발 흐름을 끊고 생산성을 저해하는 주된 요인이었습니다. 이 문제를 해결하기 위해 Vite로의 마이그레이션을 결정했습니다. Vite의 빠른 개발 서버 구동 속도와 빌드 타임 단축은 팀의 개발 경험을 극적으로 향상시켰습니다.

2. 배포 자동화: 수동 배포의 악몽에서 벗어나기

제품이 온프레미스 기반이었기 때문에 배포 과정은 매우 원시적이었습니다. 개발자가 직접 git tag를 생성하고, Webpack 빌드와 Electron 패키징을 거쳐, 보안 문제로 막힌 NAS에 수동으로 파일을 업로드해야 했습니다. 이 과정은 다음과 같은 심각한 문제를 야기했습니다.

  • Human Error: 수작업이 많아 실수가 잦았고, 배포 과정에서 문제가 생기면 원인 파악과 롤백에 많은 시간을 쏟아야 했습니다.
  • 특정 인원 의존: 배포 과정이 복잡하고 문서화가 미흡하여 저를 포함한 특정 인원에게 배포 업무가 집중되었습니다. 이는 병목 현상을 유발하고 팀의 유연성을 떨어뜨렸습니다.
  • 보안 이슈: 회사에서 외부 해킹 사고로 SFTP가 차단된 이후, NAS에 직접 파일을 업로드해야 하는 번거로움은 배포 시간을 더욱 증가시켰습니다.

처음에는 셸 스크립트로 일부 과정을 자동화했지만, 근본적인 해결책이 될 수는 없었습니다. 마침 Jenkins가 Electron 빌드와 NAS 연동을 위한 기능을 제공한다는 것을 확인하고, 이를 이용해 CI/CD 파이프라인을 구축했습니다. git tag 생성을 트리거로 빌드, 패키징, NAS 업로드, 그리고 팀즈 알림까지 이어지는 모든 과정을 자동화했습니다.

이 개선을 통해 30분 이상 걸리던 배포 시간은 3분 이내로 단축되었고, 누구나 버튼 클릭 한 번으로 배포할 수 있게 되었습니다. 개발팀은 반복적이고 소모적인 작업에서 해방되어 핵심 기능 개발에 더 많은 시간을 쏟을 수 있게 되었습니다.

State Management: 복잡성과의 싸움, XState 도입기

7개의 주요 모듈이 얽혀있는 제품의 사용자 흐름은 거미줄처럼 복잡했습니다. 저희는 Redux-Saga를 사용하고 있었지만, 다음과 같은 문제들로 인해 상태 관리의 복잡성이 통제 불가능한 수준에 이르고 있었습니다.

  • 암시적 상태: Saga의 비동기 로직은 상태의 변화를 예측하기 어렵게 만들었습니다. 특정 액션이 어떤 사이드 이펙트를 낳을지 코드 전체를 샅샅이 뒤져야 했고, 이는 잦은 버그의 원인이 되었습니다.
  • 보일러플레이트: 새로운 기능을 추가할 때마다 반복적으로 작성해야 하는 액션, 리듀서, 사가 코드는 개발 속도를 더디게 만들었습니다.

이 문제를 해결하기 위해 팀원들과 함께 RxJS, Redux-Saga 개선, 그리고 XState라는 세 가지 대안을 놓고 PoC를 진행했습니다. 그 결과, 상태를 명시적인 ‘상태 기계’로 모델링하는 XState가 복잡한 플로우를 가장 명확하고 안정적으로 관리할 수 있다는 결론에 도달했습니다.

XState 도입은 단순히 라이브러리를 바꾼 것이 아니라, 상태를 설계하고 바라보는 관점 자체를 바꾸는 계기가 되었습니다.

  • 예측 가능성 증대: 모든 상태와 전이(transition)가 명시적으로 정의되므로, 특정 상태에서 어떤 일이 일어날지 누구나 쉽게 예측할 수 있게 되었습니다.
  • 시각화를 통한 소통: XState Visualization 덕분에 복잡한 로직을 다이어그램으로 시각화하여 팀원들과 공유하고, 비즈니스 로직에 대한 오해를 줄일 수 있었습니다.
  • 안정성 향상: Actor 모델을 통해 각 상태 로직을 격리하고 동시성을 안전하게 관리함으로써 코드의 안정성과 유지보수성이 크게 향상되었습니다.

이러한 노력 덕분에 버그 발생률은 40% 감소했고, 신규 기능 추가 시간은 절반으로 단축되었습니다. 더 이상 상태 관리 문제로 골머리를 앓지 않게 된 것은 물론, 팀 전체의 개발 문화가 한 단계 성장하는 계기가 되었습니다.

Canvas 개발: 고성능 라벨링 에디터 구현기

AI 비전 솔루션의 핵심은 정확하고 빠른 데이터 라벨링에 있습니다. 기존 뷰어는 복잡한 폴리곤과 수많은 객체를 다루기엔 성능적 한계가 명확했습니다. 사용자의 라벨링 생산성을 극대화하기 위해, 대용량 이미지에서도 부드럽게 작동하고 정교한 편집 기능을 제공하는 고성능 캔버스 에디터가 필수적이었습니다.

단순히 도형을 그리는 것을 넘어, 수천 개의 폴리곤 객체를 관리하고, 확대/축소/이동 시에도 끊김 없는 사용자 경험을 제공해야 했습니다. 이는 DOM 기반의 SVG로는 해결하기 어려웠고, 저수준 캔버스 API를 직접 다루는 것은 엄청난 복잡성을 야기했습니다.

저는 이 문제를 해결하기 위해 KonvaJS기반의 DrawingEditor라는 독립된 캔버스 컴포넌트를 설계하고 개발했습니다.

  • 관심사 분리를 통한 구조 설계: 캔버스 로직이 비대해지는 것을 막기 위해, 역할을 기준으로 세 개의 모듈로 분리했습니다.

    • Core: 렌더링과 도형 객체 관리를 전담하는 엔진 역할입니다. 모든 렌더링 로직을 캡슐화하여 다른 모듈이 캔버스의 세부 구현을 몰라도 되도록 했습니다.
    • Controller: 사용자의 마우스/키보드 입력, 툴 선택 등의 상태를 제어하고, Core 모듈에 렌더링을 요청하는 역할을 합니다.
    • Util: 폴리곤 클리핑, 합집합, 교집합 같은 복잡한 기하학 연산과 데이터 형식 변환을 담당하는 순수 함수들의 집합입니다. 이 유틸들은 다른 프로젝트에서도 재사용할 수 있도록 독립적으로 설계했습니다.
  • 상태 관리 방식의 고민 (Class vs Hooks): 캔버스 에디터처럼 수많은 상태(좌표, 확대 배율, 현재 도구 등)가 긴밀하게 얽혀 있고, 라벨링의 특성상 라벨들의 Clipping, boolean operation 처리 등의 여러가지 복잡한 요구사항을 맞추기 위해서는 React의 선언적인 패러다임과 Hooks만으로는 관리하기 어렵다고 판단했습니다. 상태와 관련 로직을 명확하게 캡슐화하기 위해 Class 기반으로 핵심 모듈을 구현하여 복잡성을 제어했습니다.

이러한 구조적 접근 덕분에 복잡한 캔버스 로직을 체계적으로 관리하고, 새로운 라벨링 도구나 기능을 쉽게 추가할 수 있는 확장 가능한 아키텍처를 구축할 수 있었습니다.

향후 개선 방향: 선언적 UI와의 통합

현재는 복잡성을 제어하기 위해 Class 기반으로 구현했지만, 이는 React의 선언적 패러다임과는 다소 거리가 있습니다. 장기적으로는 이 로직을 React 컴포넌트와 useCanvas 같은 커스텀 훅으로 한 번 더 추상화하는 것을 구상하고 있습니다.

이를 통해 개발자들이 캔버스의 복잡한 내부 구현을 몰라도, 마치 <Canvas>, <Polygon> 같은 컴포넌트를 조합하듯 손쉽게 라벨링 도구를 확장하고 재사용할 수 있는 구조를 만드는 것이 최종 목표입니다. 이런 접근은 Canvas 로직의 재사용성을 극대화하고, 팀의 다른 개발자들이 더 쉽게 기능 개발에 기여할 수 있도록 도울 것입니다.