Project Detail

Ypbooks

Ypbooks — Client Project

노후화된 레거시 시스템을 걷어내고 MSA + Kubernetes 기반의 현대적인 아키텍처를 도입하여 유지보수성과 확장성을 동시에 잡았습니다.

  • Legacy Monolith → Cloud Native MSA 성공적 전환
  • Kubernetes, Helm, Harbor, GitLab CI/CD 파이프라인 구축
  • 레거시 배치 → Spring Batch + Quartz 전환
  • Vue3/Vite 프론트/백오피스 개발 및 고도화

국내 대표 서점 영풍문고의 커머스 플랫폼을 Kubernetes 기반 MSA 환경으로 전면 리뉴얼했습니다. 모놀리식 레거시를 현대화하여 대규모 트래픽에도 안정적으로 운영 가능한 시스템을 완성했습니다.

BATCH

Spring Batch 전환

@Scheduled 기반으로 흩어져 있던 주문, 배송, 알림톡 등 전체 배치를 Quartz + Spring Batch 조합으로 재설계해 장애 복원력을 높였습니다.

Spring Batch, Quartz, MyBatis
OPS

Kubernetes CI/CD

GitLab CI/CD → Harbor → ArgoCD 파이프라인을 구성하고 Jenkins 자산을 단계적으로 대체했습니다.

GitLab CI/CD, Jenkins, Harbor, ArgoCD
UX

나우드림 픽업 경험

나우드림(매장 픽업) 주문 여정을 재설계하고, Vue 백오피스에서 즉시 출고/상담 처리를 지원하도록 UX를 개선했습니다.

Vue.js 3, Option Picker, Message Box
영풍문고 프론트오피스 — 메인
1 / 23

프로젝트 개요

Ypbooks는 국내 대표적인 서점 브랜드인 영풍문고의 웹사이트 및 모바일 플랫폼의 리뉴얼 프로젝트입니다. 이 프로젝트는 UI/UX를 개선하고, 백엔드 시스템을 최신화하며, 쿠버네티스 기반의 MSA 아키텍처를 도입하여 확장성과 유지보수성을 높이는 데 초점을 맞췄습니다. 특히, 기존 시스템의 복잡성을 단순화하고 성능을 최적화하는 데 중점을 두었습니다.

주요 특징

  • 현대적이고 직관적인 UI로 개편하여 사용자 경험 극대화
  • 기존의 모놀리식 구조에서 탈피하여 마이크로서비스 기반으로 전환
  • On-premise에 Kubernetes 기반의 자동화된 배포와 스케일링으로 시스템 안정성과 확장성 확보
  • 조회가 잦은 정보는 Redis로 캐싱해 사용성 극대화
  • 다양한 결제 수단을 제공하기 위한 결제 시스템(KCP, 카카오페이, 페이코 등) 통합
  • Gitlab CI/CD, Harbor, ArgoCD를 활용한 자동 배포 CI/CD 파이프라인 구성

주요 책임 및 성과

  • 도서 재고, 기프티콘, 쿠폰, 지점 관리자 등 RDB 테이블 설계
  • 11번가 기프티콘 POS 서버와 연동해 기프티콘 코드 검증 및 소진 처리 개발
  • O2O(Online to Offline) 나우드림 개발 (온라인 구매 시 기간 내 오프라인에서 도서를 직접 받아볼 수 있는 기능)
  • 국내 및 DHL 해외배송비 산정 로직 개발
  • Profile(Dev, Prod) 환경에 따른 KCP, 카카오페이 주문 결제 로직 분리 및 버그 픽스
  • 마이페이지 (배송지 관리 및 개인 정보) 개발
  • 주간/월간 베스트셀러 개발
  • 외부 검색엔진 업체(와이즈넛)와 협력하여 검색 기능 개발
  • 주문/결제 건 조회 및 주문 취소 개발
  • 기프티콘, 나우드림 어드민 통계 개발
  • @Scheduled 로 작성돼 있던 배치 프로세스를 Spring Quartz + Spring Batch 환경으로 전환하여 물리적으로 실행 환경이 분리된 MSA 환경에서 원자 단위로 배치가 실행되도록 수정
  • 기존의 회원, 상품, 이미지, 댓글, 베스트셀러 등의 데이터를 신규 데이터베이스로 이관 배치 개발
  • 기존의 Jenkins로 CI 하고 수동으로 서버에 접속해 웹 애플리케이션을 실행 하던 개발 환경에서 Gitlab CI + Harbor + ArgoCD 로 영풍문고의 On-Premise 각 물리서버들에 자동 배포되는 환경으로 전환하는데 참여
  • 쿠버네티스에서 모듈화된 프론트엔드 화면이 제공될 수 있도록 환경별 .env 작성 및 Vite 라우팅 설정 정보 수정
  • 모든 백엔드 마이크로서비스 애플리케이션 도커 파일 작성
  • 쿠버네티스에서 모듈화된 백엔드 마이크로서비스가 제공될 수 있도록 헬름 차트 수정
  • 상품, 베스트셀러 등 쿼리 속도 개선
  • 각종 버그 픽스 (입사 당시 BTS에서 우선순위가 낮은 Minor를 제외하고 Critical, Major 이슈 약 1,600개)

