작업일지

작업일지#1 도메인을 짜보자 - 상품 도메인 설계

hj.choi 2021. 10. 7. 22:36

개요

이번에 f-lab이라는 개발관련 멘토링 컨설팅을 진행하는 곳에서, 멘토님의 리뷰를 받으면 ecommerce 관련 프로젝트를 진행하게 되었다. 단순히 코딩하는 법을 배우려는 것보다는 애플리케이션 개발에 대한 인사이트를 얻는 것이 궁극적이 목표이다. 그래서 작업을 하면서 느낀 것 배운 것 학습한 것 등 세세한 과정을 담아, 앞으로 개발 커리어를 이어나가는데 큰 도움이 되고자 한다.

사전 준비

어떤 프로그래밍 언어를 쓸지, 어떤 기술을 채택할지를 고민하는 것도 좋지만, 제일 먼저 필요한건, 개발하고자 하는 상품 도메인의 특징을 파악하는 것이 우선이라 생각했다. 물론 기술적인 기본기를 갖추는 것도 중요하지만, 한글을 안다고 바로 소설을 쓸 수 있는 것은 아니듯이, 프로그래밍 언어를 안다고 바로 애플리케이션을 짤 수 있는 것은 아니니 말이다. 따라서, 상품 도메인을 프로덕트 코드로 구현하기 전에 사전 조사를 한 항목은 다음과 같았다.

  • 도메인의 관심사 파악
  • 카테고리 별 상품 정보
  • 카테고리 별 상품 정보의 공통점

도메인의 관심사 파악

우선 내가 정의한 상품 도메인의 관심사는 다음과 같다.

  • 상품 재고 관리
  • 상품 등록, 삭제, 수정과 같은 상품 등록 프로세스 관리
  • 상품 정보 관리

여기서 위의 2가지는 사실상 상품과 관련된 데이터에 대한 CRUD의 로직을 짜면 될 것 같아 큰 문제는 아닐 수 있다. 하지만, 오히려 가장 간단할 거라고 생각했던 '상품 정보 관리'를 로직으로 풀어가는데 조금 힘들었다. 가장 힘들다고 생각했던 것은 **카테고리에 따라 다른 상품의 정보를 공통적으로 묶어서 분류하기**였다.

카테고리 분류

상품 종류마다, 어떤 정보가 있는지 파악하려면 우선 상품의 카테고리로 무엇이 있는지 파악해야 했다. 카테고리의 종류를 알아내기 위해 기존에 운영하고 있는 ecommerce 서비스에서 제공하는 카테고리 명을 참고 해보았다. 참고해본 곳은 쿠팡, 마켓컬리, 인터파크 등 여러곳이 있지만, 개인적으로 카테고리가 다양하면서 군더더기가 없다고 생각했던 서비스는 쿠팡이었다. 마켓컬리 역시 카테고리 분류를 참 깔끔하게 해주었지만, 상품 군이 쿠팡에 비해 크게 다양하지는 않았다.

대카테고리 목록

  • 패션의류/잡화
  • 뷰티
  • 출산/유아동
  • 식품
  • 주방용품
  • 생활용품
  • 홈인테리어
  • 가전디지털
  • 스포츠/레저
  • 자동차용품
  • 도서
  • 완구
  • 문구
  • 반려동물
  • 헬스/건강식품

상품의 다양성을 고려하여, 카테고리는 보통 1depth로 끝내는 것이 아니라, 2depth, 3depth 까지 (대, 중, 소 카테고리) 분류하는 곳이 대부분이었다. (헬스의 경우 운동기구도 있지만 기능성 식품도 포함되어 있다는 점을 염두하면 당연하다고 할 수 있겠다.)

카테고리 별 다루는 상품정보 목록

자 이제, 대 카테고리의 종류를 알았으니, 카테고리 별로 다루는 정보의 종류를 따져보자.

○-포함 / △-경우에따라 다름 / X-미포함

  패션 뷰티 출산/유아동 식품 주방용품 홈인테리어 가전 디지털 스포츠/레저 자동차용품 도서 문구 반려동물 헬스/건강식품
상품 이름
상품 한줄설명
브랜드 명
상품 가격
단위 X X X X X X
용량 X X X X X X X X
치수 X X X X X X
포장타입
원산지
유통기한 X X X X X X X X
100g 당 가격 X X X X X X X X X
알러지 정보 X X X X X X X X X
상품 번호
상품 평가 점수
검색 키워드
총 재고수량
할인가
작가 X X X X X X X X X X X X
ISBN X X X X X X X X X X X X
발행일 X X X X X X X X X X X X

카테고리 별 상품 정보의 공통점

표로 따로 정리하면서 내린 결론은 '애매하다'였다. 정말 기본적인 정보인, 이름, 가격, 브랜드 명 말고는 카테고리가 다르다고 무조건 제외되는 정보는 없었으며, 또한 무조건 포함되는 정보는 앞에서 언급한 4가지 말고는 아무것도 없었다. 애매하다는 결론이 난 이상, 더이상 고민하는 것은 무의미하다고 생각하여, 우선은 위에서 정리한 상품 정보를 그대로 코드로 옮겨보았다.

