K-Fashion Shop 서버의 설계와 구현

애플리케이션 설계와 구현에 대한 고민들

류종민
2021.10.25

안녕하세요. 딜리셔스 글로벌플랫폼팀 류종민입니다. 지난 9월, 저희 팀이 구성된 이후 맡게 된 첫 번째 프로젝트의 MVP 버전이 무사히 세상 밖으로 나오게 되었는데요. 첫 번째 프로젝트이니 만큼 나름 의욕 넘치게 진행됐던 개발 진행과정에서의 경험을 공유하고자 합니다.

글로벌플랫폼팀

저희는 지난 7월에 새롭게 조직된 팀입니다. 딜리셔스는 세계적으로도 흔치 않은 동대문 패션 클러스터라는 다이나믹한 환경을 이미 시스템화해 나가고 있고, 국내뿐만 아니라 해외 시장과의 연결을 만들고 확장해 나가고자 노력하고 있습니다. 그 과정에서 필연적으로 하게 되는 다양한 시도들을 기술적으로 뒷받침할 백엔드 개발 조직이 필요하게 되어 ‘글로벌플랫폼팀‘이 태어나게 됐습니다.

처음엔 2명으로 시작했고 현재는 소속 구성원이 4명으로 늘어났으며, 앞으로도 글로벌 시장에서 많은 시도들을 해 나갈 것인 만큼 조직도 계속 확장해나갈 계획입니다.

K-Fashion Shop

저희 팀의 첫 번째 프로젝트인 ‘K-Fashion Shop‘은 신상마켓을 이용하는 도매 회원들이 자신이 보유한 상품들을 좀 더 자유롭게 고객들에게 전달할 수 있게 만들자는 니즈에서 출발했습니다. 또한 신상마켓은 사업자 등록증과 도매 거래 내역 등의 증빙서류가 필요한 가입 절차로 인해 아직까지는 국내 소매상인을 주 대상으로 서비스가 제공되는 환경인데, 해외의 거래처에 상품을 소개하고 필요한 커뮤니케이션을 하기 위해서는 위챗이나 카카오스토리와 같은 별도의 서비스를 활용하고 있는 상황입니다. 따라서 신상마켓 외부의 거래처들을 관리하고 그들에게 상품의 이미지라든지 가격과 같은 정보를 제공하기 위해서는 꽤나 번거로운 과정을 거쳐야 하는데, 이러한 불편을 이 ‘K-Fashion Shop’ 서비스를 통해 해소해보고자 했습니다.

개발에 들어가기에 앞서 고민했던 것들

팀이 조직된 지 얼마 지나지 않아 바로 시작된 프로젝트였기 때문에 팀 차원에서 고민들이 좀 있었습니다. 아직 팀의 개발 문화나 일하는 방식 등 팀 빌딩 측면에서 불확실한 부분이 많았고, 새로운 프로젝트에 대해서 개발자로서 개인적인 욕심들도 있었기 때문에 먼저 팀 차원에서 몇 가지 합의를 하고 진행하고자 했습니다.

개발 언어를 정하자.

딜리셔스의 백엔드 개발 조직에서는 팀에 따라 RubyJava를 주로 사용하고 있습니다. 글로벌플랫폼팀은 처음 구성될때부터 JVM 기반의 언어를 메인으로 사용하고자 했었고, Kotlin도 고려를 했으나 최종적으로는 Java를 메인 언어로 사용하기로 했습니다. 다만 현재의 메인 언어일 뿐, 이후 경우에 따라 다른 언어들을 활용할 수 있는 가능성을 배제하진 않았습니다.

좀 더 나은 설계 기법을 고민한다.

팀원 각자 나름의 이유로 애플리케이션을 새롭게 개발해 본 경험에 공백이 좀 있는 상황이었습니다. 그런 만큼 공백 기간동안 나왔던 좋은 설계 방법론이나 도구들을 활용해보고자 했고, 프로젝트 시작전에 Clean ArchitectureDomain Driven Design, 객체지향 설계 등 애플리케이션 설계와 관련 서적들을 살펴보며 우리의 새로운 애플리케이션을 어떻게하면 좀 더 나은 형태로 설계할 수 있을지 고민하고 논의하는 과정을 거쳤습니다.

