Skip to content

인근 지역 맛집 추천 및 리뷰하는 서비스 API

Notifications You must be signed in to change notification settings

SigLee2247/lunch-map-service

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

79 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

지리기반 맛집 추천 웹 서비스 ‘Lunch Map’

image

💡 공공 데이터를 활용하여 지역 음식점 목록을 자동으로 업데이트 합니다.
사용자 위치에 따라 맛집 및 메뉴를 추천하여 더 나은 다양한 음식 경험을 제공하려 합니다.



구현 내용

  1. 위치 기반 맛집 조회 API
  2. 데이터 파이프라인 (데이터 수집, 전처리, 저장, 자동화)
  3. 시군구 맛집 조회 API
  4. 맛집 상세 정보 조회 API
  5. 사용자 API 구현

프로젝트 실행 방법

  1. 프로젝트 Clone
https://github.com/SigLee2247/lunch-map-service.git
  1. 프로젝트 파일 이동
cd lunch-map-service
  1. 프로젝트 실행
./gradlew build
java -jar build/libs/lunchmapservice-0.0.1-SNAPSHOT.jar

ERD

ERD Link: https://www.erdcloud.com/d/jxePYzGRdPpPsytQY

image


Skills


요구사항 분석

요구사항 : 요구사항 링크


API Reference

User Domain

사용자 회원가입 [POST] /api/users
  • 계정명, 패스워드 입력하여 회원가입


Request

전달 방식 Name Type Description
Body username String 사용자 계정명
Body password String 사용자 비밀번호
{
  "username": "test",
  "password": "abcd1234"
}

Response

StatusCode Message Description
201 사용자 등록 성공
400 필수값이 입력되지 않았습니다. 사용자 등록 시 필수값 누락
409 이미 사용 중인 E-mail 입니다. 사용자 등록 시 중복 E-mail
{
  "data": {
    "userId": 1
  },
  "message": "OK",
  "code": 201,
  "timeStamp": "2023-11-02 13:15:11"
}
사용자 로그인 [POST] /api/login
  • 계정명, 패스워드 이용한 로그인 JWT 토큰 활용


Request

전달 방식 Name Type Description
Body username String 사용자 계정명
Body password String 사용자 비밀번호
{
  "username": "test",
  "password": "abcd1234"
}

Response

StatusCode Message Description
200 로그인 성공
401 Unauthorized 로그인 시 문제가 발생함
사용자 설정 업데이트 [PATCH] /api/users/{userId}
  • 사용자의 위치인 위도, 경도점심 추천 기능 사용 여부를 업데이트

  • 변경을 원하지 않는 파라미터는 생략 후 전달


Request

전달 방식 Name Type Description
Body lat Double 사용자 위도
Body lon Double 사용자 경도
Body serviceAccess Enum
(LUNCH, DINNER, NONE)
사용자 점심 추천 여부
Body username String 사용자 이름
{
  "latitude": "test",
  "longitude": "abcd1234",
  "serviceAccess": "LUNCH",
  "username": "siglee"
}

Response

StatusCode Message Description
200 사용자 정보 업데이트 성공
401 Unauthorized 로그인 시 문제가 발생함
사용자 정보 조회 [GET] /api/users/{userId}
  • 패스워드를 제외한 모든 사용자 정보 반환


Request

전달 방식 Name Type Description
Path Variable usersId Long 사용자 식별자

Response

StatusCode Message Description
201 사용자 정보 조회 성공
401 Unauthorized 로그인 시 문제가 발생함
{
  "data": {
    "id": 1,
    "userName": "testName",
    "latitude": 132.123456,
    "longitude": 32.58694,
    "serviceAccess": "LUNCH"
  },
  "message": "OK",
  "code": 200,
  "timeStamp": "2023-11-02 13:15:11"
}

Restaurant Domain

맛집 상세 조회 [GET] /api/restaurants/{restaurantId}
  • 맛집 모든 필드평가 상세 리스트 포함하여 조회

  • 평가 상세 리스트는 최신순으로 조회


Request

전달 방식 Name Type Description
Path Variable restaurantId Long 맛집 식별자

Response

StatusCode Message Description
200 맛집 상세 조회 성공
404 맛집 정보가 존재하지 않습니다. 맛집 데이터 없음
401 Unauthorized 로그인 시 문제가 발생함
{
  "data": {
    "restaurantId": 1,
    "restaurantName": "아도니스",
    "lotNumberAddress": "경기도 가평군 상면 행현리 602-3번지",
    "roadNameAddress": "경기도 가평군 상면 수목원로 314-2",
    "zipCode": "12448",
    "longitude": 37.7516678333,
    "latitude": 127.3588076752,
    "location": {
      "cityName": "경기도",
      "countryName": "가평군",
      "longitude": 37.7516678333,
      "latitude": 127.3588076752
    },
    "averageScore": 0.0,
    "ratingList": [
      {
        "content": "리뷰 본문",
        "username": "작성자 이름",
        "score": 0
      }
    ]
  },
  "message": "OK",
  "code": 200,
  "timeStamp": "2023-11-02 13:15:11"
}
시군구 맛집 목록 조회 [GET] /api/restaurants
  • 해당 도시, 시군구 내의 맛집 목록 조회


