일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- Junit 5
- FCM
- 직장인 회고
- Service 계층 테스트
- ExceptionResolver
- JPA
- MySQL
- 2025 계획
- db
- softeer
- 갓생
- Spring
- proxyFactory
- Test Doulbe
- enumSet
- Java
- 인프콘2023
- 일상
- 테크쇼
- Test
- modelmapper
- 소프티어
- Test code
- 자바
- Coputer Science
- 공룡책
- Server
- mapstruct
- OS
- 2024회고
- Today
- Total
공부내용공유
Service 계층, Business 계층 본문
서론
백엔드를 처음 공부하면서 접했던 계층 개념은 가장 흔히 쓰이는 presentation
, service
, repository
이 3개의 계층으로 나눈 패턴이었다,
입사를 하고나서 개발중인 프로젝트들 중 위 계층들 외에 business
계층을 추가하는 것을 보았고, 집에서 추가로 공부하면서도 business
계층을 사용하는 예시를 보게되었다.
해당 계층을 사용하면서 내가 느꼈을 때 어떤 이점이 있는지 간단하게 정리하고자 이 글을 작성하였다.
(다른 장점, 틀린 내용이 있다면 댓글로 얘기해주세요!)
본론
일단 가장 핵심적인 목적은 도메인 로직
만 들어있는 service 계층을 만들기 위해서이다. 도메인 로직만을 남김에 따라
- 인수인계를 할 때 처음 프로젝트를 보는 사람이 도메인 로직을 체력 낭비 없이 빠르게 파악할 수 있다.
- 기획자와 같은 팀원이 해당 로직을 보면서 같이 얘기할 수 있다.(로직 검증이 편해진다.)
- private 메서드가 덜 사용되고 테스트 하기 편해진다.
- reader, commander와 같은 분리를 통해 좀 더 응집성 있는 코드가 된다.
복잡한 네임드 쿼리 명료화
요즘 spring을 사용하는 프로젝트는 대부분 편리한 기능들을 다양하게 제공해주는 jpa, spring data jpa를 사용하고 이 중named query
도 정말 많이 쓰이는 기능일거라고 생각한다.
해당 기능은 복잡한 쿼리에는 사용하기 조금 애매하지만 필드 2~3개 정도를 필요로하는 쿼리에는 활용하기 정말 좋다.
Optional<Item> findByUserIdAndBoxIdAndItemNo(Long userId, Long boxId, Long itemNo);
물론 해당 도메인을 잘 이해하고 있고 같이 개발하는 동료는 이러한 메서드 쿼리만 봐도 충분히 이해가 가능할 것이다.
다만 business
계층을 도입한 것은 위에서 얘기했던 것처럼 service
계층에 도메인 로직만 남겨놔야 하는데 해당 메서드 쿼리는
불필요한 정보 (repository
계층에서 사용될 정보들 userId와 boxId는 index를 위해 사용된다고 가정하자.) 를 가지고 있다.
여기서 business
계층을 도입하게 되면
//business
@Componet
@RequiredArgsConstructor
class ItemReader{
private final ItemRepository itemRepository;
public getItem(Long userId, Long boxId, Long itemId){
findByUserIdAndBoxIdAndItemNo(userId, boxId, itemId)
.orElseThrow(() -> new Exception());
}
}
//service
@Service
@RequiredArgsConstructor
class ItemService{
private final ItemReader itemReader;
public getItem(Long userId, Long BoxId, Long ItemNo){
//사용자 검증
Item item = itemReader.getIten(userId, boxId, itemId);
//item 검증
return ItemDto.of(item);
}
}
이렇게 service 계층에는 getItem 과 같이 도메인 정보만 남겨두고 DB를 위한 정보든 business 계층에 숨길 수 있다.
테스트하기 쉬운 코드
sercie 코드를 작성하다 보면 private 메서드를 자주 작성하게 된다. 예시를 간단하게 들자면
@Service
@RequiredArgsConstructor
class ItemService{
private final ItemRepository itemRepository;
pulbic Item updateItem(Long itemId, String name){
//업데이트할 아이템
Item item = itemRepository.findById(itemId).orElseThrow(()->new Exception());
item.updateName(name);
//중복 검증 (이름, 가격이 같은 아이템은 존재할 수 없다.)
isDuplicated(item.getName(), item.getPrice());
return item;
}
private void isDuplicated(String name, Long price){
Optional<Item> optionalItem = itemRepository.findByNameAndPrice(name, price);
if(optionalItem.ifPresent()){
throw new Exception();
}
}
}
이렇게 중복을 확인하는 코드를 메서드로 분리하려 하면 private 메서드로 분리하게 된다.
이는 테스트 코드를 작성할 때 중복검증 로직 자체를 테스트 할 수 없고 해당 메서드를 사용하는 다른 메서드들의
테스트에 모두 중복 검증을 넣어주는 방식으로 작성해야 한다.
이때 business계층으로 분리하여 ItemReader class에 해당 메서드를 두면
@Service
@RequiredArgsConstructor
class ItemService{
private final itemReader;
pulbic Item updateItem(Long itemId, String name){
//업데이트할 아이템
Item item = itemReader.getItem(itemId);
item.updateName(name);
//중복 검증 (이름, 가격이 같은 아이템은 존재할 수 없다.)
itemReader.verifyDuplication(item.getName, item.getPrice);
return item;
}
}
@Component
@RequiredArgsConstructor
class ItemReader{
pubic getItem(Long itemId){
...
}
public verifyDuplication(String name, Long price){
...
}
}
이러한 식으로 기존 private 메서드를 public으로 분리하고 해당 메서드를 위한 테스트 작성이 가능해진다.
결론
현재까지 사용하면서 내가 느낀 특징, 장점은 이렇게 크게 2가지 정도가 있었다.
분명 business
계층을 도입하는게 오버한 경우도 있을 것이다. 상황에 따라 팀원들과 컨벤션을 잘 만들어
적절히 계층을 사용하는 것이 좋아 보인다!
반박, 의견 대 환영입니당~