물론 기존에 해왔던 익숙한 방식(아래에 기술할 데이터 기반 설계라든지)으로 설계하고 구현한다면 보다 빠르게 설계를 끝마치고 구현에 필요한 시간을 좀 더 확보할 수도 있었겠지만, 보다 나은 설계 방법을 통해 우리의 애플리케이션이 좀 더 변화에 안정적이고 확장에 유연한 구조를 가질 수 있다면, 당장의 시간과 노력이 결국 나중의 시간과 노력을 더 많이 줄여줄 수 있을 거라 생각했습니다.

단, 특정 서적이나 방법론에서 제시하는 형태를 그대로 활용하기에는 그에 대한 이해도가 충분치 않았기 때문에, 각각에서 얘기하는 의도를 파악하고 적용할만한 부분들을 저희에 맞게 일부 차용하는 형태로 설계를 진행했습니다.

데이터 기반으로 설계하지 않는다.

설계 과정에서 가장 조심하고자 했던 부분이 바로 데이터 모델링을 기반으로 설계하는 것이었습니다. 저도 그랬었고 많은 개발자들이 예전부터 습관처럼 하던 설계의 시작은 ERD로 대표되는 DB 테이블 스키마 작성이었습니다. 이 방식은 경우에 따라 도메인에 대한 모델링이 미리 정해둔 데이터 구조에 종속되어 버리는 결과를 낳게 되어, 현실의 도메인 로직을 코드로 표현하는데 제약이 되어버리는 상황을 만들게 되는 것을 종종 경험했습니다.

객체지향에서는 특히 객체의 속성보다는 객체의 책임과 행위에 더 초점을 맞추는데, 이 부분에 있어서도 데이터 기반의 설계는 맞지 않은 측면이 있었고, 이런 것들을 감안해 데이터의 구조에 대한 결정은 되도록 뒤로 미루고 적절한 책임과 행위를 갖고 있는 객체들을 통해 도메인을 먼저 모델링하려고 했습니다.

코딩 컨벤션 등 각종 개발 가이드를 사전에 정하진 말자.

보통 다수의 개발자가 참여하는 프로젝트에서는 개발이 시작되기 전 미리 개발 가이드와 각종 코딩에 필요한 컨벤션들을 잡아두고 진행을 하게 됩니다. 일관된 코딩 스타일을 통해 유지보수의 용이성을 늘리는 부분에 있어서 분명 필요한 일이지만, 팀의 정책을 결정하는 데 있어서 좀 더 가능성을 열어두고 서로의 케이스를 보면서 충분히 의견을 교환하고 더 나은 결론을 통해 정책을 만들고 싶었습니다.

물론 일정에 대한 고려가 있기도 했고, 처음에는 2명이었기 때문에 나중에 컨벤션을 맞추는 것에 대해 비교적 부담이 덜 되기도 했습니다.

요구사항 분석

가장 먼저 하게 된 일은 서비스 기획 초안에 대한 검토와 리뷰를 진행해서 개발 측면에서 요구 사항을 분석하고 정리하는 것이었습니다. 제품의 핵심 기능들의 목록을 정리하고, 우선순위를 매기고, 고객과 사용자 스토리를 정의했습니다.

제품 백로그

서비스의 요구 사항을 정리하기 전에 필요한 것은 고객을 포함한 사용자들을 정의하고, 각 사용자들을 칭하는 용어를 통일하는 일이었습니다.

제품 사용자 정의

  • 도매 회원 - 신상마켓에 이미 가입되어 있는 도매 상인으로서, 신상마켓에 등록되어 있는 상품들을 통해 Shop을 개설하고 운영하는 주체입니다.
  • 방문자 - 서비스에 가입하지 않고 비로그인 상태에서 서비스를 이용하는 사용자로, 잠재적인 소매 회원입니다. 처음에는 ‘소매 사업자’라는 용어를 썼는데 소매 회원과 잘 구분되지 않는다는 피드백이 있어서 ‘방문자’로 바뀌었습니다.
  • 소매 회원 - 서비스에 가입하여 Shop을 이용하는 소매 상인입니다. Shop의 상품들을 조회하면서 거래처 관계도 맺을 수 있습니다. 초기에는 해외의 소매 상인들에 좀 더 초점을 맞춰서 기획이 되었습니다.
  • 운영자 - 사내에서 어드민 콘솔을 통해 서비스를 운영합니다.

사용자 스토리

