학원 수료 프로젝트로 3개월간 5명이서 만든 숙소 예약 서비스다. 에어비앤비를 벤치마킹해서 호스트가 숙소를 등록하고 게스트가 예약할 수 있는 플랫폼을 만들었다.
프로젝트 개요
- 기간: 2019년 4월 ~ 6월 (약 3개월)
- 인원: 5명
- 내 역할: Back-end, Server, Database (전담) / Front-end (보조)
- 서비스: 숙소 호스팅 및 예약 플랫폼
기술 스택
- Backend: Java, Spring MVC, MyBatis
- Frontend: JavaScript, Bootstrap, Handlebars
- Database: MariaDB
- Server: AWS EC2, S3
- 결제: I’mPort (아임포트)
- API: 다음 주소/지도 API
내가 맡은 기능들
1. 숙소 등록 시스템
호스트가 단계별로 숙소 정보를 입력하는 기능. fullpage.js로 스텝 바이 스텝 UI를 구현했다.
핵심 구현:
- 다음 주소 API로 주소 검색 → 지도 API로 위도/경도 변환
- 파일 업로더로 이미지 다중 업로드
- Handlebars 템플릿으로 동적 렌더링
겪은 문제: 다음 주소 API는 좌표를 안 준다. 주소만 준다. 그래서 주소 검색 후 다음 지도 API의 geocoder를 다시 호출해서 좌표를 뽑아냈다. API 두 개를 연결해서 쓴 첫 경험이었다.
2. 숙소 검색 (반경 검색)
지역명이나 역 이름으로 검색하면 해당 위치 반경 5km 내 숙소를 찾아준다.
-- 위도/경도 기반 반경 검색 쿼리
SELECT * FROM room
WHERE (6371 * acos(cos(radians(#{lat}))
* cos(radians(latitude))
* cos(radians(longitude) - radians(#{lng}))
+ sin(radians(#{lat}))
* sin(radians(latitude)))) < 5
Haversine 공식이라는 걸 처음 알았다. 지구가 둥글어서 단순 거리 계산이 안 된다는 것도.
3. 결제 및 예약
아임포트를 연동해서 실제 결제가 되는 시스템을 만들었다.
결제 플로우:
- 클라이언트 → 서버: 숙소 번호, 체크인/아웃 날짜 전송
- 서버: 주문번호, 금액 생성 후 응답
- 클라이언트: 아임포트로 결제 진행, 승인번호 수신
- 클라이언트 → 서버: 승인번호 전송
- 서버 → 아임포트: 승인번호로 결제 정보 조회
- 서버: 금액 검증 후 DB 저장 (불일치 시 결제 취소)
동시성 이슈:
같은 날짜에 두 명이 동시에 예약하면? synchronized 블록으로 해결했다. 지금 생각하면 DB 레벨에서 처리하는 게 맞지만, 당시엔 이게 최선이었다.
4. 로그인 로그 시스템
Spring Interceptor로 로그인 시도마다 기록을 남겼다.
@Component
public class LogInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) {
request.setAttribute("startTime", System.currentTimeMillis());
return true;
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler, Exception ex) {
// IP, 요청시간, 지연시간, 성공/실패 여부 저장
}
}
관리자가 로그인 시도 현황을 볼 수 있게 했다. 보안 쪽을 처음 신경 써본 기능이었다.
5. AWS 인프라 구축
EC2 두 대(웹 서버, DB 서버)와 S3를 분리 구성했다.
[Client] → [EC2 - Tomcat/Apache] → [EC2 - MariaDB]
↓
[S3 - Images]
팀원들이 각자 로컬 DB 쓰다가 데이터가 안 맞아서 고생했다. AWS에 공용 DB 올리니까 바로 해결됐다. 인프라 분리의 필요성을 체감한 순간.
6. Spring Scheduler
매일 자동으로 돌아가는 배치 작업들:
- 06:00: S3와 DB 비교해서 고아 파일 삭제
- 10:00: 오늘 체크인 예약 상태 변경
- 12:00: 오늘 체크아웃 예약 상태 변경
@Scheduled(cron = "0 0 6 * * *")
public void cleanupOrphanFiles() {
// S3 파일 목록과 DB 비교 후 삭제
}
파일 업로드 중 에러 나면 S3에만 파일이 남는다. 이런 쓰레기 데이터를 자동으로 정리하게 했다.
배운 것들
기술적으로
- Spring MVC 구조: Controller → Service → DAO 패턴
- MyBatis: XML 매퍼로 SQL 관리하는 방식
- AWS: EC2, S3 기본 사용법
- 결제 연동: PG사 API 연동 플로우
- API 조합: 여러 외부 API를 엮어서 쓰는 방법
협업 측면
- Git 충돌: 5명이 같은 저장소에 푸시하면서 merge 지옥 경험
- DB 공유: 로컬 DB의 한계, 공용 서버의 필요성
- 역할 분담: 백엔드/프론트엔드 경계에서의 협업
아쉬운 점
지금 다시 보면 부끄러운 코드가 많다.
- 커밋 메시지: “버그 수정”이 대부분… 뭘 수정했는지 알 수가 없다
- 예외 처리: try-catch로 다 감싸고 에러 로그만 찍는 패턴
- 동시성 처리: synchronized보다 DB 락이 맞았다
- 테스트 코드: 전무. 수동 테스트만 했다
그래도 이때 삽질한 덕분에 “이러면 안 되겠구나”를 배웠다.
결과
학원 수료 발표에서 좋은 평가를 받았다. 시연 영상도 찍고, 베타 서비스로 실제 배포까지 해봤다.
3개월간 밤새면서 만든 프로젝트. 코드는 엉망이었지만, 처음으로 “서비스”라는 걸 만들어본 경험이었다. 이때 느낀 성취감이 개발자로 계속 가야겠다고 결심하게 만들었다.