당니의 개발자 스토리
주문, 주문상품 엔티티 개발 본문
주문, 주문상품 엔티티 개발
이번 시간에는 가장 중요한 주문도메인을 한번 개발해 보겠습니다.

여러분 지금까지 설명드린 것 중에 이 주문 도메인이 정말 제일 중요해요. 왜냐하면 여기에 뭔가 비즈니스 로직들이 얽혀서 돌아가는 걸 JPA나 엔티티를 가지고 어떻게 풀어내는지 아실 수가 있거든요.
그리고 이제 저희가 흔히 듣던 트랜잭션 스크립트 패턴과 도메인 모델 패턴, 도메인 모델 패턴을 많이 못 접해보셨을 거에요. 엔티티에 실제 비즈니스 로직이 있고 더 많은 것을 엔티티로 위임하고 이런 스타일인데 그런 것들이 어떻게 되는지 코드를 통해서 이해해 보실 수가 있습니다.

우선 구현은 상품을 주문하고, 주문이 제일 복잡하겠죠? 그 다음에 주문 내역을 조회하고 주문을 취소할 수 있습니다. 이렇게까지가 구현인데 한번 보여드릴게요.

가입이나 등록은 단순했지만 상품 주문은

회원을 선택하고 상품을 선택한 다음에 예를 들어서 주문을 3개를 했어요. 그러면 여기서 끝나는 게 아니라 지금 보면 주문하는 순간,

이건데요. JPA1 BOOK 이라는 게 어떻게 됩니까? 대표상품 주문 수량이 지금 3건이죠. 그러면,

그러면 여기 상품 목록에 1번에 재고수량을 보면, 3개가 줄어야 돼요. 지금 줄은 거예요.
자 주문 내역에서 취소를 해볼게요.

취소하고 다시 가면,

재고가 원래 96이었는데 다시 99로 올라갔죠.
자 이렇게 재고 관리도 돼야 되고 취소도 할 수 있어야 되고 그렇습니다.

자 주문 상태별로 이렇게 검색, 조회도 할 수 있어야 되고요.
자 이걸 리마인드로 한번 보여드렸습니다. 그러면 이제 주문에 핵심 비즈니스 로직을 한번씩 넣어 볼게요.
먼저 주문에 대한 것을 넣을 건데,

개발 순서는 어떻게 할 거냐면 주문이랑 주문상품 엔티티에 핵심 비즈니스 로직을 넣을 겁니다. 그리고 리포지토리를 만들고, 서비스 만들고 검색 기능 만들고 그 다음에 기능 테스트하고 이런 순서로 할 겁니다.
이번 시간에는 주문과 주문상품 엔티티를 개발해보겠습니다.
말씀드린 대로 여기 Order 클래스에 핵심 비즈니스 로직을 넣을 거에요. 어떻게 넣을 거냐면 먼저 주문이기 때문에 제일 중요한 게 있어요. 저는 생성 메서드라고 많이 하는데, 저뿐만 아니라 다들 주문 생성하는 게 복잡하단 말이에요.

여기 Order만 생성해서 될 Order게 아니라, OrderItem도 있어야 되고 Delivery, 그리고 여러 개 연관관계가 들어가고 막 복잡해지죠?
그래서 이런 복잡한 생성은 이렇게 별도의 생성 메서드가 있으면 좋아요.

public static로 만들어 가지고 createOrder 한 다음에 주문하려면 멤버가 있어야 되겠죠. 그리고 delivery, 배송 정보가 있어야 되고 그 다음에 orderItem을 list로 넘기면 되는데 여러 개 할 수 있으니까 ... 문법으로 넘겼습니다. 이렇게 하면 여러 개를 넘길 수 있겠죠.

자 그러면 Order를 생성을 하고 order.setMember 해서 파라미터로 넘은 member를 세팅해주고 order.setDelivery 해서 delivery를 세팅해주고, 그 다음에 for문으로 돌리면서 방금 생성한 order에다가 orderItem을 집어넣어주면 되겠죠.
자 이렇게 하면 대략 세팅이 끝나고, 그 다음에 또 중요한 게 있습니다.

order.setStatus를 할 건데 처음 상태를 OrderStatus.ORDER로 강제해놓을 거에요.

자 그 다음에 order.setorderDate를 localDateTime.now(), 현재 시간으로 잡을 거구요.

자 그 다음에 return order; 이렇게 하면, 이 오더가 연관관계를 쫙 걸면서 세팅이 되고, 심지어 상태랑 주문 시간 정보까지 다 세팅을 해서 이제 완전히 정리가 되는 거죠.
이런 스타일로 작성을 하는 게 왜 중요하냐면 앞으로 뭔가 생성하는 지점을 변경해야 되면 이 createOrder 부분만 바꾸면 되거든요. 그게 중요한 포인트입니다. 이것저것 찾아다닐 필요가 없습니다.
그 다음에 이제 비즈니스 로직을 넣을게요.

