이스트소프트에서 주관하는 백엔드 개발자 부트캠프 '오르미'에서 팀 프로젝트로 만든 서비스입니다.
글을 사랑하는 사람들의 모임 모집 플랫폼 gluv입니다. 모임 활동을 하고 싶은 사용자가 플랫폼을 사용해 모임을 만들고, 구성원들과 함께 대화할 수 있는 서비스를 구현하였습니다.
모집글과 모임 정보를 통해 모임을 관리, 활동할 수 있으며 모든 사용자들이 커뮤니티 게시판을 통해 창작물을 올리고, 의견을 교환할 수 있는 장을 마련하였습니다.
Backend 서버와 Frontend 서버 별개로 개발되었으며, Backend는 Django, Frontend는 React를 사용하였습니다.
개발 기간은 12.8 ~ 12.28 총 21일이었으며 4인 1조의 협업으로 진행되었습니다.
GitHub Repository : https://github.com/OrmiFinal/gluv
FE Repository : https://github.com/OrmiFinal/gluv-FE
배포 URL : http://43.202.4.135/
- 회원 관련 기능을 구현
- 회원 관련 기능 포함 최소 3가지 이상의 기능을 구현
- 발표와 시연에 필요한 수준의 UI 구현
- ERD, API 명세서 작성
- README 외에도 발표자료 작성
- 모놀리식, 마이크로식, FBV와 CBV의 적절한 사용 등 Django를 적절히 사용
- DB 설계 고도화 (복잡도, 적절성 고려)
- 서비스 배포 : nginx, gunicorn, django 등 적절한 연계를 통한 배포
- 설계와 구현 복잡도 고려 (요구사항 작성, 와이어프레임, 설계에 따른 구현도 등)
- 외부 라이브러리 사용
- 기능 별, 모듈 별 담당을 정해 개발하는 것이 아닌 Task 별 단기 목표 설정, 역할 분배
- 분업과 코드리뷰 위주의 협업 중시
- GitHub의 Issue 기능 사용, Pull Request 시 적극적인 Review 유도
- 서로 이해할 수 있는 코드, 알기 쉬운 변수/함수 명명과 주석 작성
- 컨벤션 문서를 작성해 필요 시 참고하면서 작성
- 진척사항 공유와 다음 작업할 Task 선정을 위해 매일 일정 시간에 모여 회의
- GitHub Wiki에 회의록을 작성해 진척사항과 필요한 내용 기록, 필요할 때 다시 파악
| Backend | Frontend |
|---|---|
![]() |
![]() |
- 크기 문제로 인해 일부만 표시합니다. 전체 내용은 Wiki 링크 첨부합니다.
- Backend : Django로 제작되었으며 12개의 앱으로 구성되어 있습니다.
chatrooms 앱은 웹소켓을 통한 채팅 구현을 위해 routing.py와 consumer.py가 추가되었습니다.
books 앱은 알라딘 API와의 통신을 위해 crawlers를 추가로 구현하였습니다.
gluv 프로젝트에는 celery를 통한 스케쥴러 기능을 위해 celery.py로 설정을 추가했습니다.
redis와 docker 설정을 위해 별개의 폴더로 관리하고 있습니다. - Frontend : React로 제작되었습니다.
각각의 페이지 표현을 위한 pages 파일들 외에, 재사용성을 위한 components와 context, API 호출을 관리하기 위한 API 폴더로 관리되고 있습니다. - Folder Tree GitHub Wiki
-
크기 문제로 인해 일부만 표시합니다. 전체 내용은 Wiki 링크 첨부합니다.
| 화면 설계 | 기능 설계 |
|---|---|
![]() |
![]() |
- 크기 문제로 인해 일부만 표시합니다. 전체 내용은 링크 첨부합니다.
- WireFrame을 통해 화면설계를 진행하면서 메모를 사용해 기능설계를 겸했습니다. 기능별 요구사항을 정리하여 팀원들과 공유할 수 있도록 하였습니다.
- 카카오오븐 Wireframe
- 크기 문제로 인해 일부만 표시합니다. 전체 내용은 Wiki 링크 첨부합니다.
- URL자원과 GET, POST, PUT, DELETE 등의 메소드 및 설명, 인증과 권한에 대한 정보를 명시하였습니다.
- URL명세 GitHub Wiki
- 크기 문제로 인해 일부만 표시합니다. 전체 내용은 Wiki 링크 첨부합니다.
- WebSocket을 쓰면서 생소한 부분이 많았고 새롭게 배운 것도 많았습니다. 채팅기능을 구현하면서 WebSocket의 명세화 필요성을 느꼈습니다.
- WebSocket명세 GitHub Wiki
-
크기 문제로 인해 일부만 표시합니다. 전체 내용은 Wiki 링크 첨부합니다.
-
기획 시 API설계를 진행하였습니다. 기능을 구현할 때 해당 설계를 참조하여 Endpoint, 반환 Data의 형태를 통일할 수 있도록 시도하였습니다.
-
API의 초기 구현이 끝난 뒤 테스트를 거치면서 설계와 달라진 점을 반영, 2차로 API 명세를 만든 뒤 마지막으로 Swagger 기능을 통해 최종 API 명세를 만들었습니다.
- 메인화면의 UI입니다.
| 모집게시글 작성 | 모임 상세정보 조회 |
|---|---|
![]() |
![]() |
| 모임 상세정보 수정 | 모집게시글 반영 |
|---|---|
![]() |
![]() |
- 모임을 주최하고 싶은 사용자는 모집게시글을 작성해야합니다.
- 모집 게시글을 작성하면 모임과 모임의 일정 Data가 같이 생성되며, 글을 쓴 사용자는 모임의 리더가 됩니다.
- 모임의 리더는 일정, 모임의 이미지 등의 정보를 수정할 수 있으며, 이는 모집게시글에도 반영됩니다.
- 모임의 일정에는 '주기(frequency)', '주(week)', '요일(day)' 속성이 있습니다. 이를 통해 모임의 빈도를 '매일/매주/매월'과 같이 설정할 수 있으며, 빈도가 매월일 경우 '첫번째 주', '두번째 주' '월요일', '화요일'과 같이 구체적으로 지정할 수 있습니다.
| 모집 게시글 조회 | 모집 게시글 검색 |
|---|---|
![]() |
![]() |
- 모임에 참여하고 싶은 사용자는 모집게시글을 조회 후, 마음에 드는 모임에 가입신청을 해야합니다.
- 모임 모집 게시판은 상단의 Navbar를 눌러 이동할 수 있으며, 카테고리 설정, 정렬순서 변경 등을 통해 list에 노출되는 게시글을 조절할 수 있습니다.
- 하단의 Input창을 통해 게시물의 제목을 검색할 수 있습니다.
- 사용자가 마음에드는 모집게시글을 찾았을 때, 우측 하단의 신청하기 버튼을 통해 모임에 가입 신청을 할 수 있습니다.
- 가입신청을 누르면 해당 모임의 TeamMember Instance가 생성됩니다. 이 Instance에는 is_approved 필드가 있어 값이 False일 경우에는 가입 신청중인 사용자, 값이 True일 경우에는 모임의 구성원으로 판단하게 됩니다.
- 모임의 리더는 모임 정보의 '신청인원 관리' 메뉴에서 가입신청한 회원에 대한 승인과 거절을 할 수 있습니다.
- 모임의 리더가 신청을 승인하면 해당 사용자의 TeamMember Instance의 is_approved 값이 True로 바뀌며 모임의 구성원으로 인정됩니다.
- 신청을 거절한다면 해당 사용자의 TeamMember Instance가 삭제됩니다.
- 모임 정보 중 '구성원 관리' 메뉴에서는 모임의 구성원 목록을 확인 할 수 있습니다.
- 모임의 리더는 구성원에게 리더 권한을 이전할 수 있으며, 강퇴 또한 가능합니다.
- 모임장 이전 버튼을 누르면 해당 구성원의 TeamMember Instance의 속성 중 is_leader의 값이 True가 되며 리더로 바뀝니다. 기존의 리더의 is_leader 속성은 False가 되면 모임의 구성원으로 전환됩니다.
- 강퇴 버튼을 누르면 해당 구성원의 TeamMember Instance가 삭제되어 모임의 구성원으로 인정받지 못합니다.
- 모임의 리더가 모임을 탈퇴하면 리더의 역할을 할 구성원이 필요합니다. 따라서 is_leader의 값이 True인 구성원이 모임 탈퇴를 누를 경우 다음 index의 구성원을 찾아 is_leader의 값을 True로 변경합니다.
- 모임의 구성원이 리더 혼자일 경우 모임의 탈퇴가 불가능하며, 모임을 삭제해야합니다.
| 채팅방 입장 | 모임 채팅 |
|---|---|
![]() |
![]() |
- 모임의 구성원은 모임 정보 우측 하단의 '채팅방 입장' 버튼을 통해 모임의 채팅방에 입장할 수 있습니다.
- 채팅방의 좌측에는 로그인한 유저가 가입한 모든 모임의 채팅 list를 볼 수 있습니다. 모임의 이름을 클릭하면 해당 채팅방으로 이동할 수 있습니다.
- 채팅은 Django Channels를 사용하여 웹소켓을 구현하였습니다. Redis를 활용하여 채팅 기능 사용 시 DB의 데이터 대신 캐시에 저장된 메모리를 활용할 수 있도록 하였습니다.
- 사용자가 메시지를 송신할 때, 해당 유저가 모임의 구성원인지 파악하는 과정을 DB가 아니라 캐시에 저장된 메모리를 사용하도록 함으로써 더 빠른 응답속도를 기대할 수 있었습니다.
- Django Channels를 사용하고 ASGI를 설정했을 때의 통신구조를 나타낸 그림입니다.
- Request, Response의 흐름을 channel의 layer를 지나는 Message의 형태로 변경하게 됩니다. 이 때 HTTP Request의 경우, layer를 거치지 않고 기존의 흐름을 유지하고 있습니다.
- 사용자는 모임 외에도 커뮤니티 게시글을 통해 글을 쓰고 소통할 수 있습니다.
- 커뮤니티 게시글은 여러개의 카테고리로 구성되어 있으며 메뉴를 통해 해당 카테고리의 글만 조회할 수 있습니다.
- 또한 하단의 Input을 통해 게시글의 제목을 검색할 수 있습니다.
- 카테고리 게시글 list를 조회하는 Endpoint는
/posts/?search={search}&category={category}&page={page}입니다. - 'search' 파라미터를 통해 게시글의 제목을 필터링 해 찾아올 수 있으며, category 파라미터를 통해 원하는 카테고리의 글을 list해 가져옵니다.
- 사용자는 게시글에서 댓글을 통해 의사소통할 수 있습니다.
- 댓글의 DB에는 대댓글을 표현할 속성으로 유저의 데이터를 담고 있습니다. 특정 댓글에 대한 대댓글을 구현하는 대신 원하는 사용자의 닉네임을 태그하는 형태로 구현하였습니다.
- 댓글 DB 내 대댓글 대상인 유저의 데이터가 없을 경우 일반 댓글이 작성되며, 데이터가 있을 경우에는 해당 유저의 닉네임을 태그해 당사자에게 남기는 댓글임을 명시합니다.
| 화제의 게시글 | 최근 게시글 |
|---|---|
![]() |
![]() |
- 메인화면과 일부 게시판에서는 화제의 게시글, 최근 게시글을 list하고 있습니다.
- 게시물의 속성 중 'created_at'과 'viewcount'를 조건으로 필터링하여 일정 개수를 반환해 출력합니다.
| 좋아요 | 좋아요 취소 |
|---|---|
![]() |
![]() |
- 사용자는 마음에 든 모집 게시글과 커뮤니티 게시글에 좋아요를 줄 수 있습니다. 게시글 상세 페이지에서는 유저가 해당 게시글에 좋아요를 눌렀는지를 판단해 버튼의 종류를 다르게 보여줍니다.
- 메인화면의 상단에는 알라딘 API를 통해 전송받은 도서 정보를 통해 신간 정보를 출력합니다.
- 책의 이미지, 제목, 지은이를 출력하고 있으며 해당 항목을 클릭시 알라딘의 도서 판매 페이지로 이동합니다.
- 신간정보의 DB는 Book 앱 내부의 Task기능을 통해 일정 시간마다 알라딘 API를 호출받아 저장합니다.
- Task 기능은 Celery 패키지를 사용해 구현하였습니다.
| 새 알림 | 전체 알림 | 읽은 알림 |
|---|---|---|
![]() |
![]() |
![]() |
- 사용자가 가입한 모임의 일정이 변경되면 알림이 발송됩니다. 사용자가 헤더 우상단의 알림 버튼을 누르면 알림 모달창이 출력됩니다. 전체 알림 버튼을 누르면 전체 알림 모달이 출력됩니다.
- 새 알림은 사용자가 읽지 않은 알림을 필터하여 출력합니다. 전체 알림에서는 사용자가 읽은 알림은 흐리게 처리하여 읽지 않은 알림과 구분되도록 구현하였습니다.
- 사용자는 모집 게시글이나 커뮤니티 게시글에서 문제를 발견했을 때 해당 유저를 신고할 수 있습니다.
- 게시글의 신고 버튼을 누르면 신고 내용의 data를 담은 instance가 생성됩니다.
- 로그인과 회원가입 페이지는 모달창으로 구현되었습니다.
- JWT토큰을 사용하여 사용자 로그인을 구현하였으며, 토큰은 local storage에 저장해 핸들링합니다.
- 게시글의 우하단 신청하기 버튼을 통해 가입 신청을 할 수 있으며, 좌하단의 신청 확인을 누르면 알림 메시지로 사용자가 이미 신청을 했는지, 아닌지를 알려줍니다.
- 신청인원의 가입을 수락하면 해당 사용자는 모임의 구성원으로 인정되어 구성원 관리 메뉴에서 확인할 수 있게 됩니다.
- 모임의 리더는 '모임 정보 수정' 메뉴에서 모임의 일정을 변경할 수 있습니다.
- 모임의 일정이 변경될 때마다 구성원에게 알림이 발송됩니다.
- 현상 : 모임 상세 정보를 요청했을 때 반환까지 시간이 오래 걸리는 문제 발생
- 원인 : 모임 상세 정보는 모임의 정보 외에도 모집 게시글, 일정의 정보까지 조회할 필요가 있었습니다. 이 때 모임의 serializer에서 모집 게시글,
일정의 정보를 하나하나 get_object()설정을 해놓아 필요 이상의 쿼리가 발생하고 있었습니다. - 해결 : 리팩토링을 진행하여 related_data를 구성해 한번의 쿼리 요청으로 필요한 모든 정보를 받을 수 있도록 설정하였습니다.
반환까지의 시간이 1/3정도 단축되는 효과가 있었습니다.
| 결과1 | 결과2 |
|---|---|
![]() |
![]() |
- 현상 : 모집 게시글, 모임, 일정 View를 구현한 뒤 테스트 중 DB에 손상된 파일이 생기며 읽을 수 없는 경우가 생기는 문제 발생
- 원인 : 모집 게시글을 작성할 시, 1:1관계를 가지는 모임, 일정 모델이 같이 생성됩니다. 이 때 생성되던 도중 오류가 발생하면 모집게시글 생성은 취소되지만 해당 과정 중
생성된 모임, 일정과 모임 구성원 Data가 무결성이 깨진 채로 남아있었습니다. - 해결 : 모집 게시글 생성 View에 @transaction.atomic 데코레이터를 지정하여 원자성 부여, 오류가 생겼을 시 트랜잭션을 롤백할 수 있도록 설정하였습니다.
| 오류 | 해결 |
|---|---|
![]() |
![]() |
- 현상 : user 및 team의 디폴트 이미지를 Nginx 403 에러로 불러올 수 없는 문제 발생
- 원인 : Ubuntu 디렉토리 권한 설정 문제로 nginx가 Media 디렉토리에 접근할 수 없어 이미지를 가져올 수 없었습니다.
- 해결 : nginx가 접근 가능한 디렉토리에 media 디렉토리를 마운트하여 접근할 수 있도록 조치했습니다.
| 오류 | 해결 |
|---|---|
![]() |
![]() |
- 현상 : 특정 기능에서 API 호출 오류가 발생했을 때 수정하는데 많은 시간과 공수가 들어가는 문제
- 원인 : 모든 페이지 파일에서 별개의 fetch를 작성하면서 코드의 재사용성이 떨어지는 상황이었습니다. 문제를 개선하기 위해 fetch를 모듈로 빼 (api/*.js) 작성했으나,
아직도 반복작업이 많음을 느끼고 API요청을 공통 모듈로 빼는 작업을 가졌습니다(api/api.js). - 해결 : 구현과 배포 후 버그 수정과정에서 시간과 공수를 아낄 수 있었으며, 이후 fetch를 인터셉트해 예외처리 하는 부분을 상대적으로 빠르고 쉽게 구현할 수 있었습니다.
| api.js | api 호출 시 |
|---|---|
![]() |
![]() |
- 현상 : 웹소켓 연결 시 헤더를 설정할 수 없어 JWT 토큰을 사용한 인증 구현 불가
- 원인 : 사전조사를 할 때 기존 일부 웹소켓 서비스들이 연결 시 JWT 토큰을 사용하여 사용자 인증을 구현하고 있음을 파악했습니다.
하지만 MDN 문서를 살펴보니 본 프로젝트에서 사용하고 있던 웹소켓은 연결 시 헤더를 설정할 수 없다는 것을 확인했습니다. - 해결 : 웹소켓 통신 내에서 사용자 인증을 구현하는 방식으로 전환하였습니다.
팀원분의 작업이 끝나면 다음으로 해야 할 것을 배분해드리는 일을 하면서 고민이 많았습니다. 지금 맡은 작업이 버겁거나 오래걸릴 것 같지는 않은 지, 해당 기능에 대한 요구사항이 제대로 전달되었는지 확신이 들지 않았습니다. 프로젝트가 끝나갈 때쯤 되어서야 이 분이 어떤 작업에 더 편하고 능숙하신 지 파악하고, 보다 더 명확한 요구사항을 전달할 수 있게 되었습니다. 협업을 할 때는 의사소통이 가장 중요하다는 것, 유능한 관리자가 있어야 한다는 것을 실감한 좋은 기회였습니다.
혼자서 프로젝트를 진행할 때는 ‘와, 이 기능을 써봤다! 익숙해졌다!’정도의 배움이었다면, 협업을 하면서 그 이상의 것을 배웠다고 생각합니다. 제가 생각했던 것과는 다른 방향으로 구현되는 기능들이나, 더 효율적인 코드들을 보고 습득하는 것은 이전에 느껴보지 못했던 새로운 즐거움이었습니다.
프로젝트를 시작하기 전에는 수업을 통해 지식을 배우고 프로젝트를 통해 실제로 직접 개발을 경험하며 많은 것을 배울 수 있었습니다.
서비스 설계부터 구현, 테스트, 배포까지의 과정을 거치면서 각 단계마다 직면하는 문제들을 해결하고 성장할 수 있었습니다.
요구 사항 정의, 설계, 구현, 테스트, 배포 등의 단계를 경험하면서 전반 적인 개발을 이해할 수 있었습니다.
프로젝트 중 깃을 사용하면서 주로 병합 시 버그가 발생하는 어려움이 있었습니다. 이부분은 다른 팀원들에게 배워서 더욱 좋아졌습니다.
이번 팀 프로젝트를 통해 잊고 있던 개발의 재미를 다시 한 번 느꼈습니다. 설계부터 배포까지 협업의 과정을 통해 성장할 수 있었습니다. 그리고 그 모든 과정에 함께해주신 팀원 분들과 강사님들께 감사드립니다. DRF의 능력을 작게나마 시험해본 경험을 양분삼아 더 많이 오래 개발해나가고 싶습니다.

























