기술 스택

  • Backend: Java, Spring Boot, MyBatis
  • Frontend: TypeScript, Vue, Vite
  • Database: MySQL, Redis
  • DevOps: Kubernetes, Jenkins, Gitlab CI/CD, Harbor, ArgoCD
  • Monitoring: Grafana, Loki

담당 역할

프론트엔드와 백엔드 개발 모두를 담당하였고, 쿠버네티스를 활용한 배포 환경을 구축하고, Jenkins와 Gitlab CI/CD 파이프라인을 구성하였습니다. 기프티콘 검증 및 소진 처리, 나우드림, 베스트 셀러 등 프론트오피스/백오피스 전반에 걸쳐 개발했으며, 단순 기능 개발이 아닌 사용자의 사용성이 어떨지 항상 고민하며 구현했습니다.

배운 점

기존의 모든 배치 프로세스와 스케줄링을 Spring Quartz + Spring Batch 환경으로 전환

대표님께서 영풍문고 프로젝트 초기에 Spring Batch + Spring Quartz를 적용할 것을 기존의 개발자분들에게 지시하셨으나, 잘 진행되지 못했고, 결국 POJO로 배치 로직을 작성하고, 메서드 레벨에 @Scheduled 애너테이션을 부착해 지정된 시간에 해당 메서드가 실행되는 수준으로만 구현돼 있었습니다.
따라서 POJO 배치 로직에서 Spring Batch로의 전환이 필요했기에, BTS 버그를 수정하며 남는 시간에 Spring Batch 공부도 함께 병행했습니다. Spring Batch의 경우 배치 프로세스​만을 위한 도구임을 알게 되었고, 이를 지정된 시간에 실행하려면 스케줄링 도구가 필요하다는 것을 깨달았습니다. 스케줄링 도구로 Jenkins와 Spring Quartz 두 가지가 주로 사용되는 것을 알았고, 특히 Jenkins가 많이 사용되기도 하고, 배치 상태를 추적하기에도 조금 더 원활해보여 이를 적용하려 했으나, 대표님의 지시로 Spring Quartz를 적용하기로 했습니다.
따라서 각 배치 로직을 작성하기에 앞서 Quartz 로직도 작성해야 합니다. 다음의 두 Bean을 사용해 Quartz Trigger와 Batch Job을 정의합니다.
  • CronTriggerFactoryBeanBuilder: Quartz 스케줄을 정의합니다.
  • JobDetailFactoryBeanBuilder: Batch Job을 정의합니다.
