MongoDB를 활용하여 Repository 구현
토이 프로젝트

MongoDB를 활용하여 Repository 구현

모든 소스 코드는 https://github.com/lkimilhol/matchingProject 에서 확인 가능합니다.

안녕하세요. 저번에 MongoDB(이하 몽고) 설치하였고, 계속해서 간단하게 데이터가 들어가는지 정도 확인을 했었는데요.

 

이번에는 기획 단계를 하나 추가해 볼까 해요. 바로 주문 히스토리를 몽고를 사용하여 쌓도록 하는 것입니다.

 

다음과 같은 생각으로 이런 기획을 결정하였어요.

 

  • NOSQL에서 업데이트가 빈번하지 말아야 한다.
  • 히스토리 기록은 계속해서 데이터를 쌓는다.
  • 히스토리 기록을 통해 데이터를 조회하고 이를 가공해서 추후에 지표를 추출 할 수 있다. 이 때 몽고디비가 적절할 것이라고 판단했다.
  • 몽고는 확장에 용이하기에 서비스의 트래픽이 증가하면 이에 대한 관리가 쉬울 것.

위의 내용은 사실 가상의 내용이긴 합니다만, 위와 같은 생각에 근거하였습니다.

 

그러면 어떤식으로 서비스를 개발해 나갔는지 보도록 하겠습니다.

 

1. Repository 수정


 

일단 저번에 추가했던 OrderCurrent를 제거하였습니다. 기능 동작을 확인하기 위한 부분이었으니까요.

그리고 OrderHistory Repository를 수정하였습니다.

 

OrderHistoryRepository.java

@Repository
public interface OrderHistoryRepository extends MongoRepository<OrderHistory, String> {
	Optional<OrderHistory> findByMemberIdAndMenuIdAndShopId(Long memberId, Long menuId, Long shopId);
}

MongoRepository를 상속 받도록 인터페이스를 수정하였습니다.

 

다음은 OrderHistory 도메인입니다!

 

OrderHistory.java

@NoArgsConstructor
@Getter
@Document(collection = "current")
@CompoundIndexes({
        @CompoundIndex(name = "member_shop_menu", def = "{'memberId' : 1, 'shopId': 1, 'menuId': 1}")
})
public class OrderHistory {
    @Id
    private ObjectId id;

    private Long memberId;

    private Long shopId;

    private Long menuId;

    private Quantity quantity;

    private OrderStatus orderStatus;

    private LocalDateTime insertTime;

    public OrderHistory(Long memberId, Long shopId, Long menuId, Quantity quantity, OrderStatus orderStatus) {
        this.memberId = memberId;
        this.shopId = shopId;
        this.menuId = menuId;
        this.quantity = quantity;
        this.orderStatus = orderStatus;
        this.insertTime = LocalDateTime.now();
    }

    public static OrderHistory of(Long memberId, Long shopId, Long menuId, Quantity quantity, OrderStatus orderStatus) {
        return new OrderHistory(memberId, shopId, menuId, quantity, orderStatus);
    }
}

몇 가지 변경 사항이 있었는데요. 일단 빌더 패턴을 제거하였습니다.

 

빌더 패턴은 객체를 생성할때 매우깔끔한 코드 작성이 가능하다고 생각합니다. 메서드 체이닝을 통하여 인자로 많은 값을 넘기게 되는 경우를 제거 할 수 있어요. 하지만 빌더 패턴을 쓰다보니 단점 하나를 발견 했었는데요. 바로 필요한 인자를 빼먹을 수 있다는 것이었습니다.

 

때문에 저는 정적 팩토리 메서드를 사용하였습니다. 코드의 인자로 많은 데이터를 넣어주어야 했지만 저는 필요한 인자를 명시하는 것이 더 좋다고 판단하였습니다. 다들 아시겠지만 정답은 없는 부분이고 이 또한 안티패턴으로 느껴진다면 리팩토링을 진행해보죠!

 

그리고 Long 타입이었던 id를 ObjectId으로 변경하였습니다. 그리고 API 요청을 통해 데이터를 넣어보았습니다.

 

postman을 사용했습니다. 테스트케이스를 사용할까 했는데, 실제로 API 콜이 해보고 싶었어요.

 

몽고에 값이 어떻게 들어가있을까요?

예상했던 대로 값이 잘 들어가 있습니다. 하지만 하나 걸리는게 있네요. quantity 입니다. 

 

2. 래핑 클래스 해결


