공모전 회고록

onyoo·2022년 12월 22일
0

관광데이터 공모전 회고록

시작하며,

2022년 06월 말 시작했던 프로젝트가 2022년 10월 6일 우수상을 수상하며 마무리되었다.

아쉬웠던 점도 많았고 배워가는 점도 많았던 프로젝트를 마치며 내가 프로젝트에서 했던 작업들과 마주했던 문제들에 대해 이야기해보려고 한다.

개발자에게 제일 힘든 것은 기획

보통 회사의 경우 기획자가 따로 있기 때문에 이런 고민은 하지 않는다. 그러나 우리의 경우 해당 프로젝트를 기획자를 데려오는 등의 프로젝트를 키우는 것 보다는 현재 있는 인원에서 프로젝트를 충실히 진행해나가는 길을 선택하였다.

어찌되었든, 기획단계부터 개발자 여럿이 모여 의논을 해야했고 그 중 가장 괜찮았던 주제 맛집지도 로 선정 하였다. 해당 주제의 경우 어느정도 기획이 되어있었고 해당 주제가 공모전과도 적합하게 잘 어울린다고 생각하여 선정하였다.

기획을 하면서 가장 어려웠던 점은 해당 기능을 구현하기 위해서는 어떤 데이터가 필요하며 해당 데이터를 어떻게 얻을 수 있을지 고민하는 것이었다.

처음에 구성된 기획안에 따라 초기 구성안과 기획의도 등이 담긴 피피티를 제작하여 제출하였고.감사하게도,본선에 진출하게 되었다.

문제가 발생했던 것은 본선진출 이후였다.

바로 초기 기획안을 토대로 개발작업을 하기 위한 데이터 수집이 매우 어렵다는 사실 때문이었다.

초반의 제안서에는 다음과 같은 기능이 올라와있었다.

그러나, 맵기 라는 주관적인 특성을 객관적인 수치로 표현하기에는 어려움이 있었기 때문에 해당 부분은 기획에서 삭제되었고 비건지도 또한,타겟층이 너무 한정적이어서 기각되었다.더불어 앞서 말했던 두개의 기획 모두 여행이라는 주제와는 동떨어져있다고 생각해서 여행 이라는 키워드를 부각시킬 수 있는 방향으로 기획을 수정해나갔다.

위에서 언급했던 맵기 등의 기능이 엎어진 것 과는 별개로 단순하게 맛집지도라는 것이 메리트가 없다고 느낀 팀장이 우리 프로젝트를 사용해야하는 이유를 생각해보자고 건의하여 프로젝트의 방향성을 잡았다.
그리고 그렇게 잡힌 프로젝트의 주제는 혼밥족들을 위한 맛집지도였다.

주제의 타겟층이 명확하게 잡힌 것이다.

변경된 기획안에 따라 우리는 일단,프로젝트에 있어서 가장 필요한 데이터를 중심으로 수집하기로 하였다 바로 음식점 정보 이다.

데이터 이렇게 구하기 어려운 것이었나?

우리의 경우 기본적으로 가지고 있는 데이터가 없었기 때문에 인터넷 검색을 통해 데이터를 손수 수집하였다.
진짜, 손수 우리가 아는 맛집을 검색하여 이름,주소,전화번호,위치값 등을 수집했다.
물론 공모전에서 주는 API도 사용할 예정이긴 하지만,해당 데이터는 추후 이용하기로 하였기 때문에 음식점 데이터를 모으는 것을 우선으로 하였다.

항상 느끼는거지만 개발프로젝트를 시작하기에 앞서 가장 힘든건 발품을 팔아 데이터를 수집하는 일이다.이전에 보안관련 프로젝트를 진행하려고 했었는데 KISA에서 우리가 원하는 데이터를 배포하고 있어 해당 데이터를 받으려고 했더니 나주로 USB를 보내주면 데이터를 담아 택배로 보내준다고했었다. 보안때문에 어쩔수없는 상황이긴했지만 번거로웠다. 데이터를 구하기가 진짜 힘들다 이래서 기업들이 무료로 서비스를 풀고 사람들 데이터를 수집해가는 것을 선택한것같기도하다..이를테면 구글이라던가 구글이라던가..

