상현

아임웹 2023 주문결제 TF

( 업데이트: )

why?

아임웹의 쇼핑시스템중 주문결제가 지금은 잘 돌아가고있지만 약 10년 넘은 소프트웨어로 지금의 트래픽과 주문량을 처리하다보면 문제가있을 수 있다. 그래서 현재 사용자들의 현실세계에 가지고있던 문제점들과 기존 서비스하고있는 기능들을 안정적으로 더 많은 주문량과 트래픽을 처리할 수 있도록 변경하는 프로젝트를 시작합니다.

관심사의 분리를 위해서 우리는 좀더 심리스한 MSA(Micro Service Architecture) 구조로 만듭니다.

무엇을 했나?

주문관리 React 앱을 전반적으로 만들었습니다. ci/cd, 인프라, 프로덕트, 에러 모니터링 그리고 프로젝트를 완성해나가면서도 발전해나가는 아임웹 그리고 추가되는 기반기술들에 맞춰서 주문관리도 계속 업그레이드 하였습니다.

프로덕트의 기반

첫 아키택쳐 Nextjs + presentation pattern 을 활용한 컴포넌트 라이브러리

Nextjs에서는 데이터만 핸들링하는 패턴으로 만들고 싶었습니다. radix-ui같은 primitive한 컴포넌트에 UI만 아임웹에 맞춘 컴포넌트라이브러리 zynk를 만들어서 사용하였습니다.

zynk를 이용해 oms-template라는 라이브러리를 만들고 여기서 뷰관련된 템플릿을 라이브러리를 만들었습니다.

presentation을 만드는 방법에 집중을했고 1개의 depth가 생기면서 작업하는데 어려움이 있었습니다

SPA로 변경 (nextjs > vite)

  • nextjs를 사용하면서 이것저것 해결하기 힘들었던 문제.
  • 서버사이드에서 만들어진 js의 인스턴스의 컨트롤할수 없는 부분이 존재. (또는 설정할기 까다로워서 아직도 해결하지 못한 문제)
    • next-i18next
  • 개발하면서 렌더링이 느려서 개발속도가 더딘문제.
    • 각 페이지마다 이동할때 느렸지만 지금은 한번 로드할때 느리고 브라우저에 캐싱한다.
  • 서버사이드의 이점을 사용하지 않고있다.
  • 'use client' 만을 사용하는데 굳이 nextjs로 할 필요가있을까?
  • tanstack/query의 서버사이드 사용, 활용 하지 않고있다.

react-router-dom + dynamic page를 처리할수있는 부분을 만들어서 포팅준비를 완료하고 이번 주말에 SPA로 포팅을 완료했습니다.(현재 nextjs로 만든 앱의 커버리지 100%입니다.)

문제해결

복잡한 기획 다향한 조건을 어떻게 간결하게 풀어냈나? 저는 사양패턴을 활용했습니다!

https://github.com/Hansanghyeon/spec-pattern-ts

사양패턴을 알게되고 커머스 코드에서는 정말 유용하게 사용될 것이라고 판단해서 도입하였습니다.

간단한 예를 들면

import { Spec, type ISpecification } from 'spec-pattern-ts'

const product = new Product()

// 새롭게 추가된 상품인지 확인하는 스펙
const isNewSpec: ISpecification<Product> = new Spec(
  (candidate) => candidate.isNew === true
)
// 상품의 갯수가 0개이상인지 확인하는 스펙
const isQtyChangedSpec: ISpecification<Product> = new Spec(
  (candidate) => candidate.qty > 0
)
// 새롭게 추가된 상품이 아니고 기존 상품이면서 갯수가 변경되고 1개 이상일때
const isOriginalAndQtyChangedSpec: ISpecification<Product> =
  isNewSpec
    .not()
    .and(isQtyChangedSpec)

if (isNewSpec.isSatisfiedBy(product)) {
  return 'A'
}
if (isOriginalAndQtyChangedSpec.isSatisfiedBy(product)) {
  return 'B'
}Code language: TypeScript (typescript)

이렇게 특정 스펙을 미리지정해두고 가져가다 조건으로 사용하는 건데 기획자분이 작성해주신 기획을 보고 스펙만 따로 만들어서 가져다 사용하고 나중에 문제가 될때 이 스펙쪽만 중점적으로 보면되는 이점이있습니다

주문관리 코드에서는 이렇게 작성하였습니다

const 상품준비: ISpecification<OrderSection> = new Spec(...)
const 디지털상품: ISpecification<OrderSection> = new Spec(...)

if (상품준비.and(디지털상품).model(modelOrderSection)) {
  // 상품준비 상태이면서 디지털상품
}Code language: TypeScript (typescript)

위처럼 사용하는쪽에서 어떤 스펙인지 바로 알아볼수 있는 장점이 있지만 장점만 있는 것은 아니었습니다.

일반적으로 사용하는 방법이 아니었기에 아임웹 주문관리 시스템에만 적용된 기술 방법과 같이 되었고 새로운 사람이 들어왔을때 적응하기 어렵다는 단점을 가졌습니다.

개인적으로는 복잡한 스펙을 어떻게 쉽게 풀어나가면서 일반적으로 사용할 수 있는 것들이있나? 아니면 어떻게 만들어야하나? 고민하는 챌린지를 가지게되었습니다.

유저가 겪은 에러 어떻게 해결할까?

api 응답값이 변경되거나 프론트엔드 개발자의 실수로 어느 시점에 제품에서 에러가 날 수 있습니다. 유저가 이런 에러를 보고해주기전에 선조치를 할 수 있어야한다 생각했습니다.

Sentry를 통해서 어떤 문제를 겪는지 미리 확인 할 수 있도록 하였습니다.

Sentry는 공식 제품을 사용하면 비용이 많이 들어가는 제품중에 하나입니다. 저희는 아마존의 환경을 자유롭게 사용 할 수 있었어서 EC2에 Sentry Self-hosting 방법으로 배포해서 사용하도록 구성하였습니다.

덕분에 프로덕션에서 어떤 문제가있는지 슬랙으로 알림을 받고 에러스택을 확인해서 미리 문제를 해결해서 배포하는 환경이 되었습니다.