본문 바로가기
포트폴리오/공공 화장실 정보 제공 웹 서비스 (BE)

공공 화장실 정보 제공 서비스(급똥) 프로젝트 중간 회고

by Yura 🌼 2025. 3. 27.
728x90

프로젝트 소개

  • 프로젝트 인원 4명 (프론트엔드 2명, 백엔드 2명)
  • 프로젝트 : 사용자 위치기반 공공 화장실 정보 제공 서비스
  • Github: https://github.com/GeubDDong
  • 기술 스택
    • Frontend: Vite, React, Typescript, Styled-Components, React-Query, Zustand, Vercel
    • Backend: NestJS, Typescript, TypeORM, Swagger, Nginx, JWT, RDS, PostgreSQL, Redis
    • DevOps: Docker, Docker Comopose, EC2, Amazon Route53, Github Actions

나의 역할

  • ERD
  • 백엔드 API 개발
  • CI / CD 자동화
  • README 문서화

기억에 남는 구현

[ 가장 가까운 화장실을 우선 정렬하여 보여주는 기능 ]

프론트에서 사용자의 위도와 경도를 받으면, 백엔드는 해당 위경도를 기준으로 사용자가 보고 있는 지도 화면 범위를

top, bottom, left, right 값을 쿼리파라미터로 전달합니다.

 

백엔드는 해당 좌표 내에 존재하는 화장실만 필터링한 후 PostgreSQL의 공간 함수를 사용하여 사용자의 현재위치와 각 화장실 간의

실제 거리를 계산해서 사용자 기준 가장 가까운 화장실을 최상단에 보여줍니다.

⇒ 위 기능이 기억에 남는 이유는 단순 위경도 차이로 정렬하는 게 아닌 ST_Distance_Sphere(공간함수)를 활용하여 사용자 경험을 고려했기 때문입니다.

 

그동안 프론트엔드로 프로젝트를 진행하면서 사용자 중심으로 개발하는 게 익숙해져서 이번 백엔드 프로젝트에서도 ‘복잡한 데이터를 어떻게 하면 사용자에게 직관적으로 전달할 수 있을까’ 하는 생각으로 자연스럽게 이어졌고 실질적으로 도움이 되는 기능을 고민하고 기술적으로 구현하는데 큰 도움이 된 것 같습니다.


트러블 슈팅

1️⃣  [ 엑셀로 저장된 데이터를 DB에 가공하여 넣기 ]

어떻게 엑셀파일을 가져와서 DB로 저장할지가 가장 큰 이슈였습니다.

  1. 초기 서비스 배포 시, 엑셀파일을 불러와서 DB에 한 번만 저장하게 구현할 건지?
  2. API 요청이 있을 때 작동하게 만들 것인지?

결론부터 말씀드리면, 저희는 2번째 방식을 택했습니다. 그 이유는 현재 구현된 데이터는 서울, 인천에 한정되어 있고 이를 전국으로 늘릴 생각이었기 때문에 엑셀파일이 업데이트되어도 다시 반영할 수 없는 1번 방식보다는 API 호출 한 번이면 변경이 쉽고 유지보수 측면에도 더 좋은 2번 방식이 나을 것이라고 생각했습니다.

 

2️⃣ [ 중복되는 주소 데이터를 어떻게 가공할 것인지 ]

→ case 1 > csv 파일에서 address로 하나로 합치기

→ case 2 > SQL에 있는 COALESCE 사용하기

저희 데이터에는 도로명주소와 지번주소가 함께 저장되어 있었고,

도로명주소+지번주소가 모두 있는 것도 있고 둘 중에 한 가지 주소만 있는 경우도 함께 존재했습니다.

서울, 인천의 데이터 (약 10,000건) 라면 case1이 말이 되겠지만, 전국 데이터로 확장한다면 (56,666건)→ 약 5배 정도 차이가 발생합니다.

따라서, 이 서비스에서는

  1. 도로명 주소가 없는 데이터의 경우 → 지번 주소를 사용
  2. 도로명주소+지번주소가 모두 있는 경우 → 도로명 주소를 우선적으로 사용

즉, case2를 선택하기로 했습니다.

그렇게 하면 나중에 필터, 검색 기능을 구현하게 될 때, 특정 키워드로 검색하기 쉬워지고 사람들이 자주 사용하는 도로명 주소를 빨리 검색할 수 있기 때문에 case2로 구현하게 되었습니다!

 

3️⃣ [ 중복되는 API 호출을 어떻게 줄일 수 있을지 ]

 

(왼)메인페이지와 (오)상세페이지에서 중복되는 데이터 값

 

메인페이지에서는 사용자 위치를 기반으로 근처 화장실 목록을 불러와야 했고, 상세페이지에서는 같은 화장실의 더 많은 세부정보가 들어있어 두 API가 반환해야 할 필드가 일부 겹치면서 “DTO를 각각 만들 것인가, 하나로 통일할 것인가”에 대한 고민이 생겼습니다.

 

처음엔 메인용, 상세용 DTO를 따로 분리할 생각이었지만, 필드 중복과 응답 포맷이 불일치할 문제가 생길 수 있어 상세 페이지에서 사용하는 DetailToiletResponseDto 구조를 기반으로 메인 API에서도 필요한 필드만 추려 반환하는 방식으로 구현했습니다.

 

즉, 메인에서 ToiletDto를 반환하되, 내부 필드는 상세 Dto에서 필요한 부분만 선택적으로 가져오는 형태로 구현했고, 이를 통해 코드 재사용성을 높일 수 있었습니다.


앞으로 해결할 과제들 …

1️⃣ 우리 서비스에서 어떤 부분을 캐싱하는 게 좋을까?

  1. 우선 위치기반 조회는 반복적으로 요청할 가능성이 높고 실시간성 요구가 낮기 때문에 메인페이지사용자 주변 화장실 목록을 캐싱할 예정입니다.
  2. 상세페이지가 자주 바뀌지 않고, 반복적으로 접근할 가능성이 높기 때문에 이곳도 캐싱할 예정입니다.
  3. 좋아요 수 또한 실시간 반영이 아니어도 큰 문제가 없기 때문에 캐싱 예정입니다.

2️⃣ 위도, 경도가 없는 값들을 어떻게 처리할 것인지?

현재 위경도가 없는 데이터가 꽤 많습니다. (거의 40% 이상이ㅠㅠ..)

따라서, 사용자가 직접 주소를 입력하여 검색이 가능하게 할 예정입니다.

입력된 주소를 Geocoding API로 변환하여 위경도를 뽑아내고 지도 이동이 가능하게 할 것입니다.

이렇게 변환된 위, 경도 값을 Redis에 캐싱하고 이후에 DB에 저장하여 재사용하도록 구현할 예정입니다.

 


회고를 마무리하며…

약 한 달 동안 진행되는 프로젝트라 ‘시간상 어쩔 수 없었다!’로 귀결되는 회고가 되고 말았네요..ㅎㅎ

기술적 회고가 처음인지라 생각보다 구구절절 쓰게 되었네요..!

더 자주 회고를 작성하면서 보기 좋은 완성도 높은 글로 발전하겠습니다. 🙂

긴 글 읽어주셔서 감사합니다!! 🙇‍♀️

728x90