본격적인 개발에 앞서 의논해야했던 것들

데이터도 수집했겠다 본격적인 개발을 시작하나 했는데 아직 멀었다.
아키텍쳐를 구성해야했다.

우리프로젝트의 아키텍쳐 구성은 다음과 같다.

https://velog.velcdn.com/images/dhsdb02/post/c4186e93-c5e5-47ba-8db0-7750e89193c2/image.png

넷플릭스 유레카 서버를 이용하여 스프링부트 프로젝트를 여러개로 구성한다.

1.유레카 서버 - client 서버들을 관리하는 서버이다.

2.api-gateway - 외부에서 접근하는 서버이다. 클라이언트단에서 api-gateway로 요청을 하면 api-gateway에서 적합한 micro service에게 전달해준다. 해당 서버에 cors와 관련된 설정들을 해주면된다.

3.micro-service - 백단에서 우리가 코드를 직접 작성하는 부분이다. 이러한 식으로 분리를 하면 각각의 서비스가 독립성을 유지하기 때문에 유지보수가 쉬워진다.

해당 프로젝트를 구성을 가졌기 때문에 코드를 작성하면서 내가 작성했던 부분이 다른팀원과 독립성을 가지고 작동해서 편리했다. 왜냐하면 내가 작성하던 코드에서 문제가 발생하여도 각각 다른 프로젝트로서 작동하기 때문에 다른 프로젝트에는 영향을 미치지 않았기 때문이다.

이번 공모전을 진행하면서 얻은 지식 중에서 가장 유익한 지식이라고 생각한다.

내가 맡았던 작업들과 팀안에서 내가 맡았던 포지션

우리 팀의 경우 다음과 같은 구성으로 이루어져있다.

🙍 (나) 백엔드(nodejs,python)에 대한 프로젝트 경험은 있지만 기본적인 프론트엔드 개발지식만 가지고 있는 개발자

🙍 (a) 프론트엔드와 디자인을 같이 담당하는 디자이너

🙍 (b) 백엔드(Spring Boot)와 프론트엔드 두 부분에 대한 기본적인 프로젝트 경험이 있는 개발자

🙍 (c) 정적 프론트페이지 구성에 대한 개발지식이 있는 개발자

🙍 (d) 백엔드와 프론트엔드에 대한 기본지식만 있는 개발자

총 다섯명으로 이루어져있다.

여기에서 나와 c개발자는 “검색한 음식점과 관광지를 지도위에서 볼수있도록 하는 페이지” 를 같이 구성하였다.

제공하는 기능들은 다음과 같다.

  1. 검색창 → 전체,음식점,관광지 중 하나의 탭으로 검색어를 입력하도록 유도 들어온 결괏값을 토대로 가장 연관성이 높은 결괏값을 추출함
  2. 검색결과 출력카드 → 검색결과를 출력해줍니다. 사용자가 보고싶은 탭을 누를때마다 출력값이 다르게 보이도록합니다. 탭에는 전체,음식점,관광지 세가지가 있으며 검색청에서 선택한 탭이 기본으로 선택되어있습니다.
  3. 여행지 및 음식점 출력카드 → 사용자가 선택한 결괏값의 세부정보를 보여주는 카드입니다. 해당 카드를 통해 사용자는 여행지와 음식점에 대한 상세정보를 확인할 수 있습니다.
    1. 여행지 카드 → Tour Api에서 제공하는 데이터를 기반으로 가져온 데이터를 보여줍니다.
    2. 음식점 카드 → eats mate에서 직접 수집한 데이터를 보여줍니다.이용자의 리뷰 또한 같이 제공됩니다.
    3. 공통 → 두카드 모두 나의 코스에 추가하기 라는 기능을 사용할 수 있습니다.
  4. 나의 코스 만들기 → 사용자가 선택한 여행지와 음식점을 나의 코스로 추가할 수 있습니다. 추가된 코드는 지도에 연결되어 표시됩니다. (단,코스를 완성하기 위해서는 로그인을 해야합니다)