다음은 주문완료 건에 대해 상품 출고 처리를 요청하는 배치 로직 중 Quartz + Batch 설정 부분 입니다.
WMS: 물류 서비스
1@Value("${quartz.schedule.cron.order.completed-order-to-wms}") 2private String scheduleCronTime; // 예시: 0/30 * * * * 3 4// [A] 5@Bean 6public CronTriggerFactoryBean sendCompletedOrderToWmsJobTrigger() { 7 return ScheduleHelper.cronTriggerFactoryBeanBuilder() 8 .name("sendCompletedOrderToWmsJobTrigger") 9 .cronExpression(scheduleCronTime) 10 .jobDetailFactoryBean(sendCompletedOrderToWmsJobSchedule()) 11 .build(); 12} 13 14// [B] 15@Bean 16public JobDetailFactoryBean sendCompletedOrderToWmsJobSchedule() { 17 return ScheduleHelper.jobDetailFactoryBeanBuilder() 18 .job(sendCompletedOrderToWmsJob()) 19 .build(); 20} 21 22// [C] 23@Bean 24public Job sendCompletedOrderToWmsJob() { 25 return jobBuilderFactory.get(JOB_NAME) 26 .start(sendCompletedOrderToWmsStep()) 27 .build(); 28} 29 30// [D] 31private Step sendCompletedOrderToWmsStep() { 32 return stepBuilderFactory.get(STEP_NAME) 33 .<OrderDetailVo, OrderDetailVo>chunk(CHUNK_SIZE) 34 .reader(sendCompletedOrderToWmsItemReader()) 35 .writer(sendCompletedOrderToWmsWriter()) 36 .build(); 37}
  • [A]: Spring Quartz의 CronTriggerFactoryBean을 생성합니다. 이는 Cron 시간 트리거이며, 정의된 Cron 시간에 Trigger 되도록 하는 정의 입니다.
  • [B]: Spring Batch의 JobDetailFactoryBean을 생성합니다. 이는 Batch Job을 포함하는 팩토리 빈 입니다.
  • [C], [D]: Spring Batch의 Job과 Step을 생성합니다. 이는 JobDetail에 포함될 정보들입니다.
다음은 reader, writer 정의입니다.
1@Bean 2@StepScope 3public SynchronizedItemStreamReader<OrderDetailVo> sendCompletedOrderToWmsItemReader() { 4 MyBatisCursorItemReader<OrderDetailVo> itemReader = new MyBatisCursorItemReaderBuilder<OrderDetailVo>() 5 .sqlSessionFactory(sqlSessionFactory) 6 .queryId(getMapperPath("OrderStatusMapper.selectOrderListForWmsApi")) 7 .build(); 8 9 return new SynchronizedItemStreamReaderBuilder<OrderDetailVo>() 10 .delegate(itemReader) 11 .build(); 12} 13 14private ItemWriter<OrderDetailVo> sendCompletedOrderToWmsWriter() { 15 return items -> items.forEach(orderDetailVo -> { 16 log.info("[도서상품 출고 요청] 주문완료 건 WMS에 전송 - orderNo: {}, orderStatusCd: {}", orderDetailVo.getOrderNo(), orderDetailVo.getOrderStatusCd()); 17 orderManageService.sendOrderToDelivery(orderDetailVo, orderDetailVo.getOrderProducts(), WmsOrderType.ORDER); 18 }); 19}
MyBatis를 사용해서 데이터를 조회하는 SynchronizedItemStreamReader는 MyBatisCursorItemReader의 Wrapper 구현체로, 멀티스레드 환경에서 데이터 동기화를 보장해줍니다. 주문서 데이터의 정합성을 최대한 신회할 수 있는 상태로 유지하면서 배치를 수행할 수 있도록 사용했습니다. Wrapper로 정합성을 유지하고, delegate 메서드를 통해 ItemReader를 호출합니다. 주문을 완료한 주문서 데이터를 불러와 ItemWriter에서 주문서 상태를 변경하고 WMS에 출고 요청을 보냅니다. 이 일련의 작업은 Chunk based 로, Step을 Chunk 단위로 트랜잭션을 적용해 수행합니다.
이러한 방식으로 기존 영풍문고의 @Scheduled + POJO로 작성돼 있던 모든 배치 로직을 Spring Quartz + Spring Batch 환경으로 성공적으로 전환했고, 정상 작동 확인했습니다.

MyBatis의 활용

