해커톤이 코로나 시국 이후로 처음으로 오프라인 해커톤이 진행된 것으로 알고있습니다. 온라인으로 진행했다면 다소 지루했을 대회가 오프라인으로 진행되니, 꽤 재밌고 유익했던 경험이였던 것 같네요!
저희 팀이 진행했던 프로젝트명은 "덕꾸(Duckku)" 였습니다.
연예인 덕질을 하는 광팬들을 위한 서비스로, 소비자들이 앨범에서 원하는 구성품들로만 새로운 토큰형 앨범을 구성해서 구매할 수 있도록 하는 것이 주된 목적입니다.
이 서비스를 통한 기대효과는 아래와 같습니다.
1) 토큰형 앨범과 응모권을 사용자에게 제공함으로써, 응모권으로 인해 불필요하게 소모되는 앨범을 줄일 수 있도록 합니다.
2) 사용자에게 최적화된 덕질 환경을 제공해준다. 사용자가 선택한 관심 아티스트관 별로 사용자가 구매한 앨범을 관리할 수 있도록 합니다.
3) 또한 앱 내부 앨범 마켓을 통해 손쉽게 앨범을 구매하고 포토카드와 응모권을 소장할 수 있도록 구현했습니다.
CD 엘범은 더이상 사람들이 음악을 즐기기 위해 구매하는 수단과 용도가 아닙니다.
현재 사람들이 음반을 구매하는 주된 이유는 CD를 이용해 음악을 감상하기 위함이 아니라, 앨범에 동봉되는 굿즈, 포토 카드, 팬 싸인회 응모권 티켓 등을 받기 위해서입니다.
현재 음반 시장에서 CD와 앨범의 수요는 본래의 목적(음악 감상)
이 아니라, 가수에 대한 팬심
과 앨범이 갖고 있는 상징성
으로부터 나오고 있습니다.
음반의 의미와 역할이 달라지며, 소비자들이 음반을 소비하고 활용하는 방법 또한 달라졌습니다.
현재 음반 시장의 주 고객층은 아이돌 가수, 트로트 가수 등 거대 팬덤을 형성하고 있는 가수들의 팬들입니다.
이들은 주로 CD 자체보다는 앨범에 동봉되는 굿즈, 팬싸인회 응모권 등을 위해 앨범을 구매합니다.
더 많은 굿즈와 팬싸인회 응모권을 위해 한 사람이 수십, 수백개의 앨범을 구매하기도 합니다. → 이를 신조어로 ‘앨범깡
'이라 합니다.
마찬가지로 소속사 및 유통사에서 음반을 판매하기 위한 마케팅 전략도 달라졌습니다.
소속사 및 유통사도 음반의 수요가 더이상 음악 감상의 목적이 아님을 알고 있습니다.
이에 따라 앨범의 종류를 다양하게 하고, 앨범마다 각기 다른 포토카드를 넣고, 앨범을 많이 구매할수록 팬싸인회 응모권을 많이 갖게 되는 구조를 사용하는 등 앨범이 가지고 있는 굿즈성 성격을 강화하고 팬심을 자극하는 방식으로 음반 시장에서의 마케팅을 진행하고 있습니다.
그러나 이러한 음반 구매 및 유통 세태가 이어지며, 자연스레 여러 문제 또한 발생하게 되었습니다.
시대가 달라지며 음반의 역할과 수요가 달라지고 있는 데에 반해, 음반이 유통되고 소비자가 이를 구매해 보관하는 방식은 그대로이기 때문입니다.
현재 음반은 그것이 갖는 의미와 주 목적에 비해 너무 많은 자원을 소모하고 있습니다.
이는 소비자 개인적 차원에서는 보관이 어렵다
는 문제를, 사회적 차원에서는 환경 문제
를 불러옵니다.
음반의 주 수요가 더이상 음악 감상으로부터 나오지 않는다는 사실은 자명하며, 이는 소비자와 연예 기획사, 유통사 모두 알고 있는 사실입니다.
그럼에도 불구하고 음반의 구성과 유통 방식, 보관 방식은 그대로이니 문제는 계속 생길 수 밖에 없습니다.
음반을 구매하고 활용하는 방식, 이것이 최선일까요?
앨범 구성이 쓸데 없이 많은 자원을 소모하여 문제라면, 딱 그것만 제공하면 됩니다.
굿즈와 포토카드, 팬 싸인회 응모권 등이 소비자가 원하는 핵심이 될 것입니다.
나아가, 이러한 앨범의 새로운 구매 형태로 토큰을 제안합니다. 이하 ‘토큰형 음반
'이라 하며, 아래에서 자세히 설명하겠습니다.
그래서 저희가 개발했던 서비스를 1줄 요약한다면, 토큰형 앨범
과 온라인 앨범 보관 플랫폼
입니다.
토큰형 앨범
은 기존의 앨범과는 달리, 소비자가 필요로 하는 구성품만을 제공하는 앨범입니다.
앨범을 구매하면 종이나 플라스틱으로 되고 CD가 포함되어 있는 기존의 실물 앨범을 받는 것이 아니라, 해당 앨범을 구매했다는 인증을 담은 토큰을 발급받게 됩니다.
이 토큰을 굿즈샵, 서점, 음반 유통사 등에 제시하여 실물 앨범에 포함되어 있는 굿즈, 포토카드, 팬 싸인회 응모권을 받을 수 있습니다.
온라인 앨범 보관 플랫폼
은 이러한 토큰형 앨범에 대한 구매 내역을 기록하고, 온라인 상에서 나의 앨범을 간직할 수 있는 플랫폼입니다.
어떤 가수의 어떤 앨범을 몇 개 샀는지, 이를 어디서 언제 교환하여 굿즈 등을 받았는지.. 토큰형 앨범 구매와 관련된 모든 것이 기록됩니다.
소비자는 앨범 구성품 중 정말 필요한 것만을 실물 앨범보다 저렴한 가격으로 구매할 수 있습니다.
기획사 및 유통사 입장에서는 실물 앨범에 비해 유통과 물류를 더 간편하게 할 수 있어(앨범 전체가 아니라, 굿즈와 팬싸인회 응모권 등만 제작하여 유통하면 되므로), 관련 비용을 절감시킬 수 있다는 이익이 있을 것입니다.
마지막으로, 사회적 차원에서의 문제를 해결할 수 있습니다. 토큰형 앨범은 보다 효율적이고 환경 친화적인 방식으로 음반 구매를 할 수 있는 방안을 제시합니다.
비즈니스 이익은 토큰형 음반 발급 및 구매 시 오는 플랫폼 수수료가 주가 될 것이며, 나아가 광고나 이벤트 등을 통한 이익도 기대해볼 수 있을 것 같습니다.
백엔드 개발 진행을위해 제가 맡았던 주 파트는 데이터베이스 설계 및 API 개발이였습니다. 이번 서비스에 요구되는 데이터간의 관계(Relation)가 꽤나 복잡해서, 며칠을 밤을 새우며 개발을했더니 반쯤 정신이 나갔었던 기억이 생생하네요😅
비슷한듯 보이지만 엄연히 다른 테이블(Table) 로써 정의해야하는 것들이 꽤 많았습니다. 가령 Album(앨범) 에 관한 테이블을 하나만 설계하면 될줄 알았으나, 스토어에 존재하는 Album 과 실제 구매하는 Album 을 따로 구분지어서 DB 설계를 진행해야 했습니다.
또한 ORM(Object Relational Mapping) 측면에서도 신경써야할 것들이 꽤 많았습니다. 테이블 간의 테이블 Foreign Key 로 1:N 관계를 매핑을 하는것뿐 아니라, 다대다 관계(N : N 관계) 를 정확히 이해하고 적용시켜야만 각 테이블간의 연결관계가 오점없이 설계될 수 있었습니다.
ERD 툴을 사용해서 데이터베이스를 표현하기전에, 테블릿 PC 에 먼저 데이터베이스를 설계하고 API 를 설계하기 위한 방법들을 계속해서 고민하고, 표현하는 작업들을 반복했습니다. 정말정말~~ 많이 고민하면서 설계가 꼬이지 않기 위해 노력했던 흔적들이에요!!ㅎㅎ
DB 설계는 아래와 같이 총 8개의 테이블로 진행되었습니다. ERD cloud 로 설계할때, 각 테이블 사이의 Relation 관계가 보시듯이 ManyToMany 와 Foreign 으로 아주 복잡하게 이어지기 떄문에, 별도로 ERD 툴에서는 표현하지 않았습니다.
위의 ERD 설계 내용을 보시면 Album, AlbumFrime, Count 모델의 차이점이 궁금하실것이라 생각합니다. 또 Photocard 와 PhotocardFrime 도 있습니다.
우선 Album 과 AlbumFrime 의 차이점을 먼저 말씀드리겠습니다.
Album 은 해당 앨범을 상징하는 "상징성 앨범" 이며, AlbumFrime 은 사용자가 실제로 구매한 "구매한 실제 앨범객체" 를 의미합니다.
이때 상징성 앨범이라는 표현이 햇갈리실 수 있습니다. 스토어나 앨범추천 등의 페이지에서 보여줄 앨범으로써, 유저가 구매한 실제 앨범과 다릅니다. 유저가 구매한 실제 앨범을 스토어에서 상세 정보를 띄어주는건 당연히 말이
안되겠죠~? 🤗
Count 모델은 이따 API 구현 내용에서 상세히 말씀드리겠습니다!
앨범과 마찬가지입니다. Album 과 AlbumFrime 을 구분해서 DB 를 설계했듯이, 포토카드도 "상징성 포토카드" 와 "실제 구매한 포토타드" 를 구분해놓았습니다.
테이블 간의 연결관계는 아래와 같은 필드들을 통해 구현할 수 있었습니다! 테이블간의 relation 이 꼬이지 않도록 엄청 고생했던 기억이 정말 생생하네요 🔥
제가 개발했던 API 는 아래와 같습니다. API 가 생각보다 많아요..!!
지금와서 제가 개발한 API 개수를 확인해보니 18개나 되네요 허걱쓰
(과거의 나 고생했다 토탁토닥👍)
(API가 많다보니 사진이 너무 많아져서, API 소개를 적당선에서 끊고 싶으나 그럴수가 없네요. 하나하나 열심히 소중하게(?) 개발한 것들 이라서요ㅋㅋㅋ😄)
그리고 아래는 저희 팀원들이 개발한 기타 API 입니다.
몰론 전반적인 API 기능 개발이 다 어려웠지만, 그 중에서 특히 장시간동안 시행착오를 겪고, StackOverFlow 를 계속해서 참고 및 활용하게 만든 API 들에 대해 소개해보고자 합니다:) 😎
설계 과정
과정1) 우선 Artist_type_id_seralizer 로 시리얼라이징을 진행해주면 현재 로그인된 유저에 대한 관심 아티스트들이 JSON 포맷으로써 my_artist 에 저장되도록 구현했습니다.
user = get_object_or_404(User, pk = user_id)
my_artist = Artist_type_id_serializer(user)
과정2) my_artist 에 담긴 JSON 은 User Table 을 기반으로 만들어진 것이라,
이 JSON 의 키(key)값 중에서 "userSubartist_type_list" 라는 key 가 존재합니다.
이 필드만을 활용하기 위해 " my_artist.data[userSubartist_type_list] " 와 같이 data 옵션을 통해 이 필드만을 추출하여 my_artist_data 에 저장하도록 했습니다.
my_artist_data = my_artist.data['userSubartist_type_List']
과정3) userSubartist_type_list 필드는 ManyToMany 필드로써,
아티스트 객체 그 자체를 저장하는 것이 아니라, 아티스트 객체들의 pk (primary key) 값을 저장하고 있습니다.
따라서 구독중인 이티스트 객체들의 pk 값을 my_artist_id_list 라는 리스트에 저장하도록 했습니다.
for artist in my_artist_data:
my_artist_id_list.append(artist['id']) # 관심 아티스트의 id 값을 저장
과정4) 관심 아티스트를 QuerySet (쿼리셋) 형태로써 구독중인 아티스트 데이터를 추출하기 위해, 우선 쿼리셋을 형성해야 했습니다. 이 과정을 particular_artist 와 queryset_list 변수를 통해 해결했습니다.
또한 for 문을 순환하며 기존 퀴리셋에 for문을 돌때마다 각 퀴리셋을 union 하는 방식으로 구현해냈습니다!
# 여러 쿼리셋을 병합하기 위해, 맨 앞에 위치한 하나의 쿼리셋을 queryset_list 에 할당
particular_artist = get_object_or_404(Artist, id = my_artist_id_list[0])
queryset_list = Album.objects.filter(artist = particular_artist)
for artist_id in my_artist_id_list:
particular_artist = get_object_or_404(Artist, id = artist_id)
album_list = Album.objects.filter(artist = particular_artist) # 각 관심 아티스트에 대한 앨범 QuerySet 리스트를 추출
queryset_list = queryset_list.union(album_list) # 기존 퀴리셋에 for문을 돌때마다 각 퀴리셋을 합침
#=> union 특징 : 중복되는 원소가 여러개 존재할때 하나만 저장함
#queryset_list.append(album_list)
과정1)
저희팀은 해커톤 당일 대회에서 이벤트 앨범을 구매하신 분에 한정해서 소정의 기념품 앨범을 드리는 깜짝 이벤트를 진행했었습니다.
과정2)
이를 위해 특정 유저가 이벤트 앨범에 대한 응모권을 응모했는지의 여부를 파악할 수 있도록 User 테이블에 "ticket_apply_complete" 필드를 선언하도록 구현했고, 이 응모완료 처리를 지금 이 API 에서 처리하도록 구현했습니다.
과정3)
또한 응모권 "응모하기" 버튼을 눌렀을 때 응모처리와 동시에 해당 유저가 구매한 응모권들 또한 사용완료 처리를 하도록 구현하도록 구현했습니다. 이와 동시에 몇개의 응모권이 응모되었는지를 확인할 수 있도록, Response 로 "총 30개의 응모권이 등록되었습니다!" 를 보내주도록 구현했습니다.
과정4)
응모권 사용완료 처리 기능을 구현하기 위해, 우선 AlbumFrime 객체에 대해 필터링을 하는 작업을 filter() 메소드를 통해 3번을 진행해줬습니다. 현재 로그인한 유저가 구매한 앨범중 미사용 응모권을 보유중인 앨범만 추출해내는 작업이었습니다. 이를 for 문을 순환하면서 contains_ticket 필드, 즉 앨범의 응모권 사용여부를 체크하는 필드를 true 에서 false 로 변경해줌으로써 응모권 사용완료 처리가 되도록 구현했습니다.
cf ) 코드가 한 이미지에 담기에 너무 길어서, 자세한 소스코드를 확인해보고 싶다면 제 깃허브 레포지토리를 확인하셔도 좋을듯합니다!😄
(API 일부내용)
앨범을 구매할 떄, 해당 앨범과 관련한 아티스트 테이블, 수록곡 테이블, 앨범 프라임(= 실제로 유저가 구매한 앨범) 테이블, Count 테이블등도 구매와 동시에 많은 테이블 객체들이 생성되어야 했습니다. 이 세부사항들을 신경쓰는 것들이 꽤나 조심스럽고, 빼먹은 것들이 없는지 계속 체크해야 했습니다.
또한 ERD 툴에서 DB 를 설계한 내용을 보면 Count 테이블이 있을겁니다. 이 Count 모델은 특정 유저가 실제로 구매한 앨범의 "개수(count) 에 대한 정보를 필드로써 가지는 테이블입니다. 이 테이블을 설계해야 하는 아이디어를 떠올리는 것도 꽤나 힘들었던 기억이 나네요! 😂
- Figma 의 기획 및 디자인을 최대한 꼼꼼히 확인하면서 DB 를 최대한 열심히 설계했으나, 예상치 못했던 특정 페이지에 대한 DB 설계를 고려하지 못해서, DB 설계를 많이 수정했어야 했습니다 🙄
Album 모델을 설계했으나, AlbumFrime 외에도 구매한 앨범의 개수를 Response 로 보내주기 위한 AlbumCount 모델을 설계했어야 했습니다.
또한 내가 담당한 페이지는 아니였지만, 해커톤 당일날 확인해보니 같은 팀원 백엔드 일원이 마이페이지에서 구매한 앨범을 조회 API 를 개발하지 않았던 이슈가 발생했었습니다.
프론트에서 일부 API 에 대한 개발을 하지 않았다는 컴플레인이 들어왔었고, 급하게 API 개발에 나섰어야 했습니다.
- 로그인 문제 - 쿠키(Cookie), 세션(Session), 토큰(Token) 이슈가 발생했었다.
Postman 에서는 API 를 개발하면서 로그인 이슈가 발생하지 않았었다.
그러나 Docker 로 배포이후 로그인에 문제가 있었음을 확인했다.
다시 Postman 에서 확인을 해보니, "CSRF Token Missing" 이라는 CSRF 토큰 관련 트러블 슈팅을 직면했다. 해커톤 당시 Axios 로 연동할 시간도 얼마 남지 않았었기 때문에, request.user 로 토큰 관련 로그인 기법을 사용하지 않고,
유저의 pk 값을 url 에 넘겨줌으로써 로그인 이슈를 급하게 마무리 지었어요!
- 외부 DB를 사용하지 못해봤다니 아쉽다!🤔
- 배포 관련
대회 당일날 급하게 API가 수정되야 할 것들이 있어서 다시 수정후 재배포를 해야했었는데, DockerHub 계정이 존재함에도 불구하고 docker login 에서 계속해서 실패했었습니다.
대회장의 와이파이 문제였을지, 아니면 DockerHub 의 문제인건지 다시 확인해 볼 필요가 있겠네요!
(몰론 당시 급하게 배포를 했어야했어서, 마음이 조급했던게 가장 큰 원인이겠죠~?)
- 기타 Django, Python 문법 관련 이슈
Django 관련 : 원하는 데이터들을 걸러내고 str 로 형변환후 json.dumps 를 사용해서 str 을 JSON 으로 변환하여 Response 를 보냈으나, 형태가 굉장히 안예뻤다.
Python : Album - AlbumFrime / PhotoCard - PhotoCardFrime 모델의 관계
=> 파이썬의 상속 관계 (부모-자식 클래스) 를 활용하려고 했으나, 필드 값들이 상속되는 것은 맞지만, 뜬금없는 새로운 객체가 생성되었다.
상속 관계를 없애고, 필드 값들을 카피시키는 방식으로 이슈를 해결했었다. 상속 관계는 아니지만, 유사 상속 관계를 가지는 느낌으로 구현했다.
1) SQL 에 대해 깊이 이해하는 시간을 가져봐야겠다.
Query (쿼리) 에 대한 깊은 이해가 있더라도, 특히 어려웠던 API 개발이 쉬워졌을 것이라고 생각이 드는 것들이 존재합니다🤔
그 중 가장 떠오르는 API 는 "관심 아티스트들의 상징성 앨범 리스트 조회" 와 관련한 것이네요!
(이 API 는 위에서 어떻게 구현했는지 자세히 설명했습니다!)
다시 코드를 확인해보니 QuerySet 을 정말 어려운 방법으로 구현했는데, SQL 를 사용만했다면 원하는 필드를 손쉽게 추출해낼 수 있었다는 생각이 들었습니다.
2) 리팩토링(Refactoring) 을 하는 습관이 필요하다고 느꼈다!
정말 개발 당시 며칠밤을 새우며 열심히 개발했던 DB 와 API 들이지만, 다시 생각해보면 직전에 언급했듯이 충분히 더 좋은방법으로 개선할 수 있는 API 들이 다수 존재하는 것 같습니다.
프로젝트를 단순히 끝낸다는 것에 그치지지 않고, 소스코드를 계속 리팩토링하며 유지.보수를 꾸준히 이어가는 습관이 필요하다고 느꼈습니다.
3) TDD (테스트형 개발)
Admin 사이트에 의지하지 않고, TDD 와 같이 테스트 주도 개발을 잘 하도록 노력하는 습관을 길려야 겠다고 느꼈습니다. Django 의 큰 장정중 하나가 Admin 에서 쉽게 확인홰보고 API 개발이 가능하다는 것인데,
막상 현업 개발자들은 평소 작업화면을 들춰보면 60% 이상이 다 Test 페이지만 보고 있다고 할만큼, 유지.보수에 있어 매우 중요하다고 느낍니다.
이 또한 백엔드 개발자로써 계속 학습해나가고, 성장해야 할 부분이라고 생각합니다😉
4) 보안성 강화 - 쿠키(Cookie), 세션(Session), CSRF 등
사실 대회에 출전했던 프로젝트였기 떄문에 다행이지, 만일 실제로 배포가 된 후에 수익 창출까지 생각을 했다면 보안성에 있어서 이번 프로젝트는 아주 취약한 문제점들이 꽤 많다고 생각합니다. 앞으로도 이 보안성을 이슈를 위해서도 꾸준히 개선해나가야 할 필요를 느꼈습니다.
5) 함수형 프로그래밍(Function Programming), 클린코드(Clean Code)
적절한 개선방향의 중앙지점을 찾을 수 있는 능력
이번 프로젝트에서 프론트와 백엔드 사이에서 서로에게 편한 방향만 추구하려는 팀원들도 간혹 있어서, 앞으로는 각 파트 및 팀원들간에 적절한 의견 조율을 통해 서로에게 도움이 될 수 있는 그 중앙점을 잘 찾는 사람이 되어야겠다고 느꼈습니다.
리딩의 방향이 잘못되었을 때, 브레이킹을 걸 수 있는 능력
또한 아이디어 빌딩과 팀 분위기의 전반적인 리딩의 방향이 간혹 잘못 새어나가는 경우가 발생했었는데, 이 경험을 통해 느낀것은 "리딩의 방향이 잘못되었을 떄 브레이킹을 거는 능력" 이 중요하다는 것입니다.
이번 대회를 통해, 추후 개발자로써 학습 방향도 눈에 보일 수 있게 되었습니다. 소중한 경험을 제공해주신 멋쟁이사자처럼, 그리고 무엇보다 우리 팀원들 정말 고생많으셨습니다🤗