제일 먼저 중요한 주문 취소,

주문 취소는 아까 캔슬 버튼 누르는 거 보셨죠.
이 캔슬 버튼을 누를 때 재고를 다시 올려줘야 되는 거 보셨죠. 그래서 되게 중요한 비즈니스 로직이 들어가는 거에요.
자 이제 cancel()을 만들 건데 우선 조건이 있어요. 만약에 배송이 이미 되어버렸다, 끝나버렸다고 하면 '취소가 불가능해!' 라는 비즈니스 로직이 있어요.

그러면 Delivery의 상태가 COMP 라는 건 배송 완료 라는 뜻이거든요. 배송 완료 이면,

throw 해서 exception 해가지고 이미 배송 완료된 상품은 취소가 불가능합니다. 라고 터트리면 됩니다. 이렇게 validation 로직을 넣었구요.

그 다음에 this.setStatus를 해서, 위의 validation 로직을 통과하면 order의 상태를 cancel로 바꿔주면 됩니다.

자 그 다음에 이제 루프를 돌면서 재고를 원복할 거에요. 여러 상품을 주문하는 거기 때문에, 현재 내가 가지고 있는 this의 orderItems죠. 그냥 this를 생략할게요. this를 쓰냐, 안 쓰냐가 스타일 차이인데 저는 잘 안 써요. 왜냐하면 인텔리제이에서 헷갈리지 않게, 알아서 색칠을 해주잖아요. 물론 this를 꼭 써야 될 때도 있지만, 이거 정말 얘꺼야! 라고 강조 문법으로 강조할 때랑 이름이 똑같을 때 외에는 보통 this를 잘 안 씁니다. 자 뭐 근데 이건 스타일 차이니까.

자 그 다음에 이렇게 만들어 줄게요. 이거는 뭐냐면, orderItem에도 다 cancel 해줘야 되겠죠. 한번 주문할 때 고객이 상품 2개 주문할 수 있잖아요. 그럼 orderItem이 2개 생겼잖아요. 그래서 2개 전부 각각 cancel, cancel 날려주는 거에요.
그럼 cancel()을 만들어주러 가봅시다.

그럼 이제 OrderItem에도 비즈니스 로직 들어온 거란 말이에요. 그럼 한번 cancel 시 어떻게 해야 될까요?

cancel은 지금 OrderItem이 Item을 가지고 있잖아요. 일단 이 Item의 재고를 늘리는 게 목표예요. Item을 가지고 온 다음에 어떡합니까? addStock 해서 다시 재고를 주문 수량만큼 늘려줘야 돼요. 현재 orderItem에서 아까 3건 주문했었죠? Item에서 그 3건의 재고 수량을 원복해줘야 됩니다.

자 그래서 이렇게 재고 수량을 원복했습니다. OrderItem의 Cancel의 뜻은 뭐냐면, 재고 수량을 원복해준다. 그리고 물론 여기 OrderItem에도 상태가 있어야 될 것 같긴 한데, 복잡하니고 Order로 상태를 Cancel로 볼 수 있으니까 이렇게만 바꿔놨습니다.

그래서 이렇게 Order를 단순화했습니다.
자 그 다음에 뭐가 있냐면, 비즈니스 로직 말고 여러분 조회 로직이라는게 때때로 필요해요. 뭔가 계산이 필요할 때가 있거든요.
내가 주문한 전체 가격은 현재 나한테 정보가 없고 orderItem들을 다 더하면 되거든요.

자 이렇게 totalPrice를 0으로 초기화 해놓고 orderItems 루프를 돌면서 orderItem 에 있는 totalPrice를 가져옵니다.

왜냐? 주문할 때 주문 가격과 수량이 있거든요.

그래서 이렇게 하면 됩니다.

이렇게 하면 totalPrice가 계산이 됩니다. 물론 좀 더 깔끔하게 할 수 있겠죠. java 스트림이나 람다 같은 거를 활용하시면 되게 깔끔하게 작성할 수 있습니다. option + enter 누르시면,

인텔리제이가 역시나 해줍니다.

여기서 option + cmd + N 누르시면,

inline으로 이렇게까지 변경하실 수 있습니다.

다시 stream으로 바꿔서 mapToInt로 해서 바꾼 다음에 sum 하면 아까랑 똑같은 로직입니다. Java8를 기본적으로 공부하시는게 아니기 때문에

일단 이걸로 적어놨습니다.
자 이렇게 해서 끝났고, 여기에 대해서 이제 기능을 하나씩 자세하게 다시 설명 드리겠습니다.

먼저 생성 메서드 보시면,