내가 담당했던 부분은 1,2,3번에 대한 프론트엔드,백엔드 부분이다.

아래의 링크에 접속하면 제시된 화면을 직접확인할 수 있다.

https://eats-mate.com/

강남 검색 화면

메뉴에서 여행지 클릭시 나오는 화면

메뉴에서 음식점 클릭시 나오는 화면

나의 코스에 추가하기 버튼을 누르면 오른쪽 하단에 나오는 코스버튼이 나온다

추가한 코스가 다음과 같이 지도에 표시된다

프로젝트 도중 직면했던 문제들

프로젝트를 진행하면서 다양한 문제를 만나고 해결했었지만 그 중 가장 기억에 남는 몇가지를 추려 정리해보고자 한다.

문제점 1 - 위도,경도 데이터를 데이터베이스에 저장하고 그것을 가져오는 방법

첫번째 문제는 위도와 경도 데이터를 처음 다루어보는데에서 발생한 문제였다.

내가 담당한 지도파트에서 가장 중요한 것은 가게와 여행지의 위치 정보를 담고있는 위도와 경도를 다루는 것이었다. 특히 Spring Boot를 처음 사용하는 나로서는 JPA를 통해서 Entity를 생성하고 데이터를 가져와야했는데 Integer,Boolean 등 일반적인 데이터 타입이 아닌 다른 타입을 다루어야 했기 때문에 사전에 조사가 필요했다.

조사를 통해 알아낸 사실을 직접 실행에 옮기고 여러 문제가 발생했는데 다음과 같은 순서로 발생하였다.

  1. 좌표의 경우 우리가 위도,경도 값을 가지고 있는데 그것을 나누어 저장하게 되면 데이터가 잘리기 때문에 POINT 로 저장해주어야 한다.

    위도와 경도를 별도의 DECIMAL (또는 FLOAT) 컬럼에 저장하지 않고, POINT 유형의 컬럼을 사용하여 저장 → 좌표는 POINT(경도, 위도) 의 형식으로 저장해야 합니다.

    도서관의 좌표를 저장하는 fclty_loc 컬럼에 Spatial Index를 생성 → R-Tree 인덱스이며, MBR (Minimum Bounding Rectangle) 을 사용한 검색에서 성능 최적화 가능

    [MySQL] 가장 가까운 시설물 조회 - 두 좌표 (위도, 경도) 사이 거리 계산

  1. MariaDB에 다음과 같이 샘플 데이터를 넣어보았다.
insert into information
(name, gubun, gugun_nm, location, cntct, 
homepage_url, usage_of_week_and_time, address)
values('부탄츄','일식','마포구',ST_GeomFromText('POINT(37.5564036476463 126.926735502823)'),
'02-3144-3304','https://www.instagram.com/butanchu_seoul/',
'월-토: 11:30 - 23:00 일: 11:30 - 21:30','서울특별시 마포구 와우산로35길 75');
  1. Spring Boot 에서 JPA를 이용하여 Point 데이터를 가져오려고 함 → 해당 데이터를 읽어오지 못함

  2. 위도,경도 데이터를 가져오는데에 사용되는 라이브러리가 hibernate-spatial 이어 해당 부분에 대한 조사를 진행함

    Cannot call sendError() after the response has been committed

    → 당시 발생한 에러문

    참고자료

    How to read geography values using hibernate?

  3. 조사 결과 Spatial 또한 해당 데이터를 미지원하는 것으로 추정되어 잠정적으로 다른 데이터 타입으로 위도 경도를 저장하기로 함.

당시 발생한 문제를 이슈로 작성한 것

https://github.com/SWIDX/Eats-Mate-Backend/issues/13

해당 문제를 해결하지는 못하고 우회하는 방식으로 Decimal을 사용하였다.