상품 Entity 개발

상품 도메인 v1 - 일괄적으로 `Item` 하나로 관리

entity : Item

@Builder
@Getter
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@AllArgsConstructor
public class Item {

    // 상품 pk
    private Long id;

    // 상품 이름
    private String name;

    // 상품 한줄설명
    private String description;

    // 브랜드 명
    private String brandName;

    // 상품 가격
    private Integer price;

    // 단위
    private String unit;

    // 용량
    private String volume;

    // 치수
    private String dimension;

    // 포장타입
    private String packageType;

    // 원산지
    private String origin;

    // 유통기한
    private String expiration;

    // 100g 당 가격
    private Integer pricePer100g;

    // 알러지 정보
    private String allergicInfo;

    // 상품 번호
    private String modelSerialNo;

    // 상품 평가 점수
    private Integer rating;

    // 검색 키워드
    private String searchKeyword;

    // 총 재고수량
    private Integer stock;

    // 할인가
    private Integer discountPrice;

    // 작가
    private String author;

    // 출판사
    private String publisher;

    // ISBN
    private String isbn;

    // 발행일
    private LocalDate publishedTime;

}

우선 코드를 이렇게 짠 이유를 간단히 설명하자면, `@Builder`를 통해, 객체 생성을 좀더 깔끔하게 진행하고, 자바빈 패턴으로 객체를 생성했을 때 '객체의 일관성'이 깨지는 문제를 방지하고자 하였다. `@NoArgsConstructor(access = AccessLevel.PRIVATE)`는 자바빈 패턴으로 객체를 생성하지 못하게 막기 위함이고, `@AllArgsConstructor`는 빌더 패턴을 적용하는데 필요한 생성자로 볼 수 있다.

이 방식은, 우선 상품의 정보의 공통성을 고려하기보다는 그냥 전부 하나의 객체에 모든 정보를 넣었다. 이러한 형태의 코드를 우선은 `상품 도메인 v1`이라고 하겠다.

상품 도메인 v2 - Item을 추상 클래스로, 대 카테고리 별로 서브 클래스로 구현

이 방식은 생성자 로직만으로 어떤 종류의 상품 객체를 생성할지 명시적으로 보여주고, 카테고리마다 다른 상품의 정보를 따로 관리하는 방법을 찾는 과정에서 나온 코드 구조이다. 이 의도를 코드로 나타내면 다음과 같다.

상품의 추상 클래스

public abstract class Item {

    // 상품 pk
    private Long id;

    // 상품 이름
    private String name;

    // 상품 한줄설명
    private String description;

    // 브랜드 명
    private String brandName;

    // 상품 가격
    private Integer price;

}

상품 카테고리 별 구현 클래스

public abstract class Item {

}

public class Baby extends Item {

}

public class Car extends Item {

}

public class Food extends Item {

}
...

카테고리 별 생성자

Item book = new Book();
...;
Item clothes = new Cloth();
..;
Item food = new Food();
..;

결론부터 이야기 하자면, 이 방식을 채택하진 않았다. 가장 큰 이유로 scalable하지 못하다는 것이었다. 서브 클래스로 카테고리별 상품을 따로 관리하는 것이 얼핏 보면, 깔끔해 보였지만, 만약 상품 정보 관리의 정책이 바뀌거나 필드 명이 바뀌어서, 수정을 해야할 때 발생하는 문제는 다음과 같았다.

  • 클래스 관리가 힘들다. 만약 카테고리가 추가되면, 또 카테고리에 해당하는 서브 상품 클래스를 만들어야 할텐데, 이 방식은 카테고리가 다양해질수록 유지보수의 어려움 역시 비례하여 증가한다.
  • 또하나는 상품 도메인이 카테고리 도메인의 관심사에 관여하는 모습을 보인다. 즉, 타 도메인과의 결합이 발생한 것이다.
  • 마지막으로, 마이바티스를 통해 SQL mapping을 하는 과정에서, 카테고리 별로 쿼리명을 따로 관리해주어야 한다. (xml을 직접 타이핑 하는 작업은 정말 지루하고 괴롭다.)

결론

물론 더 고민해보면 더 좋은 방법이 생각날 수 있겠지만, 현 시점에서는 null 데이터를 허용하는 대신에 모든 상품의 정보를 Item 클래스에 담아서 전부 관리하는 것이 더 편리해 보인다. 그럼 카테고리별로 상품을 분류하고 관리하는 작업은 어떻게 하느냐에 대한 의문이 생기기도 했는데, 그 부분은 우선 카테고리 도메인을 개발하면서 진행하도록 하고, 지금은 상품 자체의 관심사에 집중하도록 하였다.