스프링 부트 - 주문 Service 개발 학습 내용
"인프런 - 실전! 스프링 부트와 JPA 활용1 강의를 듣고 작성한 글 입니다."
www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-JPA-%ED%99%9C%EC%9A%A9-1#
실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발 - 인프런
실무에 가까운 예제로, 스프링 부트와 JPA를 활용해서 웹 애플리케이션을 설계하고 개발합니다. 이 과정을 통해 스프링 부트와 JPA를 실무에서 어떻게 활용해야 하는지 이해할 수 있습니다. 초급
www.inflearn.com
OrderService 생성 및 코드 작성
우선 아래의 코드를 한 번 보자.
- OrderItem.java
// 생성 메소드
public static OrderItem createOrderItem(Item item, int orderPrice, int count){
OrderItem orderItem = new OrderItem();
orderItem.setItem(item);
orderItem.setOrderPrice(orderPrice);
orderItem.setCount(count);
item.removeStock(count); // 주문한 수량 만큼 재고에서 차감
return orderItem;
/*
주문이 들어오면 우선적으로 이 생성 메소드에서 주문 상품에 대한 정보들을 세팅해준 후(orderItem),
주문 수량에 맞춰서 현재 상품 재고 수량을 차감시킨 뒤, Order 도메인 클래스에 있는
createOrder 메소드에 주문 상품에 대한 데이터를 넘겨준다.
*/
}
- Item.java
// 재고 증가
public void addStock(int quantity){
this.stockQuantity += quantity;
}
// 재고 감소
public void removeStock(int quantity){
int restStock = this.stockQuantity - quantity;
if(restStock < 0) { // 0 보다 적어지면 안되기에 유효성 검증 코드를 넣는다.
throw new NotEnoughStockException("need more stock");
}
this.stockQuantity = restStock;
}
- OrderService.java
private final OrderRepository orderRepository;
private final MemberRepository memberRepository;
private final ItemRepository itemRepository;
// 주문
@Transactional
public Long order(Long memberId, Long ItemId, int count){
Member member = memberRepository.findOne(memberId);
Item item = itemRepository.findOne(ItemId);
// 배송 정보 생성
Delivery delivery = new Delivery();
delivery.serAddress(member.getAddress());
// 실제로는 배송지 정보를 직접 입력하는 편이 좋으나, 예제 간편화를 위해 멤버 데이터 주소를 그대로 가져온다.
// 주문 상품 생성
OrderItem orderItem = OrderItem.createOrderItem(item, item.getPrice(), count);
// 주문 생성
Order order = Order.createOrder(member, delivery, orderItem);
// 주문 저장
orderRepository.save(order);
// cascade 옵션으로 인해 내부에 연관관계로 매핑된 Entity 데이터들까지 함께 persist 된다.
return order.getId();
}
// 취소
@Transactional
public void cancelOrder(Long orderId) { // 주문을 취소할 땐 주문에 대한 id 값만 받아온다.
// 주문 Entity 조회
Order order = orderRepository.findOne(orderId);
// 주문 취소
order.cancel();
}
위의 주문 비즈니스 메소드를 보면 어딘가 이상한 점이 있다.
원래 createOrder 메소드에서 orderItem, delivery 와 같은 데이터들을 활용하려면 일단 두 데이터들이 persist 되어서 영속성 컨텍스트 위에 올라가 있어야 하지 않은가?
그런데 위의 코드를 보면 어디를 보아도 orderItem, delivery 데이터들을 JPA 영속성 컨텍스트에 persist 하는 코드가 존재하지 않는다.
하지만 그럼에도 불구하고 메소드는 정상적으로 잘 동작한다.
어째서일까?
그 이유는 바로 Order 도메인 클래스에서 Delivery, OrderItem 도메인 클래스와 연관관계를 매핑할 때 CascadeType.ALL 옵션을 사용했기 때문에 Order 도메인 클래스가 영속성 컨텍스트에 persist 되면, cascade 옵션으로 인해 내부에 있는 orderItem, delivery 와 같은 연관관계로 매핑된 데이터들 까지 함께 persist 되기 때문이다.
참고 : evan-development.tistory.com/43?category=941005
영속성 전이(CASCADE) 와 고아 객체 - 1
"인프런 - 자바 ORM 표준 JPA 프로그래밍 강의를 듣고 작성한 글 입니다." 영속성 전이 : CASCADE 특정 Entity 를 영속 상태로 만들 때 연관된 Entity 도 함께 영속 상태로 만들고 싶으면 사용하는 옵션, 즉
evan-development.tistory.com
Cascade 의 범위에 대해서는 고민들이 많다.(어디까지를 cascade 해야할까?)
보통 명확하게 자르기는 애매한데, 어떤 개념에서 쓰면 좋을까?
Order 같은 경우 Order 가 Delivery, OrderItem 을 관리하는데, 이와 같은 경우처럼 명확하게 서로 참조하는 관계에서 주인이 서로 명확한 경우에 사용하는 것이 좋다.
(유일하게 서로 종속적인 연관관계를 가질 경우 - 다른 도메인 들이 참조할 수 없는 경우)
만약 Order 뿐만이 아니라 다른 도메인 에서도 Delivery 와 같은 데이터들을 참조하여 사용할 수 있다면 Cascade 옵션을 함부로 사용해서는 안된다.
createOrderItem 과 같은 생성 메소드를 사용할 때 참고사항
협업 프로젝트를 하게 되면 개발자 마다 코딩하는 스타일이 다를 수 있다.
이 같은 경우 이번 예제에서는 createOrderItem 메소드를 처음 호출할 때 파라미터로 필요한 데이터들을 한번에 받았지만, 다른 코딩 스타일을 가진 개발자의 경우 아래와 같은 방식으로 코드를 작성할 수도 있다.
--------------------------------------------------------------
OrderItem orerItem = new OrderItem();
orderItem.setCount(count);
.....
--------------------------------------------------------------
이런 경우 기능을 똑같이 수행 하더라도, 서로 다른 코딩 스타일이 많이 퍼지기 시작하면 이후에 코드를 유지 보수하기가 힘들어진다.
그런 사태를 막기 위해 protected 키워드를 통해 생성 메소드 이외의 방법으로 객체를 생성하는 방식을 제한시켜 줄 수 있다.
- OrderItem.java
protected OrderItem() { // 디폴트 생성자로 객체르 생성하는 것을 막는다.
}
위와 같이 protected 메소드를 통해 디폴트 생성자로 객체를 생성하는 것을 막아주면, 생성 비즈니스 메소드를 통해서만 객체를 생성하게 되어 서로 다른 방식으로 똑같은 객체를 생성한 코드가 많이 퍼지는 바람에 나중에 유지 보수를 하기 힘들어지는 경우를 방지 할 수 있다.
(JPA는 기본 스펙상 protected 까지 디폴트 생성자를 만들 수 있게 허용해준다.)
그런데 위와 같은 디폴트 생성자 또한 어노테이션 만으로 적용시켜 줄 수 있다.
- OrderItem.java
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class OrderItem{
.......
}
- Order.java
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Order {
....
}
** 참고
주문 서비스의 주문과 주문 취소 메소드를 보면 비즈니스 로직 대부분이 Entity 에 있다.
서비스 계층은 단순히 Entity 에 필요한 요청을 위임하는 역할을 한다.
이처럼 Entity 가 비즈니스 로직을 가지고 객체 지향의 특성을 적극 활용하는 것을 도메인 모델 패턴이라고 한다.
martinfowler.com/eaaCatalog/domainModel.html
P of EAA: Domain Model
martinfowler.com
반대로 Entity 에는 비즈니스 로직이 거의 없고 서비스 계층에서 대부분의 비즈니스 로직을 처리하는 것을 트랜잭션 스크립트 패턴이라고 한다.
martinfowler.com/eaaCatalog/transactionScript.html
P of EAA: Transaction Script
| P of EAA Catalog | Transaction Script Organizes business logic by procedures where each procedure handles a single request from the presentation. For a full description see P of EAA page 110 Most business applications can be thought of as a series of tra
martinfowler.com
둘 중 어느쪽이 더 낫다는 것이 아니라, 상황에 따라 어떤 방식이 더 유지보수 하기 쉬운지 고민해봐야 한다.