각각 정의된 사용자들이 우리 서비스에 대해서 어떤 목적을 갖고 어떤 기능을 이용하려고 하는지에 대해 사용자 스토리로 정리하고자 했습니다. 서비스가 제공할 핵심 기능들을 요약하여, 각 기능이 무엇이고 누가, 왜 이용하는지를 최대한 핵심 키워드들로 단순화하려고 했습니다. 이를 통해 요구 사항의 대략적인 얼개를 파악하고, 그에 대한 우선순위를 결정하여, 이후 설계나 구현을 진행할 때 참고하는 기준으로 삼았습니다.

팀 위키에 작성한 사용자 스토리의 일부

위 문서를 보시면 아시겠지만, 초반에는 정식 서비스 명칭도 결정되지 않았던 상황이라 스토어 라는 용어를 사용했습니다. 정식 서비스 명칭이 K-Fashion Shop 으로 정해지면서 스토어라는 용어도 Shop 으로 대체되었습니다. 이하 첨부된 자료들 중 몇몇의 용어들도 현재와 차이가 있습니다.

개념 모델

앞에서 정의한 제품의 백로그를 바탕으로 도메인의 요구 사항을 유스케이스로 세분화하고, 도메인을 구성하는 핵심 객체와 컴포넌트들을 도출하면서 애플리케이션의 대략적인 구조를 잡았습니다. 그리고 최대한 이 단계에서는 사용할 언어나 프레임 워크, 라이브러리 등 구현 단계에 대한 고려를 전혀 하지 않고, 오로지 글과 그림으로 표현되는 객체로 도메인을 모델링 하고자 했습니다. 그래야만 도메인에서 해결하고자 하는 문제에 집중하고 그에 맞는 객체 모델이 나온다고 생각했습니다.

유스케이스

사용자 스토리를 기반으로 좀 더 구체적으로 기능들이 어떻게 동작해야 하는지 정의하고, 그것을 바탕으로 핵심 도메인 개념들을 추출하고자 했습니다.

팀 위키에 작성한 Shop 개설에 대한 유스케이스

도메인 객체

유스케이스를 통해 드러난 도메인의 개념들을 객체와 상태 등으로 점차 모델링 해 나갔습니다.

핵심 객체 간 관계를 표현한 다이어그램

회원과 관련된 부분을 좀 더 세분화하여 정리한 다이어그램

이렇게 도출된 도메인 객체들에 대해서 각자 어떤 책임과 역할을 가져야 하는지 정리했습니다. 그리고 객체가 어떤 상태를 갖고 있다면 상태가 어떻게 변화하게 되는지도 다이어그램을 통해 간략히 표현했습니다.

팀 위키에 작성한 Shop(스토어) 객체에 대한 정의

Shop 상태에 대한 다이어그램

구현 모델

개념 모델을 통해 도메인의 요구 사항을 어느 정도 정리했고, 그를 기반으로 구현과 관련된 고민을 시작했습니다.

개발 언어와 애플리케이션 프레임워크를 각각 JavaSpring Framework로 결정했기 때문에, 실제 구현에 들어가기에 앞서 모듈의 구조나 기본적인 패키지 레이아웃을 정했습니다.

초기 컴포넌트 다이어그램

패키지 레이아웃은 기본적으로는 Domain Driven DesignLayered ArchitectureClean ArchitectureHexagonal Architecture를 참고하여 구성했고, 핵심 비즈니스 영역이 외부 영역에 의존하지 않고 순수하게 도메인 로직을 표현할 수 있게 하는 데에 중점을 뒀습니다.

초기 패키지 레이아웃

K-Fashion Shop은 기존 신상마켓의 도매 회원 정보와 상품 정보에 대한 의존성을 갖고 있는 서비스입니다. 그래서 의존하고 있는 다른 시스템과 어떻게 연결이 되고 협력을 하는지 한눈에 볼 수 있도록 도식화했습니다.

신상마켓과의 연관관계를 표현한 다이어그램

이렇게 기초적인 설계가 어느 정도 마무리 된 이후에 프론트엔드와 신상마켓 백엔드, 어드민 등 서버에서 제공해야 하는 API들의 명세를 위키에 정리하여 공유하고 피드백을 받은 후 실제 구현에 들어가게 됐습니다. API 명세는 이후에 Spring Rest Docs를 통해 제공하는 것으로 변경했습니다.