기존에는 JPA를 사용해봤으나, MyBatis를 활용한 쿼리 로직 작성은 처음 경험해 봤습니다. 사실 ORM이 제일 편하고 좋은 줄 알았으나, SQL Mapper를 직접 사용해보니 의외로 편리함과 자율성을 느꼈습니다. MyBatis의 가장 좋은 점은 개발자가 작성한 SQL 쿼리를 직접 실행하기 때문에 쿼리 제어권이 JPA보다 훨씬 강력하다는 것입니다. 따라서 복잡한 SQL 작성이 용이하고, 데이터베이스에 종속적인 기능(DBMS 함수나 쿼리 힌트 등)도 사용이 가능했고, 그러다보니 쿼리 최적화에도 JPA에 비해 탁월했던 것 같습니다. JPA로 복잡한 SQL을 구현할 때에는 조금 애를 먹었던 기억이 있는데, MyBatis에서는 DSL(Domain Specific Language)에 크게 구애받지 않고 쉽게 구현할 수 있어 좋았습니다. 그리고 JPA의 N+1 문제 등을 크게 고려하지 않고 필요한 테이블에 대한 JOIN이 가능했기에 관계에 대한 부담도 적었습니다. JPA의 경우 복잡한 관계로 엮인 데이터를 처리하려면 엔터티 매핑과 Fetch 전략에 의존해야 했던 것에 비하면 천국이였습니다. 쿼리 중 일부는 다음과 같습니다.
1<select id="findAll" resultType="kr.co.ypbooks.base.admin.model.o2o.NowdreamOrderManagementResponseDto"> 2 SELECT 3 os.company_cd, 4 os.order_no, 5 os.order_id, 6 CASE 7 WHEN os.order_status_cd = 1000 THEN '입금대기' 8 WHEN os.order_status_cd = 1010 THEN '주문완료' 9 WHEN os.order_status_cd = 1061 THEN '전체취소' 10 WHEN os.order_status_cd = 1040 THEN '출고' 11 WHEN os.order_status_cd = 1071 THEN '전체반품' 12 END AS order_status_cd, 13 DATE_FORMAT(os.order_date,'%Y-%m-%d %H:%i') AS order_date, 14 JSON_VALUE(os.orderer_info, '$.ordererName') AS orderer_name, 15 JSON_VALUE(os.orderer_info, '$.ordererEmail') AS orderer_email, 16 os.member_cd, 17 os.zbcnr, 18 CASE 19 WHEN os.member_flag = 1 THEN '회원' 20 ELSE '비회원' 21 END AS member_flag, 22 CASE 23 WHEN os.payment_method_cd = 900 THEN 'O' -- 무통장입금 24 WHEN os.payment_method_cd = 999 THEN 'R' -- 적립/예치 25 WHEN os.payment_method_cd = 100 THEN 'K' -- 신용카드 26 WHEN os.payment_method_cd = 101 THEN 'A' -- 실시간계좌이체 27 WHEN os.payment_method_cd = 102 THEN 'M' -- 휴대폰 소액결제 28 WHEN os.payment_method_cd = 110 THEN 'J' -- 문화/도서/해피머니 상품권 29 WHEN os.payment_method_cd = 300 THEN 'P' -- PAYCO 30 WHEN os.payment_method_cd = 200 THEN 'Q' -- KAKAO 31 WHEN os.payment_method_cd = 400 THEN 'Z' -- TOSS 32 END AS payment_method_cd, 33 os.total_quantity, 34 JSON_VALUE(os.payment_info , '$.totalProductSalesPrice') AS order_amount, 35 CASE 36 WHEN JSON_VALUE(os.branch_pickup_info, '$.pickupTypeCd') = 1 THEN '1시간이후' 37 ELSE '직접수령' 38 END AS receiving_method_cd, 39 IFNULL(os.branch_pickup_yn, 'N') AS branch_pickup_yn, 40 IFNULL(os.branch_pickup_admin_yn, 'N') AS branch_pickup_admin_yn, 41 (SELECT COUNT(ORDER_CONSULT.consult_no) FROM ORDER_CONSULT WHERE order_no = os.order_no) AS order_consult_cnt 42 FROM ORDER_STATUS AS os 43 WHERE id >= #{pageable.offset} 44 <include refid="whereBySearchDto" /> 45 ORDER BY os.reg_date DESC, os.upd_date DESC 46 LIMIT #{pageable.pageSize} 47</select> 48 49<sql id="whereBySearchDto"> 50 51 ... 생략 ... 52 53 <!-- 주문 상태 --> 54 <if test="searchDto.orderStatusCd != null and searchDto.orderStatusCd.name() != 'ALL'"> 55 AND os.order_status_cd = #{searchDto.orderStatusCd.code} 56 </if> 57 <!-- 수령 여부 --> 58 <if test="searchDto.branchPickupYn != null and searchDto.branchPickupYn != ''"> 59 AND os.branch_pickup_yn = #{searchDto.branchPickupYn} 60 </if> 61 62 ... 생략 ... 63 64</sql>
include로 whereBySearchDto라는 이름의 SQL을 WHERE 절에 불러와 적용하거나, 내부적으로 if 분기 또한 가능해 복잡한 SQL 쿼리 구성에 탁월한 개발 경험을 제공합니다.

