전체코드 : https://gitlab.com/galid1/jpa-commerce
이번 시간에는 지난 포스팅 https://galid1.tistory.com/729 에서 완성한 엔티티를 기반으로, Service, Repository
를 개발해보도록 하겠습니다.
1. Repository
도메인 객체
를 지속적으로 사용하기 위해서는, 이를 영구적으로 저장하고, 다시 그것을 가져오는 방법이 필요합니다. 즉, RDBMS, MYSQL, 로컬파일
과 같은 물리적 저장소에 도메인 객체를 보관해야합니다. 이를 위한 도메인 모델이 바로 Repository
입니다.
public interface XRepository {
public X findById(Long xId);
public void save(X x);
public void deleteById(Long xId);
}
Repository
는 애그리거트 단위로 도메인객체
를 저장하고 조회합니다. 애그리거트 안에는 필요한 엔티티, 벨류 객체가 모두 포함되어있기 때문입니다. 예를들면, 위와 같은 기능을 제공합니다.
1.1 Repository의 위치
Service, Controller와 같이 도메인객체를 사용하는 코드는 Repository
를 통해서 도메인 객체에 접근합니다. 도메인 모델 관점에서 Repository는 도메인 객체를 저장하고 조회하는 기능을 추상화한 고수준 모듈입니다. 따라서 Repository는 도메인과 같은 패키지에 위치하며, 이를 구현하는 저수준 모듈은 infra
패키지에 위치하게 됩니다.하지만, 저희는 Spring Data JPA를 이용할것이므로, 별도의 구현체를 직접 만들지 않을것 입니다.
1.2 Repository 구현
구현이랄게 없습니다. Repository Interface를 생성한뒤 JpaRepository<Entity, KeyType>을 상속
받으면 끝입니다. 나머지 Repository들도 위와 같이 구현을 진행합니다.
2. Service
응용서비스는, 사용자의 요청을 처리하고 결과를 반환하는 역할을 합니다. 주요 역할은 아래와 같습니다.
- 도메인 객체간의 실행 흐름을 제어한다.
- 트랜잭션 처리를 한다.
도메인 객체간의 실행 흐름제어
public Result doService(Request req) {
1. 리포지토리에서 애그리거트를 조회합니다.
X x = xRepository.findById(req.getId());
2. 애그리거트를 통해 도메인 로직을 수행합니다.
Result result = x.doSomething(req.getParam());
3. 결과를 반환합니다.
return result;
}
응용서비스는, 주로 도메인 객체간 흐름을 제어하기 때문에, 보통은 위와 같은 형태로 작성됩니다.
트랜잭션 처리
응용서비스에서는, 트랜잭션을 처리하는데요, 응용서비스에서, 여러 애그리거트를 통해 하나의 논리적인 기능을 구성하기 때문입니다. 따라서, 응용서비스에서 이루어지는 처리는 원자적으로 수행되어야 하기 때문에 이곳에서 트랜잭션 처리를 맡게 됩니다.
주의사항
응용서비스를 구현할때의 주의사항이 있습니다. 절대로 도메인 로직을 구현하면 안된다는 것
입니다. 도메인 로직은 당연히, 도메인 계층에서 구현을 해야하는데요, 좀더 구체적인 이유는 아래와 같습니다.
도메인 로직이 분산된다.
도메인 로직이 분산된다면 어떨까요? 도메인 로직이 도메인 계층에만 존재하는 것이 아니기 때문에, 도메인로직에 대한 수정이 있을때, 서비스 계층까지 같이 수정이 될 수도 있습니다.
도메인 로직이 중복된다.
도메인 로직을 응용서비스에서 구현하는 경우, 같은 도메인로직을 여러곳에서 반복해서 구현하는 경우도 더러 발생하게 됩니다. 당연히, 수정시 여러 클래스를 같이 수정해야하는 불편함이 발생합니다.
2.1 Service 구현
ItemService
첫번째로 구현해볼 서비스는 ItemService 입니다. 요구사항을 통해서 천천히 하나씩 구현해보도록 하겠습니다.
- 상품 등록
상품등록은 간단합니다. 사용자의 요청인
AddItemRequest
를 통해서, ItemEntity(루트 애그리거트)
를 생성하고, ItemRepository
의 save() 메소드만 호출해주면 됩니다. 그후 반환된 Entity의 id를 반환합니다.메소드 상단을 보시면 @Transactional
을 보실 수 있는데요, 스프링의 트랜잭션 추상화를 통해 간단히 @Transactional
어노테이션을 붙이기만 한다면, 해당 메소드를 실행할때, 트랜잭션을 시작하고, 종료할때 정상적으로 수행이 완료되면 commit을 해주게 됩니다.
- 상품 조회
상품조회 역시 repository를 통해서 애그리거트를 가져와, 사용자가 원하는 형태로 가공해 반환을 해주면 끝입니다.
@Transactional 클래스 레벨, 메소드 레벨
클래스 레벨에 @Transactional
을 입력하면, 해당 클래스 내의 모든 메소드에 @Transactional
을 부여한것과 같이 동작을 합니다. 이때 readOnly= true
를 부여하면, 읽기 작업을 수행하는 메소드에 최적화를 할 수 있습니다. 그리고, 쓰기 작업이 이루어지는 메소드에 별도로 @Transactional
을 부여하면, readOnly의 default 값이 false이므로, 일반 트랜잭션이 수행됩니다.
OrderService
주문서비스는 조금 복잡해보이지만 무척 간단합니다. 사용자의 주문요청(OrderRequest)를 통해서 OrderEntity
를 생성한 뒤, Repository를 통해 저장함으로써 주문을 생성합니다.
주문취소
주문 취소 역시, Repository 주문 도메인 객체
를 구한뒤, OrderEntity의 cancel()을 호출하는 것이 끝입니다.