nametypeoptioncomment
id (pk)longnot null음식점 id
namevarchar(100)not null음식점명
gubunvarchar(50)not null음식점 분류
gugunvarchar(50)not null구명
latDECIMAL(16,14)not null위도
lngDECIMAL(16,14)not null경도
cntctvarchar(100)not null전화번호
homepage_urlvarchar(255)식당홈페이지 주소
usage_of_week_and_timevarchar(100)식당 영업 시간
addressvarchar(100)not null식당 주소

Decimal (길이, 소수점 자리 수)로 생성한다. long,int 를 넘어가는 경우 해당 타입으로 저장하여 핸들링한다.

일단, maximum으로 생성되도록 설정함.

DECIMAL을 사용하여 DECIMAL(10,7) 과 같이 사용하여 DB에 저장을 하는것 이 좋을 것 같습니다. (DECIMAL의 범위는 -10^38+1 ~ 10^38-1 까지 입니다,)

[출처] [DB] 지도 좌표(위도,경도) 저장하기|작성자 jeongupark

문제점 2 - 데이터를 Union했는데 Union한 데이터를 어떻게 다루어야하지?

지도파트의 데이터베이스는 총 두개로 이루어져있다.

  • information 테이블 → 음식점 정보
  • tour_information 테이블 → 여행지 정보

여기에서 나는 두가지 테이블에 있는 정보를 Union한 결과를 리턴하는 API를 만들어주세요 라는 요청을 받았고.

해당 요청을 처리하기 위해 코드를 작성하려고 할 때 다음과 같은 의문점이 들었다.

나는 테이블마다 컨트롤러를 따로만들어 API를 구성하고 있는데 어떻게 Union한 데이터를 다루는 API를 구성해야하지? 라는 의문점이었다.

대략적인 폴더 구조는 이러하다.

즉, Service 레이어가 없는 상태였다. Service레이어가 없기 때문에 Controller에 비즈니스 로직을 작성할 수 밖에 없었다. 이는 스프링부트 기본구조와 각각의 레이어가 어떤 역할을 하는지 모르는 상태에서 작성했기 때문에 발생한 문제였다.

해당 문제를 해결하기 위해서는 가장 먼저 해야 할 일은 서비스 레이어가 어떤 동작을 처리하고 어떤 일을 하는지 알아보는 것이었다.

Service와 Controller의 차이는 무엇이며 각각의 계층이 어떤 동작을 하고있는지 파악을 했어야했으며, Service에서 어떤 동작을 처리하고 Controller로 건네주는지 알아야 현재 내가 작성했던 코드를 Service와 Controller로 분리하기 때문이다.

당시 참고했던 블로그

해당 서비스를 분리하고 다음과 같은 과정으로 문제를 해결했다.

  1. 유니온한 데이터를 담을 Interface를 준비한다. 해당 데이터는 이미 작성해놓은 엔티티에 타입들이 명세되어있기 때문에 다음과 같이 작성하면 되었다.

    import java.math.BigDecimal;
    
    public interface UnionInterface {
        Integer getId();
        String getName();
        String getGubun();
        BigDecimal getLat();
        BigDecimal getLng();
        String getAddress();
        String getType();
    }
  2. Repository에 다음과 같이 직접작성한 쿼리문과 함께 코드를 작성해준다

    @Query(value ="select id, name, gubun, lat, lng, address, '음식점' as type from map_service.information " +
                "where (name like %:text% or address like %:text% or gugun like %:text%) " +
                "union select content_id as id, name, gubun, lat, lng, address, '여행지' as type " +
                "from map_service.tour_information where (name like %:text% or address " +
                "like %:text% ) ORDER BY name asc ", countQuery = "select id, name, gubun, address, '음식점' as type from map_service.information where (name like %:text% or address like %:text% or gugun like %:text%) union select content_id as id, name, gubun, address, '여행지' as type from map_service.tour_information where (name like %:text% or address like %:text% ) ORDER BY name asc ",nativeQuery = true)
        List<UnionInterface> getSearchUnionInformation(String text);

    아쉬운점은 해당 내용이 어떤 리포지토리에 작성해야하지? 라는 생각이 들었다. 왜냐 이 데이터는 두 테이블 어디에도 속하지 않기 때문이다.

  3. Service에 다음과 같이 로직을 작성해준다.

    @Override
        public List<UnionInterface> getAllUnionInformation(String text){
            List<UnionInterface> listOfAllInfo;
            listOfAllInfo = db.getSearchUnionInformation(text);
    
            return listOfAllInfo;
        }
  4. MapServiceController에 해당 로직을 이용한 API를 구성한다

    @GetMapping("getAllData")
        public List<UnionInterface> getAllData(@RequestParam("keyword") String keyword){
            List<UnionInterface> res =tourInformationService.getAllUnionInformation(keyword);
            if(!res.isEmpty()){
                return res;
            }else{
                return null;
            }
        }

    Eats-Mate-Backend/MapServiceController.java at main · SWIDX/Eats-Mate-Backend