Gitlab CI/CD → Harbor → ArgoCD → Kubernetes Cluster 배포 경험

인프라가 Kubernetes 환경으로 이전되기 전에는 Jenkins 어드민 페이지에 들어가 각 마이크로서비스를 배포하는 형태였습니다. UI에서 각 마이크로서비스의 배포 및 CI 버튼을 누르면 Gitlab의 develop 브랜치 코드를 기반으로 빌드하고 각 물리 서버로 JAR 파일이 전송되고, 물리 서버에 SSH로 접속해 서비스를 shell script로 실행하는 형태입니다.
영풍문고 측에서 인프라를 쿠버네티스 환경으로 전환해 달라는 요청이 있었고, 이를 달성하기 위해 다같이 일주일 정도 날을 잡아 CI/CD 구성에 전념했습니다. 각 마이크로서비스 이미지를 만들 Docker 파일을 정의하고, Gitlab CI/CD에서 배포하고자 하는 커밋을 대상으로 실행 버튼을 누르면 빌드 후 생성한 이미지를 Harbor로 업로드합니다. 다음은 QA 프로파일 배포 CI 스크립트 내용 중 일부입니다.
1// [A] 2back_order_build_jar: 3 extends: .common_build_template 4 variables: 5 SERVICE_NAME: "back_order" 6 7// [B] 8.common_build_template: 9 variables: 10 SERVICE_NAME: "common build" 11 when: manual 12 stage: build_jar 13 image: openjdk:17-jdk 14 only: 15 - develop 16 script: 17 - ./gradlew --stacktrace back:${SERVICE_NAME}:clean -Pprofile=qa back:${SERVICE_NAME}:build; 18 artifacts: 19 paths: 20 - back/${SERVICE_NAME}/**/*.jar 21 22// [C] 23back_order_deploy_harbor: 24 extends: .common_deploy_harbor_template 25 variables: 26 SERVICE_NAME: "back_order" 27 HARBOR_REPOSITORY_NAME: "ypbooks/back_order" 28 needs: 29 - back_order_build_jar 30 31// [D] 32.common_deploy_harbor_template: 33 variables: 34 SERVICE_NAME: "common deploy harbor" 35 HARBOR_REPOSITORY_NAME: "ypbooks" 36 stage: deploy_harbor 37 image: 38 name: gcr.io/kaniko-project/executor:debug 39 entrypoint: [ "" ] 40 41 before_script: 42 - mkdir -p /kaniko/.docker 43 - echo "{\"auths\":{\"$HARBOR_URL\":{\"username\":\"$HARBOR_USER\",\"password\":\"$HARBOR_PASSWORD\"}}}" > /kaniko/.docker/config.json 44 45 script: 46 - | 47 /kaniko/executor \ 48 --context $CI_PROJECT_DIR \ 49 --dockerfile $CI_PROJECT_DIR/back/${SERVICE_NAME}/Dockerfile \ 50 --destination $HARBOR_URL/${HARBOR_REPOSITORY_NAME}:$CI_COMMIT_SHORT_SHA \ 51 --skip-tls-verify 52 only: 53 - develop
CI/CD 대상 마이크로서비스 중 주문(Order) 서비스의 배포 예시입니다.
  • [A]: 주문 서비스 빌드 정의입니다.
  • [B]: 각 마이크로서비스에서 공통적으로 사용되는 빌드 구성 정의입니다. QA 프로파일로 빌드해 JAR 파일을 생성합니다.
  • [C]: 주문 서비스 도커 이미지를 Harbor로 업로드하는 정의 입니다.
  • [D]: 각 마이크로서비스에서 공통적으로 사용되는 도커 이미지 생성 및 Harbor로 업로드하는 정의입니다. 도커 이미지 생성에는 Kaniko를 사용합니다.
