Fun편log팀은 프론트엔드 개발자 2명 백엔드 개발자 1명이 팀을 이뤄 11월 15일부터 12월 22일까지 포트폴리오 멘토링 프로젝트를 진행했습니다.
프론트엔드 개발자 두 분은 비전공자 개발자로써 독학으로 html/css, React를 공부하였으나 취업에 쓸만한 근사한 포트폴리오가 없어 지원하셨고,
백엔드 개발자 분은 Windows 개발자로 계시다가 백엔드 개발자로 전향하기 위해 지원하셨습니다.
3명의 멘토(설계, 백엔드, 프론트엔드)의 강의, 설계&코드 리뷰를 받으면서 개발을 진행했습니다.
Frontend : https://github.com/Couch-Coders/12th-Fun-Pyeon-log-fe
Backend: https://github.com/Couch-Coders/12th-Fun-Pyeon-log-be
- 전국 약 5만개의 편의점 정보를 찾아볼 수 있습니다!
- 편의점의 리뷰, 키워드를 통해 원하는 편의점을 찾을 수 있습니다!
- 브라우저에서 위치를 허용하면 내 위치에서 가까운 편의점을 찾을 수 있습니다!
- 별점, 거리순, 키워드 등 다양한 방법으로 필터링할 수 있습니다!
- 실시간 리뷰에서 편의점 대한 상세한 이용 후기들을 만나보세요!
- 유저들이 직접 선택한 해당 편의점의 대표 키워드 Best 5를 확인해보세요!
프론트엔드 개발자 분들은 이번 프로젝트에 처음으로 TypeScript를 제대로 적용해 보는 것을 목표로 기술스택을 정하였습니다. ESLint를 강하게 적용하여 좋은 TypeScript 작성 습관을 기르기 위해 노력하였습니다. 또한 Redux Toolkit을 실제 프로젝트에 적용해 보았습니다.
백엔드 개발자분은 이번 프로젝트에 처음으로 Spring Security와 Spring Data JPA를 사용해보기로 하였습니다. Security Filter를 작성하여 Cookie의 JWT 토큰을 검증하였고, JPA를 통해 DB를 다루었습니다.
배포는 Frontend는 Netlify를 백엔드는 Qoddi를 사용하였습니다. 둘다 무료 호스팅을 제공하기 때문에 AWS 배포시 나중에 비용 문제로 내려 포트폴리오를 활용 못하는 문제를 해결하고 오랬동안 포트폴리오를 사용할 수 있도록 하였습니다.
Netlify는 Proxy기능을 제공하여 Secure Cookie를 cors문제 없이 주소가 다른 서버에 보낼 수 있어 Cookie를 통한 JWT 인증시 활용되었습니다.
Firebase를 통해 OAuth(구글 로그인) 구현하였으며, GitFlow 정책과 Github Actions 통합을 통해 브랜치별 자동 배포 시스템을 구축하였습니다.
설계 단계부터 발생한 문제는 전국에 편의점이 너무 많다는 것이었습니다. 데이터를 전부 크롤링하는데는 너무 오랜 시간이 걸린다는 판단이 들었고, 또한 RDB에서 위치 기반으로 거리를 측정하는 것은 어렵다는 정보를 들었습니다.
전통적인 아키텍처 대로 구현을 한다면 제한된 시간 내에 프로젝트를 완료 할 수 없다고 판단하였고, 프론트에서 Kakao API를 통해 근처 편의점을 검색하면 해당 편의점에 id를 통해 DB에 저장된 리뷰를 가져오는 식으로 아키텍처를 수정하였습니다. 백엔드에서 API를 호출하는게 더 좋은 구조로 보였으나 팀 구성이 프론트2 백엔드1 이기 떄문에 프론트에서 해당 로직을 처리하기로 하였습니다.
바뀐 구조는 다음과 같습니다.
편의점 리뷰만 DB에 저장하기로 했기 때문에 초반 Database 구조는 다음과 같았습니다.
그러나 해당 구조는 메인페이지 조회시 메인페이지에 참여하는 모든 편의점의 요약 정보를 그때 그때 만들어 줘야하는 치명적인 단점이 있었습니다. 그래서 편의점 요약 정보를 테이블로 저장하면 더 빠르게 조회할 수 있지 않을까 했고, 변경한 구조는 다음과 같습니다.
이제 편의점 요약정보만 원할 시 에는 편의점 요약정보 테이블만 검색하면 데이터를 넘겨 줄 수 있게 되었습니다.
위치 기반으로 근처의 편의점을 조회하기 때문에 편의점 필터링 기능은 이미 고정된 편의점 리스트에서 필터링이 됩니다. 때문에 API를 추가 호출하지 않고 필터링을 구현할 수 있다는 판단하에 프론트에서 필터링하기로 결정하였습니다.
javascript로 많은 데이터를 다뤄본 적이 별로 없어서 어려웠으나, filter
와 some
을 공부하고 적용해 보았습니다.
const sortKeyword = (newData: ConvType[]) => {
let sortResult
if (selectKeyword.length === 0) {
// 선택된 키워드가 없을 시 원래 편의점 리스트를 가져옴
sortResult = newData
} else {
// 선택된 키워드가 하나라도 리뷰에 포함되어 있으면 리스트에 포함
sortResult = newData.filter((data) =>
data.keywordList.some((keyword) => selectKeyword.includes(keyword))
)
}
return sortResult
}
위의 코드는 Keyword로 Filtering하는 예제입니다. 지금 보면 별거 아닌데 이떄는 이런 기능을 처음으로 사용하였기 때문에 어려웠던 것 같습니다.
팀 프로젝트시 코드 만큼이나 발목을 잡는 것은 Git입니다. 충돌이라도 나면 혼란스럽습다.
프론트앤드 개발자 두분이 머지를 할때마다 코드가 충돌나는 문제가 발생하였습니다. Prettier 설정이 서로 달라 저장시 코드가 각자의 설정에 맞게 변경되는 것이 문제였습니다. prettierrc.json
를 추가해 해당 문제를 해결하셨습니다.
{ "trailingComma": "es5",
"tabWidth": 2,
"semi": false,
"singleQuote": true
}
또한 package-lock.json
이 계속 충돌 나는 문제가 발생했는데 npm버전이 서로 달라 충돌이 발생하였습니다. 서로 환경을 통일시켜 문제를 해결했습니다.
팀 프로젝트에서는 항상 팀원들의 개발환경 및 코드 컨벤션을 통일 시켜야 합니다.
많은 충돌을 해결을 통하여 Git을 자신감있게 다룰수 있게 되었고 Git에 대한 자신감을 얻을 수 있었습니다.
프로젝트를 진행하다 보니, 하나의 Service가서 여러 Repository에 대한 의존성을 가지게 되고, 이 구조가 나쁘다는 느낌을 받았습니다. 그러나 좋은 소프트웨어 구조가 애매하기 때문에 이를 어떻게 고쳐야 할지 어려움을 느꼇습니다.
수업 시간에 좋은 소프트웨어 설계를 위한 SOLID
에 대해서 배우고 리팩토링 멘토링 시간을 통해 좋은 소프트웨어 구조와 코드에 대해서 다시한번 생각해 본 것 같습니다.
특히 자주 사용하는 키워드를 캐시로 보관하기 위하여 JPARepository
를 확장하는 리팩토링을 진행하였는데 이런 구조로 나중에 Repository가 외부 API를 이용해 데이터를 리턴해 주거나 하나의 인터페이스로 JPA외 R2DBC, MyBatis 등과도 혼용하여 사용할 수 있다는 것을 알게되었고 인터페이스 상속의 강력함을 알게 되었던 것 같습니다.
리팩토링 과정에 대한 자세한 내용은 여기 를 확인해주세요
김예지(프론트엔드 개발자)
진짜 개발자
로 시작하는 기분이네요. 시작하는 서비스가 어떻게 만들어지는지 깨닫고, 포트폴리오도 완성
하는 경험을 할 수 있던게 개발자로써의 큰 원동력이 될 것 같습니다.오도경(백엔드 개발자)
강명훈(프론트엔드 개발자)
nice