사실 도출된 도메인 객체들을 기반으로 실제 구현까지 고려된 클래스 다이어그램 작성까지 하고 구현으로 넘어가려고 했으나, 첫 출시를 목표로 한 MVP 기준에서는 도메인 로직도 크게 복잡하지 않았고, 이 정도 설계 만으로도 충분히 구현으로 진행할 수 있겠다는 판단이 들어서 이후 바로 구현으로 넘어갔습니다. 사실 일정에 대한 부담도 좀 있었고요.

구현

구현과 관련해서는 세세한 내용보다는 팀에서 고민했던 부분들을 위주로 말씀드리려고 합니다.

구현에 대한 원칙

구현에 들어가면서도 고민이 되는 부분들이 있었는데, 그와 관련해서 몇 가지 원칙을 정하고 지키고자 했습니다.

첫째, 도메인 영역에서는 외부의 라이브러리나 프레임워크에 의존하지 않고 오직 순수한 자바로 구현하려고 했습니다.

최근에는 라이브러리나 프레임워크 사용을 위해 필연적으로 따라오는 어노테이션들이 도메인 로직에 대한 가독성을 떨어트린다고 생각했습니다. 또한 저희의 핵심 도메인 로직들은 되도록이면 눈에 보이는 곳에 모두 표현하고 싶었습니다. 그래서 Lombok과 같은 어노테이션 기반 라이브러리들도 기본적으로는 사용하지 않았습니다. 물론 그에 따라 Java의 언어적 한계 탓도 있지만 보일러 플레이트 코드들이 필연적으로 생겨나게 되었습니다. 요즘은 IDE에서 제공하는 코드 생성 기능들이 꽤 쓸만하기 때문에, IDE의 힘도 좀 빌려서 극복했습니다.

둘째, 개념 모델에서 유스케이스로 표현된 각 기능 명세를 각각 하나의 유스케이스 객체로 추상화하고자 했습니다.

그렇게 함으로써 이후 유지 보수 과정에서 특정 유스케이스에 대한 코드의 확인이나 수정이 필요할 때 하나의 추상화된 영역에서 관리될 수 있도록 했습니다. 그리고 일반적으로 서비스라 불리는 객체들의 역할을 조금 다르게 활용했는데, 애플리케이션 영역에서 유일하게 스프링 프레임워크에 의존성을 갖고, 스프링의 DI 기능을 통해 유스케이스에 의존성을 주입해 주기만 하는 단순한 역할을 맡겼습니다. 지금 생각해 보니 굳이 서비스란 이름을 붙일 필요는 없었던 것 같습니다.

셋째, 위에서 패키지 레이아웃을 언급할 때 말씀드렸던 것처럼, 핵심 비즈니스를 포함하고 있는 도메인 영역은 다른 영역에 대해 의존관계를 갖지 않고, 다른 영역에서 제공하는 기능이 필요하다면 도메인 영역에서 인터페이스를 제공하여 의존성을 역전시키는 형태(DIP)로 도메인 영역이 다른 영역의 요구사항으로 인해 불필요하게 오염되는 것을 막고자 했습니다.

특히 영속성을 위해 JPA를 사용하게 되었는데, 도메인 영역이 외부(프레임워크)에 의존하지 않도록 하기 위해 도메인 엔티티와 JPA의 엔티티들 별도로 나눠서 구현을 하게 되었습니다. 하지만 이 부분은 사실 꽤 많이 고민이 되었습니다. 동일한 도메인 개념을 표현하기 위한 엔티티를 이중으로 관리하다 보니, 구현 코드가 다소 복잡해지고 관리해야 하는 파일의 개수가 늘어나는 단점이 있었습니다. 또한 JPA가 의도한 바와는 다르게 활용하게 되는 것 같아서 우려도 되었습니다. 하지만 도메인 영역의 독립성을 지키기 위해서 한 번쯤 시도해 볼 만한 부분이라 판단했습니다. JPA의 특성상 엔티티 클래스에 꽤 많은 어노테이션이 들어가는데, 이런 어노테이션들로 인해 오히려 도메인 로직에 대한 가독성이 떨어지지 않도록 고려했습니다.

넷째, 도메인 영역의 객체들을 구현할 때는 최대한 다른 영역에 대한 고려 없이, 도메인을 있는 그대로 코드로 표현하기 위해 노력했습니다.

