1. DTO 길이 제한 추가
코드래빗 리뷰에서 DTO에 길이 제한이 없으면 DB 컬럼 제약조건 위반이 flush 시점에 늦게 발생한다는 지적이 있었다. 요청 단계에서 미리 검증하도록 @Size 어노테이션을 추가했다.
// CompanyCreateRequest.java
@NotBlank
@Size(max = 200, message = "업체명은 200자 이하여야 합니다.")
private String name;
@NotBlank
@Size(max = 255, message = "주소는 255자 이하여야 합니다.")
private String address;
// ProductCreateRequest.java
@NotBlank
@Size(max = 200, message = "상품명은 200자 이하여야 합니다.")
private String name;
2. MSA 간 통신 구조 이해
주문 서비스에서 상품 재고 차감이 필요한 상황에 대해 팀원과 논의했다.
결론: 주문 서비스에서 FeignClient로 상품 서비스의 재고 차감 API를 호출하는 방식으로 연동한다.
주문 생성 → FeignClient → POST /products/{productId}/stock/decrease
상품 서비스는 재고 차감 API만 제공하면 되고, 호출은 주문 도메인 담당자가 구현한다.
3. 검색/정렬/페이징 구현
3-1. Repository 수정
Spring Data JPA의 메서드 이름 기반 쿼리를 활용해 검색과 페이징을 구현했다.
// CompanyRepository.java
Page<Company> findByNameContainingAndDeletedAtIsNull(String name, Pageable pageable);
Page<Company> findByHubIdAndNameContainingAndDeletedAtIsNull(UUID hubId, String name, Pageable pageable);
// ProductRepository.java
Page<Product> findByNameContainingAndDeletedAtIsNull(String name, Pageable pageable);
Page<Product> findByCompanyIdAndNameContainingAndDeletedAtIsNull(UUID companyId, String name, Pageable pageable);
Containing 키워드를 사용하면 LIKE '%값%' 쿼리가 자동으로 생성된다.
3-2. Service 수정
페이징 단위를 10/30/50으로 제한하는 로직을 추가했다.
@Transactional(readOnly = true)
public Page<Product> search(UUID companyId, String name, int page, int size, String sortBy) {
// 페이징 단위 기본 10, 30이나 50으로 변경 가능
size = List.of(10, 30, 50).contains(size) ? size : 10;
Sort sort = Sort.by(Sort.Direction.DESC, sortBy);
Pageable pageable = PageRequest.of(page, size, sort);
if (companyId != null) {
return productRepository.findByCompanyIdAndNameContainingAndDeletedAtIsNull(companyId, name, pageable);
}
return productRepository.findByNameContainingAndDeletedAtIsNull(name, pageable);
}
3-3. Controller 수정
기존 findAll() 메서드를 검색/정렬/페이징을 지원하는 방식으로 교체했다. 허브별/업체별 조회도 같은 엔드포인트에서 통합 처리한다.
// CompanyController.java
@GetMapping
public ResponseEntity<APIResponse<Page<CompanyResponse>>> findAll(
@RequestParam(required = false) UUID hubId,
@RequestParam(required = false, defaultValue = "") String name,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(defaultValue = "createdAt") String sortBy) {
Page<CompanyResponse> responses = companyService.search(hubId, name, page, size, sortBy)
.map(CompanyResponse::from);
return ResponseEntity.ok(APIResponse.success(responses));
}
3-4. API 사용 예시
# 전체 조회
GET /products
# 상품명 검색
GET /products?name=테스트
# 업체별 조회 + 검색
GET /products?companyId={uuid}&name=테스트
# 페이징 단위 변경
GET /products?size=30
# 정렬
GET /products?sortBy=name
GET /products?sortBy=createdAt
# 모두 적용
GET /products?companyId={uuid}&name=테스트&size=10&page=0&sortBy=createdAt
4. 트러블슈팅 — 한글 정렬 순서 이슈
sortBy=name으로 정렬 시 가나다 순서가 아닌 이상한 순서로 정렬되는 현상이 발생했다.
원인: PostgreSQL이 한글을 유니코드 코드포인트 기준으로 정렬하기 때문에 우리가 기대하는 가나다 순서와 다르게 동작한다.
결론: 버그가 아니라 PostgreSQL collation 특성이다. 가나다 순서가 필요하다면 LC_COLLATE 설정 변경이 필요하지만 현재 단계에서는 넘어간다.
5. build.gradle 의존성 관리 개선
팀 회의를 통해 멀티모듈 프로젝트의 build.gradle 관리 방식을 개선했다.
원칙:
- 루트 — lombok, test 등 모든 모듈 공통 의존성만 관리
- common — 공유 도메인 의존성 독립 관리
- 각 모듈 — common 의존 + 해당 모듈에서만 필요한 의존성만 선언
// 루트 build.gradle subprojects
dependencies {
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
// common/build.gradle
bootJar { enabled = false }
jar { enabled = true }
// company/build.gradle
dependencies {
implementation project(':common')
implementation 'org.springframework.boot:spring-boot-starter-validation'
}
'Spring 개발일지' 카테고리의 다른 글
| [대규모 AI 시스템 프로젝트] 최종 개발 마무리 (0) | 2026.04.07 |
|---|---|
| [대규모 AI 시스템 프로젝트] 서비스 연동 및 추가 개선 (0) | 2026.04.06 |
| [대규모 AI 시스템 프로젝트] 상품 도메인 구현 및 리팩토링 하기 (0) | 2026.04.02 |
| [대규모 AI 시스템 프로젝트] MSA에서 공통 코드 관리하기 + 업체 도메인 구현 (1) | 2026.04.01 |
| [대규모 AI 시스템 프로젝트] MSA에서 공통 코드 관리하기 (2) (1) | 2026.03.31 |