Request

전달 방식 Name Type Description 필수값
Parameter cityName String 도시(도, 시(광역시)) False
Parameter countryName String 시, 군, 구 False

Response

StatusCode Message Description
200 시군구 맛집 목록 조회 성공
401 Unauthorized 로그인 시 문제가 발생함
{
  "data": {
    "content": [
      {
        "restaurantId": 1, 
        "restaurantName": "아도니스",
        "lotNumberAddress": "경기도 가평군 상면 행현리 602-3번지", 
        "roadNameAddress": "경기도 가평군 상면 수목원로 314-2",
        "longitude": 37.7516678333, 
        "latitude": 127.3588076752,
        "location": {
          "cityName": "경기도",
          "countryName": "가평군",
          "longitude": 37.7516678333,
          "latitude": 127.3588076752
        },				
        "averageScore": 0.0
      }
    ],
    "pageable": {
      "offset": 0,
      "size": 30,
      "totalElements": 30,
      "last": true,
      "numberOfElements": 100,
      "first": true,
      "totalPages": 10
    }
  },
  "message": "OK",
  "code": 200,
  "timeStamp": "2023-11-02 13:15:11"
}
위치 기반 맛집 목록 조회 [GET] /api/users/nearby
  • 해당 위도, 경도 위치를 기반으로 반경 km 내의 맛집 목록 조회

  • 요청 좌표와 식당 사이의 거리인 거리순평점순 조회 가능


Request

전달 방식 Name Type Description 필수값
Parameter currentLongitude String 추천 받을 위치 경도 True
Parameter currentLatitude String 추천 받을 위치 위도 True
Parameter range Double 추천 맛집 반경 True
Parameter sorting String 정렬 기준 False (거리순)

Response

StatusCode Message Description
200 위치 기반 맛집 목록 조회 성공
400 필수값이 입력되지 않았습니다. 맛집 조회 시 필수값 누락
401 Unauthorized 로그인 시 문제가 발생함
{
  "data": [
    {
      "id": 1,
      "locationId": 1,
      "name": "아도니스",
      "lotNumberAddress": "경기도 가평군 상면 행현리 602-3번지", 
      "roadNameAddress": "경기도 가평군 상면 수목원로 314-2",
      "zipCode": 1234,
      "longitude": 37.7516678333,
      "latitude": 127.3588076752,
      "averageScore": 0.0
    }
  ],
  "message": "OK",
  "code": 200,
  "timeStamp": "2023-11-02 13:15:11"
}
맛집 평가 [POST] /api/restaurants/evaluation/{restaurantId}
  • 사용자가 작성하는 음식점 리뷰
  • 평점은 0 ~ 10까지의 자연수만 줄 수 있으며 입력 필수
  • 간단한 리뷰는 필수가 아니다.

Request

전달 방식 Name Type Description 필수값
Boody score Integer 평점 True
Parameter content String 리뷰 댓글 False
Path Variable restaurantId Long 맛집 식별자 True
Login 로그인 여부 Long 로그인 시 사용자 식별자 조회 True

Response

| StatusCode | Message | Description | | --- | --- | | | 200 | | 리뷰 등록 성공 | | 400 | 필수값이 입력되지 않았습니다. | 평점 작성 누락 | | 401 | Unauthorized | 로그인 시 문제가 발생함 |

{
  "data": [
    {
      "restaurantId": 1
    }
  ],
  "message": "OK",
  "code": 200,
  "timeStamp": "2023-11-02 13:15:11"
}

구현 및 성능 개선

RawRestaurant, Restaurant 테이블의 분리

  • RawRestaurant 테이블은 공공 데이터 포털에서 제공하는 API 요청으로 받아온 데이터만을 저장하는 원본 데이터베이스
  • Restaurant 테이블은 RawRestaurant 테이블의 일부 데이터와, Lunch Map 서비스에서 사용되는 데이터를 저장을 위한 데이터베이스
  • 두 테이블 분리 시 insert, update가 2배 더 발생 but 테이블 분리를 통해 보다 유연하고 독립적인 테이블 주고 가질 수 있음

대용량 insert, update에 대한 쿼리 최적화 160s -> 34s 단축