예를 들자면, 보통 도메인 엔티티를 구현할 때 기존 데이터 기반 설계에 대한 습관이 남아 있어서 ‘DB에 어떤 식으로 저장하고 불러올까?’ 등을 같이 고민하게 되는데, 이렇게 되면 애써 데이터 기반 설계를 회피하려 했으나 구현은 데이터 기반이 되어 버리는 상황이 발생하게 됩니다. 그래서 최대한 이 부분을 조심하고자 했고, 심지어 ‘영속성은 꼭 RDB를 이용하지 않을 수도 있다.’ 라는 가정도 세우면서 인프라 영역의 세부 구현을 최대한 늦게 고민하고자 했습니다.

회원 가입에 대한 주요 구현 객체 다이어그램

테스트

테스트 코드는 개발자라면 누구나 있어야 한다고 생각하지만 적절하게 만들고 유지하기가 참 어려운 것 같습니다. 부지런한 성격이 아니라서 그런지, 그동안 개발자로서 지내오면서 TDD는커녕 핵심 비즈니스 로직에 대해서라도 꼼꼼히 유닛테스트를 작성하지도 못 했던 것 같습니다.

이번 프로젝트를 진행하면서도, 처음에는 TDD에서 얘기하는 개념들을 일부 도입해서

  • 유스케이스 객체에 한해서는 테스트 클래스를 만들고,
  • 주석으로 해당 유스케이스가 만족해야 하는 조건별 기능이나 예외케이스를 기술하고,
  • 하나의 케이스마다 테스트 메서드를 만들어 BDD를 차용해 ‘주어진 조건’, ‘검증하려는 행위’, ‘결과 검증’ 으로 영역을 나누어 의도하는 결과를 검증할 수 있도록 테스트를 작성하고,
  • 실제 구현을 통해 테스트 결과를 점차 성공하게 만들고,
  • 이후 구현 코드를 한 번 더 리뷰하고 리팩토링하는 과정을 거치면서

유스케이스를 하나씩 구현해나갔습니다. 개인적으로는 생각보다는 코딩에 들어가는 시간이 크게 늘어나진 않았는데, 런칭이 점점 가까워지고 일정에 부담이 생기면서 마음이 급해져서 그런지 구현 코드가 먼저 떠오르는 건 어쩔 수가 없었습니다. ㅠㅠ

하지만 좋은 경험이 됐고, 테스트와 관련해서 미진한 부분은 이후에도 계속 보완하고 노력해보려 합니다.

작성했던 유스케이스 테스트 코드의 일부

회고

이 프로젝트를 시작하면서 설계와 구현 측면 모두에 대해 새롭게 고민하고 시도해 보는 데에 대한 개인적인 기대와 욕심이 있었습니다. 이미 많이 알려지긴 했지만 비교적 일반적이진 않았던 접근들을 현업에 적용해 보면서 팀원들도 익숙치 않은 환경 때문에 고생을 많이 하셨습니다. 이 애플리케이션이 정말 우리가 의도했던 대로 좀 더 나은 방향으로 만들어졌는지 되묻는다면 솔직히 아직은 확신할 수 없을 것 같습니다. 하지만 개발하는 과정에서 나름 치열하게 고민하고 논의했던 경험들과, 더 나은 구조와 구현을 위해 노력한 흔적들이 곳곳에 묻어있어서 더 나아질 가능성이 충만한 애플리케이션을 얻었다고 생각합니다.

마지막으로 한가지 말씀드리고 싶은 것이, 이 글에서 전통적인 ‘데이터 기반 설계‘를 회피하고자 했다고 말씀드렸는데, 사실 ‘데이터 기반 설계는 후지고 나쁘니까 무조건 쓰지말자!’ 라는 의도까지는 아니었습니다. 다만 저희의 도메인을 표현하는 데 있어서 더 나은 방향으로 나가기 위해서 택한 하나의 시도였다고 생각해주세요. 각 도메인의 특성과 처한 상황이 모두 다르기 때문에, 다양한 기술과 방법론은 각자의 환경에 맞게 잘 선택하고 도입하는 게 중요하다고 생각해요. 제가 팀에서도 한 번씩 강조하는 말이 있는데 이 말을 끝으로 글을 마치고자 합니다.

오래된 것을 유지하는 게 나쁜 게 아니라, 더 나아지기 위해 노력하지 않는 게 나쁘다.

류종민

딜리셔스 백엔드 개발자

"쩔고 싶다... 이왕이면 개쩔고 싶다..."