Querydsl
- JPQL의 컴파일 시점 오류 확인 불가
- 쿼리 가독성과 타입 안정성을 보장하여 유지보수 용이
디렉토리 구조
| 기능 | 메서드 | 엔드포인트 | Query Params | status |
|---|---|---|---|---|
| 주문 생성 | POST | /orders | 201 | |
| 주문 단건 조회 | GET | /orders/{orderId} | 200 | |
| 허브 주문 목록 조회 (페이지네이션) | GET | /orders/hubs/{hubId} | page, size, sort | 200 |
| 업체 주문 목록 조회 (페이지네이션) | GET | /orders/companies/{companyId} | page, size, sort | 200 |
| 주문 수정 | PATCH | /orders/{orderId} | 204 | |
| 주문 취소 | PATCH | /orders/{orderId}/cancel | 204 | |
| 주문 삭제 | DELETE | /orders/{orderId} | 200 |
| 기능 | 메서드 | 엔드포인트 | Query Params | status |
|---|---|---|---|---|
| 상품 생성 | POST | /products | 201 | |
| 상품 단건 조회 | GET | /products/{productId} | 200 | |
| 상품 전체 목록 조회 (페이지네이션) | GET | /products | page, size, sort, productName | 200 |
| 허브 등록 상품 목록 조회 (페이지네이션) | GET | /products/hubs/{hubId} | page, size, sort, productName | 200 |
| 업체 등록 상품 목록 조회 (페이지네이션) | GET | /products/companies/{companyId} | page, size, sort, productName | 200 |
| 상품 수정 | PATCH | /products | 204 | |
| 상품 삭제 | DELETE | /products | 200 |
-
유저 권한별 각 기능 유효성 검증
-
유저 권한별 각 기능 유효성 검증
-
DTO의
record타입 사용- 클라이언트의 요청에 대한 불변성과 안정성을 명시
-
Pageable의sort쿼리 - 정렬 조건 타입 안정화- 정해진 정렬 조건만 허용 - 예기치 못한 쿼리 또는 에러 발생 방지
- 동일한
property정렬 시 예외 발생 SortTypeENUM@Getter @RequiredArgsConstructor public enum SortType { CREATED_ASC("createdAt", Sort.Direction.ASC), CREATED_DESC("createdAt", Sort.Direction.DESC), UPDATED_ASC("updatedAt", Sort.Direction.ASC), UPDATED_DESC("updatedAt", Sort.Direction.DESC), PRICE_ASC("price", Sort.Direction.ASC), PRICE_DESC("price", Sort.Direction.DESC), ; private final String value; private final Sort.Direction direction; public static void validate(Sort sort) { Set<String> check = new HashSet<>(); for (Sort.Order order : sort) { if (!match(order)) { throw new CustomRuntimeException(ProductException.UNSUPPORTED_SORT_TYPE); } if (!check.add(order.getProperty().toLowerCase())) { throw new CustomRuntimeException(ProductException.DUPLICATED_SORT_TYPE); } } } private static boolean match(Sort.Order order) { return Arrays.stream(SortType.values()) .anyMatch(sortType -> sortType.value.equalsIgnoreCase(order.getProperty()) && sortType.getDirection().equals(order.getDirection())); } }
-
Querydsl의getOrderSpecifier()을 통한 동적 정렬 조건 쿼리 구현- 클라이언트가 여러 개의
sort쿼리를 넘기도록 하여.orderBy()에 적용private OrderSpecifier<?>[] getOrderSpecifiers(Sort sort) { List<OrderSpecifier<?>> orderSpecifiers = new ArrayList<>(); for (Sort.Order sortOrder : sort) { Order order = sortOrder.isAscending() ? Order.ASC : Order.DESC; // sort 쿼리 내 정렬 방향을 Order 객체로 파싱 PathBuilder<Product> pathBuilder = new PathBuilder<>(product.getType(), product.getMetadata()); // sort 쿼리 내 정렬 값을 QClass 내 필드값에 해당하는 값으로 매핑 orderSpecifiers.add(new OrderSpecifier<>(order, pathBuilder.getString(sortOrder.getProperty()))); } return orderSpecifiers.toArray(new OrderSpecifier[0]); }
- 클라이언트가 여러 개의
OpenFeign Invalid HTTP method: PATCH
Order서비스에서Product서비스로 주문 상품 수량 수정이나 주문 취소시 차감했던 상품의 재고를 다시 변경하기 위해PATCH메소드 사용OpenFeign은 HTTP 요청을 추상화하여 메소드 호출로 REST API 호출 가능하도록 지원해주는데 HTTP 기본 구현체가 추가 의존성 없이 작동하도록 설계됨HttpURLConnection은 별도 라이브러리 의존 없이 사용 가능하므로OpenFeign에서 사용PATCH는 비교적 최신 기능이라HttpURLConnection에서 지원이 안되는 것
- 해결방안
- 외부 HTTP 클라이언트 라이브러리를 사용하여 해결(커스텀)
- ApachHttpClient, OkHttpClient
- 하지만 각 서비스 간 내부 통신이므로 Restful 규약을 준수할 필요는 없다고 생각함
- 최종 결정
PATCH->POST변경
- 권한별 기능 설계 및 구현에 시간을 너무 사용하여
OpenFeign통신 장애 대응 로직을 충분히 구현하지 못해 아쉽다. - 각 기능의 최적화나 고도화를 진행하지 못한 점도 개선할 필요가 있다.
- 다음 프로젝트에서는 설계 단계에서 서비스의 전체적인 도메인에 대한 이해를 충분한 회의나 지식 공유등을 통해 정리하고 가는 것이 좋을 것 같다.