요청사항을 해결하면서 스프링부트의 기본구조가 Repository,Service,Controller로 이루어진 이유. 각각의 레이어가 처리하는 일이 무엇인지에 대해서 알아볼 수 있었다. 그러나 아쉬움도 존재한다.

지금 다시 되돌아보면 이 문제를 누군가에게 설명하고자 하면 잘못된 점을 정확하게 고치지는 못했다는 생각이 든다. 이를테면 근본적으로 잘못된 Repository,Controller 코드들을 삭제하고 수정하지는 않았던 것들 말이다. 당시는 프로젝트의 마감을 위해 달렸기때문에 이후 혹시 잘못되면 어쩌지라는 마음에 크게 손보지는 못했던점이 아쉽다.

추후 개선하고싶은 것들

재사용성이 높은 코드로 리팩토링 하고싶다.

이 말이 무슨말인고 하면, 프론트엔드와 백엔드 두가지 부분으로 나누어 서술하자면 다음과 같다.

[프론트엔드]

개발하던 당시 지도에서 사용했던 검색결과 출력 카드 컴포넌트와 음식점 정보 카드와 여행지 정보 카드 여러 컴포넌트를 개발했었다.

그러나 해당 컴포넌트를 재사용이라는 관점에서 바라보면 좋은 점수를 줄 수 없다. 왜냐하면 프로젝트에서 해당 컴포넌트를 지도외에서 다른 곳에서 사용할꺼라 생각하지 않고 만들었기 때문이다.

그렇기 때문에 추후 프로젝트를 확장할 경우 해당 컴포넌트를 재사용할 수 있도록 리팩토링 하고싶다.

[백엔드]

백엔드 부분의 경우 또한 마찬가지다. 사실 프로젝트를 진행함에 있어 내가 백엔드를 단독으로 담당하고 있지는 않았기 때문에 한쪽에 치중하여 신경쓰지 못했다. 이젠, 스프링부트의 기본적인 구조와 각 레이어에 대한 이해 또한 하고 있기 때문에 내가 맡았던 지도파트 백엔드 프로젝트 파일들을 재구성하고 싶다.

현재 코드를 보면, Union데이터를 TourInformationRepository에서 결괏값을 가져와 구성하고 있다. Union데이터는 Tour정보와 음식점 정보를 가지고 있는 결괏값이기 때문에 TourInformation을 다루고있는 TourInformationRepository에서 해당 데이터를 다루는것은 적절치 않다. 즉, 각각 어떤 데이터를 담당하고 있는지 명확하게 분류해주는 작업이 아쉬웠고. 추후 개선하게 된다면 해당 부분을 작업하게 될 것 같다.

프로젝트 이후 얻은것들과 변화한 나의 모습

해당 프로젝트로 얻은 것들이 매우 많다.

간략하게 변화된 나의 모습을 보여줄 수 있다!

첫번째로는 Git을 제대로 쓰는 법을 배웠다는 것이다.

사실, Git을 모르지도 쓰지 않는 것도 아니었다. 그러나 커밋메세지의 컨벤션을 지정해 어떤 내용을 담아야하지 라는 생각은 해보지는 않았다.