이 메서드가 기본적으로 밖에서 Order를 new 해서 값을 셋셋셋 하는 방식이 아니라, 생성을 할 때부터 아예 그냥 createOrder를 무조건 호출해야 돼요. 그래서 값을 Member, Delivery, OrderItem을 쭉쭉쭉 넣어 가지고 이 생성 메서드 내에서 완결을 시킵니다.

주문 생성에 대한 복잡한 비즈니스 로직을 여기 createOrder 안에 전부 다 응집해놓는 거죠.
그래서 앞으로 뭔가 주문을 생성하는 것과 관련되면 createOrder를 들어가면 딱 여기만 고치면 되겠죠.
자 그래서 createOrder를 만들었습니다.
그 다음,

주문 취소는 이미 배송된 상품은 주문하지 못한다는 비즈니스 로직에 대한

이 check 로직이 엔티티(Order 클래스) 안에 있죠. 그 다음에 상태를 바꿔주고 루프를 돌면서 그 orderItem에 대해서 cancel()을 치면,

orderItem에 있는 cancel()로직이 Item의 재고를 원복 시켜주죠. 그렇게 해서 cancel의 addStock을 가보면,

다시 주문할 때는 removeStock이 호출되고요. addStock하면 재고가 다시 늘어나게 됩니다.
마지막으로 전체 주문 가격 조회 로직은

totalPrice를 OrderItem에 가서

개별 주문 가격과 주문 수량을 곱하기 해서 나온 결과가 OrderItem의 getTotalPrice로 얻을 수 있거든요.

이거를 더해가주고 반환을 해주게 됩니다.
그리고 지금 하나가 빠졌는데 OrderItem에 생성자 메서드를 하나 만들어 줄거에요. OrderItem도 이제 생성하는게 단순하지 않거든요.

이 생성자 메서드에는 세팅해줄 만한 게 필수로 Item이랑 OrderPrice랑 Count가 있겠죠. Order 같은 경우는 주문 상품을 만들 때 굳이 어떤 주문인지 필수로 세팅해야하는 것은 아닙니다. 일일히 세팅하기 귀찮겠죠.
그리고 한번 어떤 주문인지 알면 그 뒤에 주문서는 바뀌지 않습니다. 반면, Item이나 가격, 수량은 주문 상품을 생성할 때 언제든 유동적으로 바뀔 수 있죠.
근데 여기서 질문이 들어올 수 있습니다.

'Item에 보면 price가 있으니까 그 가격을 OrderPrice로 하면 되지 않나요? 굳이 set해서 받는 이유가 뭔가요?'
왜냐면 뭐 쿠폰 받거나 할인되거나 이럴 수 있잖아요. Item이나 OrderPrice, Count가 바뀔 수 있기 때문에 이건 매개변수로 따로 가져가는 게 맞아요. 지금 시스템은 단순하기 때문에 할인 같은 건 없긴 하지만요.

그 다음에 지금 로직을 보시면 중요한 게 다 세팅하고 난 다음, 넘어온 것만큼 item에서 재고를 까줍니다.
그 다음에 return orderItem; 이렇게 해주면 orderItem을 생성을 하면서 재고가 count만큼 줄어드는 걸 볼 수 있습니다.
이건 실제 Order랑 관련이 있겠죠. Order를 만들 때 보면,

Member랑 Delivery랑 OrderItem... 이렇게 넘어오죠. 그래서 createOrder 하기 전에 OrderItem을 만드는 로직에서 이미 재고를 까고 넘어 온다고 보시면 될 것 같아요.
자 여러분 이렇게 하면 Order와 OrderItem에 대한 생성 메서드부터 시작해서 핵심 비즈니스 로직이 완료가 됩니다.

사실 참고로 여기 있는 생성 메소드는 실무에서 훨씬 복잡하겠죠. 여기서 넘어오는 OrderItem도 사실 막 이렇게 넘어오는 게 아니라, 뭔가 파라미터나 DTO가 막 넘어오면서 더 복잡하게 들어오고, 그냥 이 생성자 메서드 안에서도 OrderItem을 아예 생성해서 넣을 수도 있고요. 그게 이제 상황에 따라서는 더 깔끔한 방법이 됩니다.
자 이번 시간은 여기까지 하고, 다음 시간에는 주문 리포지토리를 개발하겠습니다.
OrderItem을 실제 create 하고 이런 로직들은 서비스 만들 때 다 보여드릴게요.
'스프링 > 실전! 스프링 부트와 JPA 활용1' 카테고리의 다른 글
| 주문 서비스 개발 (0) | 2024.05.08 |
|---|---|
| 주문 리포지토리 개발 (0) | 2024.05.04 |
| 상품 서비스 개발 (0) | 2024.04.29 |
| 상품 리포지토리 개발 (0) | 2024.04.29 |
| 상품 엔티티 개발(비즈니스 로직 추가) (0) | 2024.04.29 |