다음은 주문 서비스의 QA 프로파일 배포 Helm 차트 내용 중 일부입니다.
1// [A] 2replicaCount: 3 3 4// [B] 5image: 6 repository: harbor.ypbooks.co.kr/ypbooks/qa_back_order 7 pullPolicy: Always 8 9// [C] 10ingress: 11 enabled: true 12 className: "nginx" 13 annotations: {} 14 hosts: 15 - host: qa.ypbooks.co.kr 16 paths: 17 - path: /back_order 18 pathType: Prefix 19 - host: m-qa.ypbooks.co.kr 20 paths: 21 - path: /back_order 22 pathType: Prefix
  • [A]: 생성할 Pod의 개수 정의 입니다. QA의 경우 테스터 분들께서 큰 불편함 없이 테스트 할 수 있는 환경을 구성하기 위해 기본 3개로 설정했습니다.
  • [B]: 주문 서비스 도커 이미지가 업로드 된 Harbor 저장소 경로입니다.
  • [C]: Ingress 리소스를 생성하여 외부 요청을 클러스터 내부 서비스로 라우팅하도록 합니다. Ingress 리소스로 Nginx Ingress Controller를 사용합니다. 호스트는 qa.ypbooks.co.kr와 m-qa.ypbooks.co.kr로 설정했으며, 이러한 경로로 요청이 오면 /back_order로 라우팅하도록 설정했습니다. pathType의 경우 Prefix로 설정해 경로가 /back_order로 시작하는 모든 요청을 해당 서비스로 라우팅하도록 합니다.
apps-application.yaml은 App of Apps 패턴으로 여러 하위 애플리케이션을 관리하기 위한 루트 애플리케이션 설정입니다. ArgoCD는 이 루트 애플리케이션을 통해 Git 저장소에 정의된 여러 애플리케이션들의 Helm 차트를 활용해 쿠버네티스 클러스터에 리소스를 자동 배포합니다.
이렇게 기존 Jenkins를 통한 JAR 파일 배포 방식에서 GitLab CI/CD → Harbor → ArgoCD → Kubernetes Cluster로 이어지는 배포 파이프라인을 구축해 개발 및 테스트 프로파일 단계에서 빠르게 피드백을 반영할 수 있었으며, 안정적이고 신속한 운영 환경 배포를 달성할 수 있었습니다.

Vue 라이브러리 첫 사용