image
  • JDBC properties 설정을 통한 쿼리 최적화
    • jdbc.batch_size, order_inserts, order_updates 설정 → 여러 개의 SQL 쿼리 종류 별 정렬
    • rewriteBatchedStatements=true 설정 → JDBC 내부적으로 각각의 insert문을 하나의 bulk insert로 수정
  • Restaurant, Location 테이블의 키 매핑 전략 IDENTITYSEQUENCE로 수정
    • Restaurant, Location 테이블은 대용량 insert, update가 발생하는 테이블
    • IDENTITY 전략의 경우 앞의 설정에도 불구하고 하나의 쿼리가 하나의 DB Connection으로 발생 → 키 seq를 DB로부터 미리 할당 받아오도록 하기 위해 SEQUENCE 전략으로 수정

Scheduling을 위한 라이브러리 선택

  • 고려한 라이브러리 종류
    • Quartz Scheduler (외장형 라이브러리)
    • Spring Boot Scheduling (내장형 라이브러리)
Quartz Scheduler 장단점

장점

  1. 기본 제공되는 Spring Boot Scheduling 대비 세부적인 설정 가능
  • 서로 의존성 있는 스케줄 작업의 실행 및 실패 시 간단하게 제어 가능
  • 즉 작업 실패 시 재동작 트리거를 손쉽게 설정할 수 있음
  1. DB 기반으로 스케줄러 간의 Clustering 기능 제공 (로드밸런싱 사용 시 장점)
  2. In-memory Job Scheduler 제공
  3. 다양한 플러그인의 존재
  4. Scheduler 와 Job의 분리
  • Job이 추가 되었을 때 스케줄러를 재배포 하게 되면 스케줄러가 중단되고 실행되는 작업이 많을 때는 재배포 타이밍을 잡기 어려워진다. → 이를 해결하기 위해 Job 과 Scheduler의 분리를 통해 별도 배포 가능

단점

  1. 외부 의존성 사용으로 인한 의존성 추가
  2. 클러스터링을 위해 DB Table 생성
  3. Clustering 기능 제공하지만 단순한 random 방식이라 완벽한 Cluster 간의 로드 분산 X
  4. Fixed Delay 타입 보장 X (실행된 이후 특정 시점 뒤 실행 방식)
  5. 내장형 Scheduler 대비 불필요한 설정 추가
  • 내장형 Scheduler 사용 시 간단한 어노테이션으로 사용이 가능
Spring Boot Scheduling 장단점
  • 스프링에서 제공하는 내장형 스케줄러
  • 특정 주기로 실행할 작업 정의 및 관리 가능
  • 1개의 스레드를 활용해 스케줄링 진행 → 반복 실행해도 동일한 스레드에서 작업을 진행

장점

  1. 내장 라이브러리로 추가적인 의존성 불필요
  2. @Scheduled 어노테이션을 통해 간단한 제어 가능

단점

  1. 로드 밸런싱 등 특정 APP의 인스턴스를 여러 개 생성 시, 같은 스케줄링이 여러 번 실행되는 것을 방지하기 위해 ShedLock 필요 → @TryLock 어노테이션으로 가능
  2. 인메모리 스케줄러로 스케줄링 tasks 관련 정보는 메모리에서 관리 → 어플리케이션이 재시작되거나 중단되면 기존 tasks 정보 모두 사라짐, 즉 하나의 어플리케이션 메모리에서 동작하므로 분산 시스템이나 MSA 구조에는 적합X
  3. task 간 의존성을 부여하기 힘듦, 예를 들어 A task 실행 → B task 실행과 같은 tasks 간 실행 순서를 정의하기 어려우며 특정 task 실행 실패 시 동작을 지정할 수 없음

사용한 Scheduling

Spring boot에서 내장하고 있는 Spring Boot Scheduling 사용

  • 프로젝트 내의 스케줄링의 규모가 크지 않기 때문에 내장형 Scheduling으로도 구현할 수 있을 것 이라 판단
  • 추후 고도화 작업을 통해 필요 시 Quartz Scheduler 변경 예정
    • 로드 밸런싱 등을 통해 같은 기능을 하는 다수의 APP 인스턴스의 활성화
    • 추후 Scheduling 간의 복잡한 의존성이 발생하거나, 실패 시 진행할 의존성 추가가 필요한 경우 변경

Spring Boot Scheduler 공식문서

Quartz 참고 문서


SequenceDiagram

평점 추가 시퀀스 다이어 그램

sequenceDiagram
	controller ->> service : evaluateRestaurant()
	service ->> repository : findById()
	repository -->> service : entity
	service ->> restaurant : addRating()
	restaurant ->> restaurant : calculateAverageScore()
	Note over restaurant, restaurant: avgScore 계산 <br> 소숫점 첫째자리 반올림 
	service --> controller : response Data 전달

Loading

About

인근 지역 맛집 추천 및 리뷰하는 서비스 API

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages