인테리어 시공/수리/자재 구매를 하나의 플랫폼에서 해결할 수 있는 서비스
4명, 2025-11 ~ 2025-12
| 구분 | 사용기술 |
|---|---|
| 프론트엔드 | React |
| 백엔드 | Spring Boot / Spring Security / JWT |
| 데이터베이스 | MariaDB / JPA / QueryDSL |
전문가 목록을 조회하면서 매칭 건수와 리뷰 평점을 한 번에 가져오기 위해 Expert, Matching, Review 테이블을 조인하는 과정에서 카디널리티 곱이 발생했고, 매칭수와 평점도 기대하는 값보다 훨씬 높은 값이 나왔다.
프로젝트 중
나는 이 문제를 단순히 데이터 중복 문제로 받아들였기 때문에 Expert, Matching, Review에 해당하는 각각의 PK에 대한 중복제거를 하는 방향으로 해결하기로 했다.
countDistinct()를 각 PK에 적용시켜 데이터 중복 문제를 해결했지만, 프로젝트가 다 끝나고 정리하는 중, 이 방법이 카디널리티 곱을 해결하는 것이 아니고, 데이터가 많아지면 DB 성능이 좋지 않다는 것을 알게되었다.
프로젝트 후
좋지 않은 방향으로 문제를 해결한 것 같아서 다른 방법이 있나 검색하던 중, 두 가지 방법을 알게 되었다.
1) 역정규화
전문가 테이블에 매칭수와 평점 컬럼을 추가해서 같이 관리하는 방법이다. 물론 매칭이 성사될 때와 리뷰가 작성될 때 매번 업데이트 해줘야 하지만, 목록을 조회하는 기능을 비교할 수 없을정도로 많이 사용하기 때문에 비용이 더 저렴하다고 생각했다.
2) 서브쿼리
서브쿼리를 사용해서 전문가별로 그룹화 하여 집계를 마치고 메인 테이블과 조인하는 방법이다. 역정규화 없이도 카디널리티 곱 현상을 차단할 수 있다.
테스트 결제라고 해도 계좌에 잔액이 부족하면 결제가 안되는 줄 알고, 브라우저에서 개발자 도구를 통해 결제 금액을 수정한 상태로 결제를 진행해보니, 수정된 금액으로 결제가 되었다.
결제가 완료되어 주문이 들어간걸 테스트 하는 상황이었지만, 엄청 큰 취약점을 발견하게 되었다.
브라우저 메모리에 저장된 상태값을 그대로 API 요청 바디에 담아 전송하고 있었기 때문에, 서버에 위변조된 데이터가 전송이 되어 그대로 결제 금액으로 받아들여 결제가 되는 것이었다.
클라이언트에서 받은 최종 결제 금액을 믿지 않고, 서버쪽에서 최종 결제 금액을 다시 계산해서 산출하는 과정으로 구현을 했다.
장바구니 페이지에서 브랜드별로 상품을 보여주고, 각 브랜드의 배송 정책에 따라 배송비를 계산하여 화면에 표시해야 하는 복잡한 요구사항이 있었다.
우선 백엔드에서 브랜드별로 그룹화된 DTO 리스트를 전달해주어 기초 구조는 잡혀 있었다.
화면상에서 배송 단위에 따라 셀을 병합하고 배송비를 따로 계산하기에는 배송정책으로 나눠지지 않은 리스트 형태였다.
그리고 배송비는 고정이 아니라 수량 조절에 따라 실시간으로 변하는 상태가 있어야 했고 재 계산하는 로직이 필요했다.
브랜드별로 1차 그룹화를 백엔드에서 CartBrandDTO를 설계해서 브랜드의 정보, 배송비 정책, 해당 브랜드의 상품 목록을 묶어 프론트엔드에 전달했다.
전달받은 상품 목록을 렌더링 하기 전에 묶음/개별을 기준으로 다시 한번 그룹화 하는 로직을 구현했다.
reduce를 사용해 동일한 배송 정책을 가진 상품들을 배열로 묶어, 테이블 구성 시 rowSpan을 적용하여 셀을 병합했다.
보안 의식
클라이언트의 데이터는 언제든 위변조될 수 있다는 사실을 체감했고, 시스템 안정성을 위해 서버에서 재검증 로직이 필수라는 원칙이 생겼다.
실무적 변화
이미 완성된 구조에 복잡한 배송 정책이 추가되는 상황을 겪어서 고생했지만, 실무와 유사한 요구사항 변경을 경험한 것 같아서 긍정적으로 생각한다.
개발 방향성
단순히 기능을 구현하기보다, 어떻게 하면 더 안전하고, 덜 복잡하고, 유지보수하기 편한 구조를 만들 수 있을까에 대한 고민이 생겼다.