프론트엔드 UI 라이브러리로는 React를 쭉 사용해 왔지만, 영풍문고의 경우 프론트엔드 프로젝트가 Vue로 작성돼 있어서 개발하려면 Vue 환경 내에서 진행해야 했습니다. 사실 Vue가 존재한다는 것 만 알고, 직접 사용해본 경험은 전무했기에 공식문서에서 튜토리얼을 빠르게 1회독 하고 바로 개발에 진행했습니다. 남은 개발 기간에 비해 처리해야 할 버그와 새롭게 개발해야 하는 기능이 너무나도 많았기에 필요한 부분을 빠르게 찾아가며 개발했습니다. 소문대로 React에 비해 간단하긴 했지만, 생소함에서 오는 어려움에 인해 쉽지만은 않았습니다. 어드민 백오피스의 나우드림 관련 코드 중 일부는 다음과 같습니다.
1<!-- orders/NowdreamDashboard.vue --> 2<template> 3 <section class="order-dashboard"> 4 <NowdreamFilterPanel 5 v-model:status="filters.status" 6 v-model:receiving-method="filters.receivingMethod" 7 v-model:branch-only="filters.branchOnly" 8 @submit="fetchOrders" 9 /> 10 11 <NowdreamTable 12 :orders="orders" 13 :busy="isLoading" 14 @bulk-release="handleBulkRelease" 15 /> 16 17 <PaginationBar 18 v-if="pagination.total > 0" 19 v-bind="pagination" 20 @change="handlePageChange" 21 /> 22 </section> 23</template> 24 25<script setup lang="ts"> 26import { useNowdreamOrders } from '@/composables/useNowdreamOrders' 27 28const filters = reactive({ status: 'ALL', receivingMethod: 'ALL', branchOnly: false }) 29const { orders, pagination, fetchOrders, releaseOrders, isLoading } = useNowdreamOrders(filters) 30 31const handleBulkRelease = async (selectedOrderNos: string[]) => { 32 if (!selectedOrderNos.length) return 33 await releaseOrders(selectedOrderNos) 34 fetchOrders() 35} 36 37onMounted(fetchOrders) 38</script>
1<!-- orders/components/NowdreamFilterPanel.vue --> 2<template> 3 <FormSection @submit.prevent="$emit('submit')"> 4 <OptionField label="주문 상태" :items="statusOptions" v-model="modelValue.status" /> 5 <OptionField label="수령 방법" :items="receivingOptions" v-model="modelValue.receivingMethod" /> 6 <ToggleField label="미수령 주문만 보기" v-model="modelValue.branchOnly" /> 7 <PrimaryButton type="submit">검색</PrimaryButton> 8 </FormSection> 9</template> 10 11<script setup lang="ts"> 12const modelValue = defineModel<{ status: string; receivingMethod: string; branchOnly: boolean }>('status') 13</script>
1<!-- orders/components/NowdreamTable.vue --> 2<template> 3 <DataTable :rows="orders" :loading="busy" selectable @select-change="emit('bulk-release', $event)"> 4 <template #default="{ row }"> 5 <td>{{ row.orderNo }}</td> 6 <td>{{ row.statusLabel }}</td> 7 <td>{{ row.branchPickupLabel }}</td> 8 <td>{{ row.consultCount }}</td> 9 <td> 10 <StatusButton :status="row.status" @click="emit('bulk-release', [row.orderNo])" /> 11 </td> 12 </template> 13 </DataTable> 14</template> 15 16<script setup lang="ts"> 17defineProps<{ orders: OrderSummary[]; busy: boolean }>() 18defineEmits<{ (e: 'bulk-release', value: string[]): void }>() 19</script>
Vue를 도입한 뒤에는 NowdreamFilterPanel, NowdreamTable, PaginationBar처럼 기능 단위로 쪼갠 컴포넌트를 만들어 div 중첩 없이 곧바로 비즈니스 로직을 드러냈습니다. defineModel, defineProps/Emits, v-model 확장 구문을 조합해 필터 입력과 테이블 선택 상태를 최상위 대시보드 컴포넌트와 자동으로 동기화했고, useNowdreamOrders composable 하나로 데이터 페칭·페이징·일괄 처리 로직을 캡슐화했습니다. Vue의 선언적 바인딩과 slot 기반 테이블 구성 덕분에 UI 수정과 도메인 로직 수정을 완전히 분리할 수 있었고, 운영팀의 요구사항을 빠르게 반영하는 체계를 갖췄습니다.

Tech Stack

온, 오프라인 경계가 흐려지는 커머스를 지원하기 위해 Java/Spring 백엔드, Vue/Vite 백오피스, GitLab CI/CD, Harbor, ArgoCD 파이프라인, Kubernetes 클러스터를 통합했습니다.

BACKEND

Commerce Platform Core

8

주문/배송/재고 도메인을 담당하는 핵심 백엔드 모듈

Java 11Spring BootSpring BatchSpring QuartzSpring Cloud GatewaySpring Data JPAMyBatisRedis

FULFILLMENT

Order Fulfillment & Pickup

4

나우드림 픽업과 출고 처리 자동화를 위한 서비스

Order OrchestratorWMS IntegrationMyBatis Cursor Item ReaderSynchronizedItemStreamReader

OPS

Infrastructure & Delivery

7

CI/CD와 클러스터 운영을 위한 인프라 도구

GitLab CI/CDJenkinsHarbor RegistryArgoCDKubernetesHelmKaniko

FRONTEND

Frontend & Admin

4

웹/모바일 쇼핑몰과 백오피스 UI

TypeScriptVue.js 3ViteSCSS Modules

DATA

Data & Monitoring

3

로그, 모니터링, 데이터 파이프라인

PrometheusGrafanaLoki