이번 프로젝트를 하면서 같이 작업했던 개발자분을 통해서 그러한 습관을 기르게 되었고 GIT에 대한 능력이 많이 향상되었다.

2021년 GIT COMMIT MESSAGE

2022년 GIT COMMIT MESSAGE

또한 잘못올린 커밋메세지를 올리거나,잘못된 머지 커밋이 들어간 경우 수정요청이 들어온 경우가 많았는데 실수로 Rebase 범위를 너무 많이 잡아 수정되지 않아도 될 커밋까지 포함되어 커밋이 꼬이는 등의 문제상황을 많이 경험했었다. 그런 경험을 하면서 Git에 대한 기본적인 지식을 습득할 수 있었고 나의 GIT 사용 능력을 많이 향상 시킬 수 있었다.

두번째로는 프로젝트 아키텍쳐에 대한 중요성이다.

https://velog.velcdn.com/images/dhsdb02/post/c4186e93-c5e5-47ba-8db0-7750e89193c2/image.png

처음으로 작성해본 프로젝트 아키텍쳐였고, MSA라는 구조가 가지는 장점에 대해서 몸소 경험해볼 수 있었다.

첫번째, 다른 프로젝트에서 문제점이 생겨도 나는 문제가 생긴 프로젝트와 독립성을 유지하고 있기 때문에 내가 작업하는 곳에는 문제가 발생하지 않는다는 것이다.

두번째, API GATEWAY를 통해 관리하기 때문에 프로젝트가 여러개여도 하나의 포트로 게이트웨이에서 포트문제,CORS 에러 등을 관리할 수 있다.

특히, 첫번째 장점을 몸소 느끼게 된 계기가 있었는데 바로 프론트엔드 작업을 하면서였다.

프론트엔드 작업의 경우, 백엔드와 달리 철저하게 관리하지는 못했다. 다들 프론트엔드 작업에 대한 전문성이 없었기 때문에 백엔드 처럼 관리할 수 없었기 때문이다.

그래서 develop 브랜치를 놓고 작업을 진행했는데 하나의 코드를 두명이상이 수정하면 문제가 항상 발생했다(이러한 문제를 해결하기 위해서 브랜치를 파는거지만,나 혼자 브랜치를 파서 진행하다가 내가 작업하는 기능과 겹치는 코드를 작업하는 개발자 분은 그냥 develop에서 브랜치 없이 진행하셔서 흐지부지가 되었다..)

발생했던 문제를 간략히 설명하면 다음과 같다.

내가 수정하던 코드를 누가 수정해서 커밋을 올려버렷다..근데 그 코드에서 줄이 바뀌고 내가 쓰던 함수가 사라졌다..→ 이럴 경우,,무한 머징의 세계로 끌려가는데 진짜 지옥같았다. 프로젝트 초반도 아니고 중반이었기 때문에 코드가 몇백줄인 상황에서 어떤걸 넣고 어떤걸 빼고 이걸 판단해야했다.이러한 상황을 방지하기 위해 커밋하기 전에도 동료 개발자에게 미리 알린 후 커밋했다.물론 그래도 항상 오토머징 커밋은 발생했고 3명이상이 커밋해야한느 상황에서는 항상 컨플릭트가 발생했다.

이러한 문제상황을 프론트엔드 작업을 하면서 겪게 되니,백엔드의 구성이 매우 훌륭했다는 것을 몸소 느낄 수 있었다.

이번 프로젝트를 통해서 다양한 지식을 습득 할 수 있었고 당시는 힘들었지만 지금 생각해보면 개발자로서 기본적인 지식을 확충할 수 있는 기회였던 것 같다. 더불어, 단순한 이론지식이 아니라 이론을 실습으로 직접 경험해볼 수 있었기 때문에 더 기억에 남는 좋은 경험이었다.

profile
반갑습니다 ! 백엔드 개발 공부를 하고있습니다.

0개의 댓글