카테고리 없음

[CSO Project] 4 - 계단식 url 유효성 검사를 어떻게 해야할까?

wonow_ 2024. 4. 23. 20:35

ERD

erd 상황

 

item 부분 create 및 get 메서드를 만드는데, 궁금한게 생겼다.

API 명세

 

현재 url 을 이렇게 겹겹이 계단식으로 쌓아서 개발을 진행중이다.

 

이런 식으로 url을 구성하면 해당 url 에 대해서 유효성 검사를 해줘야한다.

 

url 유효성 검사?

현재 url은

 

/brand/{brandId}/category/{categoryId}/item/{itemId} 이런 계단식 형식으로 되어 있는데

 

/brand/1/category/1/item/1

이렇게 입력하고 get 맵핑으로 요청을 보내면 1 번 브랜드1 번 카테고리의 1 번 아이템을 불러온다는 것이다.

 

그럼 서비스 코드를 이렇게 쓰면 되려나?

public ItemDto getItem(Long itemId) {
	
    return itemRepository.findById(itemId).orElseThrow...; // 이하생략
}

 

위 처럼 쓰면 안 된다.

 

위 형태로 코드를 작성하면 /brand/2/category/5041/item/1 을 입력해도 똑같은 결과가 나온다.

즉 brand 와 category의 유효성 검사를 안하기 때문에 잘못된 주소를 입력해도 상관없이 값이 온다는 것

 

public class ItemServiceImpl {

    private final BrandService brandService;
    private final CategoryService categoryService;

    public ItemDto getItem(Long brandId, Long categoryId, Long itemId) {

        brandService.checkBrand(brandId);
        categoryService.checkBrand(categoryId);

        return itemRepository.findById(itemId).orElseThrow...; // 이하생략
    }
}

public class BrandServiceImpl {

	private final BrandRepository brandRepository;
    
	public void checkBrand(Long brandId) {
    	
        if (!brandRepository.existsById(brandId) {
        	throw new Exception();
        }
    }
}

// 이하 생략

 

즉 위와 같이 exist 쿼리를 날려서 확인을 해줘야만 /brand/1/category/1/item/1 이라는 주소로만 해당 아이템을 불러올 수 있다.

 

근데 여기서 드는 궁금점이 있다.

 

이렇게 작업하면 메서드 하나당 쿼리 세번을 날리는데, 그냥 쿼리 한번으로 해결하면 안되나?

 

public ItemDto getItem(Long categoryId, Long brandId, Long itemId) {
	
    return itemRepository.findByIdAndBrandIdCategoryId(itemId, brandId, categoryId).orElseThrow(
    	() -> new Exception();
    );
}

 

이렇게 작성하고 레포지토리 딴에서

 

@Query("SELECT"
		+ " new org.domain.dto.ItemDto(i.name)" // 매개변수 생략
        + " FROM Item i"
		+ " JOIN i.category i"
        + " JOIN c.brand b"
        + " WHERE i.id = :itemId AND i.category.id = :categoryId AND i.category.brand.id = :brandId"
)
Optional<ItemDto> findByIdAndBrandIdAndCategoryId(@Param("itemId") Long itemId, @Param("brandId") Long brandId, @Param("categoryId") Long categoryId);

 

이렇게 검색하면 되는 거 아닌가? 그러면 쿼리 한번으로 해결이 될텐데?

 

과연 이렇게 간단할까?

 

SoftDelete 가 추가 된다면?

만약에 Brand 에 SoftDelete 가 추가 된다고 해보자

 

그러면 단순 url 에 대한 유효성 검사가 아니라 SoftDelete 검사까지 해줘야한다.

 

public class BrandServiceImpl {

	private final BrandRepository brandRepository;
    
	public void checkBrand(Long brandId) {
    	
        if (!brandRepository.existsByIdAndIsDeletedIsFalse(brandId) {
        	throw new Exception();
        }
    }
}

 

이렇게 검사를 해줘야 된다.

 

이렇게 하면 Brand 에서 발생한 수정사항을 BrandService 랑 BrandRepository 둘 다 바꿔주기만 하면 된다.

하지만

아까와 같이 쿼리로 유효성 검사를 하는 경우에는

@Query("SELECT"
		+ " new org.domain.dto.ItemDto(i.name)" // 매개변수 생략
        + " FROM Item i"
        + " JOIN i.category i"
        + " JOIN c.brand b"
        + " WHERE i.id = :itemId AND i.category.id = :categoryId AND i.category.brand.id = :brandId AND i.category.brand.isDeleted = false"
)
Optional<ItemDto> findByIdAndBrandIdAndCategoryId(@Param("itemId") Long itemId, @Param("brandId") Long brandId, @Param("categoryId") Long categoryId);

 

Brand 에서 발생한 수정사항을 ItemRepository에서 수정을 해야한다.

Brand 의 FK를 갖고 있는 테이블이 많아진다면?

Brand 에서 발생한 수정사항이 다른 OtherService OtherRepository 까지 전부 수정해줘야 한다.

유지보수가 떨어진다는 뜻이다.

 

만약 요구 사항으로 SoftDelete 가 아닌 다른 유효성 검사도 추가 된다고 하면 한번 더 연관 테이블의 Brand 관련 유효성을 모두 수정해줘야한다.

 

성능상으로 조금 떨어진다 하더라도, 개발 편의가 엄청 오른다.

 

이건 취향차이일까? 연관 테이블이 깊어진다면, 그만큼 나가는 쿼리도 많아질텐데?

물론 계단식 url을 안쓰면 해결이 되기는 한다. 근데, 꼭 써야되는 상황에서는?

 

어떤 방법을 사용해야할지 고민이 많이 된다.