국비 학원에서 진행하는 파이널 팀 프로젝트가 종료되었다.
약 한달간 6명의 인원으로 진행되었고, 우리 팀은 숙소와 맛집 검색/추천/예약 서비스를 제공하는 웹 사이트를 주제로 선정하였다.
전체적인 틀은 '여기어때' 사이트를 벤치마킹하되, 맛집과 관련된 서비스는 '다이닝코드'를 참고하였다.
Spring boot를 프레임워크로, MVC 개발방식을 사용하였다.
개발 환경
OS : Window 10 64bit
Framework : Spring Boot
Build : Maven
WAS : Apache Tomcat 9.0
IDE : Eclipse 2021-12 (4.22.0)
Language : Java 17.0.2
DB : Oracle 21c EE (JDBC : ojdbc 8), myBatis
Browser Support : Chrome
HTML , CSS, Javascript with BootStrap 5.2.0, jQuery
Version Management : Git, Github
역할 분담
기능 별로 역할을 분담하여 해당 기능에 대한 프론트-백 작업을 담당한 사람이 모두 구현하는 방식으로 진행했다.
내가 맡은 역할은 '숙소 검색 및 상세조회'였고, 개발업무 외에는 DB 변경 쿼리 기록, 회의록, DB에 넣을 데이터파일 등 공유드라이브 관리를 맡았다.
프로젝트 후반에 팀원 한 분이 개인 진로 문제로 나가시고, 그 외에도 미구현 상태인 부분들이 있어서
추가적으로 '맛집 검색 및 상세조회' 또한 내가 맡아서 진행했다.
그리고 숙소-맛집을 통합해서 위치기반으로 검색할 수 있는 기능을 추가구현했다.
데이터를 화면에 표현할 때에는 페이징처리, 무한스크롤링(프론트에서 배열에 담아두고 스크롤이 바닥에 닿을 때마다 새로 컨텐츠 제공) 방식을 모두 사용해보았다.
ajax로 데이터를 받아올 때에는 페이징처리를 위한 객체에 대한 요청과, 검색데이터 요청을 별도로 처리했는데, 하나의 컨트롤러에서 처리했어도 좋았을 것 같다.
가장 핵심 기능인 숙소 검색에서는, 뷰를 담당하는 하나의 jsp파일을 사용하되
접근 방식에 따라 다른 parameter를 받는다는 점을 이용해 jstl <c:if> 태그로 '검색조건'을 다르게 출력하도록 했다.
결과적으로 숙소를 검색하는 옵션으로는
숙소유형, 지역(카테고리), 숙박일정, 금액, 정원, 특정 공용시설 유무, 특정 객실시설 유무, 특정 태그 유무, 평점순/거리순/가격순 정렬이 있다.
검색조건인 form태그의 입력요소 값들을 serialize() 메소드를 사용해 쿼리스트링으로 만든 뒤 ajax로 get요청을 보냈고,
위 조건들을 각 변수의 값으로 받는 Criteria객체를 요청핸들러메소드의 매개변수로 정의했다.
그리고 myBatis에서 if태그와 choose when 태그를 이용하여 다이나믹 쿼리를 구성하였다.
숙박일정에 따라서 그 일정에 예약이 가능하고, 객실 관련 조건을 만족해야지만 해당 숙소가 조회될 수 있게 하였다.
또 사용자에게 제공하는 데이터 중 '최저금액' 또한 해당 숙소가 조회될 수 있게끔 한 객실의 가격을 조회하도록 하였다.
내 위치로부터의 거리를 제공하고, 거리 순으로 정렬하는 문제에 대해서는
일단 프론트에서 geolocation을 이용해 내 위치를 구하고(위치정보 허용 안할 시 서울 중심으로 처리)
삼각함수를 이용해 두 좌표 간의 거리를 구하는 함수를 오라클에서 CREATE하여 다이나믹 쿼리에서 값을 계산하고, 조회하도록 했다.
쿼리가 다소 (내가 느끼기에는) 복잡해졌지만, 다양한 방식으로 숙소 데이터를 조회하더라도 새로운 코드를 짤 필요가 없어서 좋았다.
DB 조회하는 단계에서 잘 처리하면 이후 비즈니스 로직이 훨씬 편리해질 수 있다는 걸 느낀 것 같다.
하지만 아직 알지 못하는 '인덱스' 개념을 공부하고 적용해보면 좋을 것 같다.
'내 주변의 가장 가까운 맛집'을 카테고리 별로 보여주고, 키워드 검색 시 통합검색 페이지로 이동한다.
하지만 기본적인 백엔드 로직은 숙소와 같다.
기본적으로 geolocation을 통해 획득한 '내 위치'를 중심으로 가까운 장소 정보를 페이징처리해 보여준다.
지도 우측 상단의 버튼을 클릭하면 현재 보고 있는 지도의 중심을 검색 기준으로 변경할 수 있다.
숙소 상세조회의 약도를 클릭하면 그 숙소를 중심으로 한 다른 숙소,맛집 정보를 조회할 수 있다.
맛집 상세조회의 약도를 클릭하면 그 맛집을 중심으로 한 다른 숙소, 맛집 정보를 조회할 수 있다.
통합검색에서는 myBatis에서 keyword와 일치해야 하는 컬럼 대상을 숙소 객실시설명, 공용시설명, 식당 메뉴 등 다양하게 적용하여
좀 더 실제 검색서비스처럼 기능할 수 있도록 해보았다.
ex) '수영장'을 검색하면 수영장을 공용시설로 가진 곳을, '냉면'을 검색하면 냉면을 메뉴로 가진 곳을 조회할 수 있다.
찜하기 기능
숙소/맛집을 조회하는 모든 페이지(내 찜 목록 포함)에서 로그인한 사용자가 해당 장소를 찜하기/취소할 수 있도록 하였다.
이미 찜한 장소는 채워진 하트 아이콘이 출력되도록 했는데,
화면에 제공할 데이터를 먼저 조회한 뒤, 사용자 식별번호와 장소 식별번호를 이용하여 찜하기 상태를 조회해서 서비스 클래스에서 별도로 for문을 돌려 처리했다. 이 부분도 더 좋은 방법이 있을 것 같아 생각해봐야겠다.
장소 평점에 리뷰 점수 반영하기
리뷰를 등록했을 때, 해당 장소의 평점에 그 리뷰에서 매긴 점수까지 즉각 반영되도록 했다. (장소 평점 = 리뷰점수 총합 / 리뷰개수 가 되도록)
해당 메소드에 대해서는 스프링에서 제공하는 @Transactional
어노테이션을 사용하여 여러 번의 DB작업이 일관성을 가지도록 하였다.
그리고 google chart
api를 이용해 장소의 평점 분포에 대한 통계를 상세조회 페이지에서 보여주게 했다.
리뷰 점수 별점아이콘으로 표현하기
별점아이콘은 부트스트랩의 아이콘 라이브러리를 사용했는데,
서버로부터 '리뷰 점수'만 받아와서 jstl, el로 표현할 수도 있겠지만 *.5점에 대한 '반개짜리 별점 아이콘'도 표현하려면 내용이 복잡해졌다.
그래서 n번째 별점아이콘에 대한 정보를 제공하는 클래스를 별도로 만들어서 화면에서는 reviewRateIcon.star1
(1번째 별점아이콘에 대한 정보) 과 같은 방식으로 해당 아이콘의 클래스명을 바로 획득할 수 있도록 했다.
구현하기에는 훨씬 편리했는데, 뷰-모델의 경계를 흐리는 태도인지 생각해보아야겠다.
리뷰 작성 경과시간 표현하기
숙소, 맛집 모두 상세조회 페이지에서는 javascript에서 moment.js
라이브러리를 활용해
작성시점으로부터 경과한 시간을 '방금전', '6분전', '1시간전'과 같은 형식으로 출력되도록 하였다.
그 외에는 추가적으로 인기숙소 조회, 인기숙소의 최신 리뷰 조회 등의 기능과 페이지를 만들어보았고
화면 표현에 있어서도 swiper
라이브러리를 사용해 이미지 슬라이드 만들기, 카카오맵의 커스텀오버레이 활용하기, 마커이미지 커스텀으로 만들기 등 이전에 해보지 못한 것들을 시도해보았다.
중복되는 비즈니스 로직 코드
숙소,맛집이 로직 자체는 같은데 처음부터 각각 테이블, vo, service, mapper를 나눠서 작성해서 세부기능을 구현할 때 같은 내용의 메소드를 별도로 정의해주어야 하는 경우가 많았다. 맛집 관련 테이블, 클래스들을 초반에 내가 세팅하지 않은 상태였기도 하고, 후반부에 급하게 대신 구현을 하다보니.. 일단은 중복이 많더라도 그대로 진행했는데 이 점에 대해 아쉬움이 크다.
리뷰를 조회하는 부분에서는 criteria에서 조회 대상이 숙소인지, 식당인지 여부를 문자열로 저장해 전달하고
그에 따라 다르게 데이터를 조회하도록 해 보았는데,
전반적으로 공통되는 기능들을 객체지향 개념을 활용해서 정리하지 못한 것 같다.
팀 프로젝트의 완성도 기여
내가 맡은 부분들은 기본 화면 구현, 기본 기능, 세부 화면 구현, 세부 기능 순으로 진행하여 일정에 차질이 없었던 것 같다.
그래서 마감 이전에 여유로운 일정으로 오류를 수정하고, 추가 기능으로 통합검색 페이지도 만들어볼 수 있어서 좋았다.
그러던 와중에 다른 미구현 기능들에도 참여하게 되었지만, 급하게 작업을 하게 될 수밖에 없었다.
팀 프로젝트인 만큼, 좀 더 일찍부터 팀 프로젝트의 완성도에 기여하는 걸 중점으로 두고 작업을 했으면 어땠을까 아쉬움도 든다.
(리뷰, 문의 기능 관련, CSS 완성도 등)
공유 툴의 활용
이번에는 ERD를 작성할 때 erdCloud
라는 웹상에서 서비스하는 공유 ERD 작성도구를 사용해보았다.
세미프로젝트에서는 초반에 DB 설계를 할 때에 막막하기도 하고, 한 사람 중심으로 작성할 수밖에 없었는데
각자 담당한 기능 관련한 테이블을 동시에 작성하고 공유할 수 있어서 좋았다.
하지만 그만큼 자신이 작성하지 않은 테이블에 대한 숙지를 스스로 잘 해야한다는 점이 주의할 점인 것 같다.
그리고 한 달 정도 되는 프로젝트 기간 동안 DB의 변경사항이 꽤 많을 것을 고려하여 카카오톡이 아닌 공유드라이브에서 DB 변경문을 작성해 공유하자고 건의했는데, 구글 드라이브 사용이 익숙하지 않은 분들도 계셔서 결국엔 카톡에서 공유되는 내용들을 내가 실시간으로 공유드라이브 문서에 날짜별로 기록해두었다.
다른 팀원 분이 중간에 오라클을 초기화해서 다시 DB 세팅을 했어야 하는 상황이 있었는데, 그 때 내가 해둔 기록이 도움이 되었다고 해서 다행이었다.
그 외에도 마감 전 자잘한 오류 수정목록에 대하여 공유문서에서 완료 여부, 담당할 사람 이름을 적어서 진행해서 편리했다.
의사소통과 협업능력
늘 그렇듯 이번에도 의사소통이 중요하고, 협업 능력을 키워야겠다는 것을 느꼈다.
조 편성 시에 조장 권유를 받았는데, 세미프로젝트에서 조장을 해본 만큼 파이널프로젝트에서는 조장이 아닌 팀원으로서의 협업 경험을 해보고 싶어서 조장을 맡지 않고 팀원으로 참여하게 되었다.
오히려 팀원으로서 적극적으로 질문하고, 의견을 내면서 팀에 도움이 될 수 있을 것 같아서였다.
세미프로젝트 때보다는 눈치를 덜 보고, 그때그때 질문하고 의견을 내고자 했다.
그러다보니 오히려 다른 분들이 짜증이 나는 건 아닐까 걱정했는데, 다른 팀원분들도 소통의 중요성에 대한 공감대를 가지고 계셔서 "전혀 짜증나지 않았다"고 말해주셔서 감사했다. 그래서 서로 오류를 봐주거나 더 나은 방향을 제시할 수 있어서 좋았던 것 같다.
하지만 적성이 안맞아 나가신 분이나, 마감 직전까지 연락이 잘 되지 않고 작업내용이 없는 분도 계셨다. 그런 상황에서 앞서 얘기한 것처럼, 내가 맡은 작업 외에 다 같이 잘하는 방향으로, 그리고 프로젝트 완성도에 대한 생각을 좀 더 해보았으면 어땠을까 아쉬움도 든다.
내 입으로 말하기는 좀 그렇지만 학원에서 내가 잘하는 편이라는 칭찬을 감사하게도 많이 받았는데, 내가 맡은 부분 뿐만 아니라 프로젝트 전반적인 완성도를 함께 높이려면 나 자신의 협업 능력을 많이 향상시켜야겠구나 생각이 많이 들었다.
개인적으로 마저 보완할 것 / 스스로 해볼 것
- 전반적으로 백엔드 중복 코드 관련 리팩토링
- 페이징처리하는 조회 기능 코드 수정
- 프론트엔드 javascript 실행시점 등에 대한 공부, 마찬가지로 중복 코드 관련 리팩토링, 별도 파일로 빼서 사용하기?
- 잘못된 url입력 등에 대한 예외처리, 오류페이지 연결
- 숙소/맛집 마커이미지 다르게 하기
- open API 사용한 로그인, 결제 기능 직접 해보기
앞으로 개인적으로 보완하는 내용은 깃허브 저장소에 업데이트할 예정이다.
예약 기능 구현은 어떻게 하셨는지 알 수 있을까요?