Quantity는 제가 토이 프로젝트에 적용한 래핑 클래스입니다. 

수량은 일반적으로 int 타입으로도 해결이 가능할거에요. 하지만 저는 원시값을 최대한 포장하는 개발을 지향하고 있습니다.

 

원시값에 대해서 궁금하신 분들은 다음 포스팅을 참고해보시면 좋을거같아요.

 

https://kimmayer.tistory.com/entry/1%EC%A3%BC%EC%B0%A8-%EB%AF%B8%EC%85%98-%EB%A1%9C%EB%98%90-%EC%9B%90%EC%8B%9C-%EA%B0%92-%ED%8F%AC%EC%9E%A5

 

1주차 미션 - 로또, 원시 값 포장

안녕하세요. 우아한 테크 코스2기로 활동한 김일호입니다. 이번에는 1주차 미션이었던 로또를 공부하며 고민하였던 내용 중 원시값 포장에 대해서 얘기 해보려고해요. 이번 미션을 통하여 고민

kimmayer.tistory.com

 

아무래도 수량에 저렇게 객체가 들어가버리면 조건으로 수량을 주는 경우 문제가 생기지 않을까요? 조회 자체의 쿼리를 어떻게든 작성한다고 하더라도 복잡성이 크게 올라갈거 같습니다.

 

때문에 Long 타입으로 도큐먼트를 정하되 코드 내에서는 Quantity로 돌아다니도록 해야겠습니다.

 

OrderHistory 수정 코드입니다.

 

OrderHistory.java

@NoArgsConstructor
@Document(collection = "current")
@CompoundIndexes({
        @CompoundIndex(name = "member_shop_menu", def = "{'memberId' : 1, 'shopId': 1, 'menuId': 1}")
})
public class OrderHistory {
    @Id
    private ObjectId id;

    private Long memberId;

    private Long shopId;

    private Long menuId;

    private int quantity;

    private OrderStatus orderStatus;

    private LocalDateTime insertTime;

    public OrderHistory(Long memberId, Long shopId, Long menuId, Quantity quantity, OrderStatus orderStatus) {
        this.memberId = memberId;
        this.shopId = shopId;
        this.menuId = menuId;
        this.quantity = quantity.getAmount();
        this.orderStatus = orderStatus;
        this.insertTime = LocalDateTime.now();
    }

    public static OrderHistory of(Long memberId, Long shopId, Long menuId, Quantity quantity, OrderStatus orderStatus) {
        return new OrderHistory(memberId, shopId, menuId, quantity, orderStatus);
    }
}

Quantity에 getAmount()라고 하는 getter를 추가하게 되었습니다. (Quantity에 대한 부분은 어떻게 풀어가면 좋을지 고민을 해봐야겠어요)

 

그럼 다시 테스트를 해보죠.

 

quantity가 수정되었습니다.

원하는대로 되었네요.

 

3. 주문 취소


주문 취소 API를 개발해서 다음 히스토리가 잘 쌓이는지 확인해보도록 해보죠.

마찬가지로 INSERT를 사용할 예정입니다. 히스토리를 UPDATE 하는것은 지양해야 하겠죠.

 

OrderController.java

@DeleteMapping(value = "/orders/{orderId}")
@ResponseBody
public ResponseEntity<ResultBody> deleteOrder(@PathVariable Long orderId) {
	orderService.deleteOrder(orderId);
	return ResponseEntity.noContent().build();
}

 

이제 postman으로 주문 취소를 해보도록 해보죠.

주문 취소의 상태가 잘 들어가있습니다. 컬럼명이 짤렸지만 orderStatus 입니다!

원하는 기능 구현은 모두 진행하였군요!

(마지막으로 패키지 네임을 수정하고, 주문 히스토리에 주문 ID를 넣어서 마무리 지었습니다)

 

4. 마치며


다음으로 필요한 부분을 고민해보았는데요. 아마 인덱스를 수정해야 할거같네요. 어떤식으로 조회가 될 지 고민을 해보고 필요한 인덱스를 추가해보도록 하겠습니다.

 

몽고를 실제로 더 깊게 사용해보려고 토이프로젝트에 적용하였는데요. 역시나 직접 구현하지 않으면 예상치 못했던 문제들을 파악하기 힘든거 같습니다.

 

추가로 Quantity 부분에 대해서 좋은 아이디어나 피드백 주실 분들은 언제나 환영입니다!

 

감사합니다!