캡스톤 프로젝트 회고

Y·2023년 6월 26일
0

프로젝트

목록 보기
4/5

교내 캡스톤 디자인 과목으로 앱 개발을 진행하게 되었다. 한 학기동안 진행했는데, 아이디어 선정하는 기간을 제외하면 순수 개발기간은 서버 기준으로는 한달 조금 넘는 기간, 클라이언트 기준으로는 두달 가까운 기간 소요된 것 같다. 추가적으로 이어서 프로젝트를 진행할 의향이 다들 있어서 더 이어질 수도 있을 것 같긴하지만, 캡스톤 교과목 자체는 종강했기 때문에 회고글을 작성한다!


프로젝트 개요

팀원 구성

팀원은 나 포함 세명으로, 서버 2명, 클라이언트 1명으로 구성되었으며 서버는 DB 담당과 API 및 인프라 담당으로 구분하였다. 나는 API와 인프라를 담당했다.

주제

사실 주제를 선정하는 데에만 한 달여정도의 시간이 소요돼서 개발도 많이 지체됐는데, 최종적으로 선정한 주제는 문해력 교육용 어플리케이션이다. 우선은 사회적으로 유의미한 앱을 만들고자했고, 따라서 최근 사회에서 문제가 되고 있거나 이슈가 되는 현상에 대해 고민하다가 문해력 문제를 떠올렸다.

팀웓들끼리 서로 요새 과제 등 때문에 글을 쓰려고 해도 단어가 잘 생각이 안나거나 문장을 다듬기가 힘들었던 적이 많았다, 글을 읽을 때 잘 읽히지 않았던 적도 많았다, 그런 얘기를 하다가 요새 사회적으로도 이슈라는 것을 떠올렸고, 이 주제로 앱을 만들어보면 어떨까요? 하게 됐다. 그래서 관련 자료들을 찾아보니 확실히 관련 기사나 연구들도 꽤 많고, 시장의 수요도 괜찮을 것 같다고 판단하게 됐다. 마지막으로 기존 앱을 확인했을 때도 그 수가 많지 않았다! 그래서 최종적으로 이 주제를 선정하게 되었다.

기술 스택

기술 스택으로 클라이언트는 Flutter, 서버는 Spring Boot로 구현하였으며 DB는 MySQL을 사용하였다. 인프라는 AWS EC2를 사용했다.

클라이언트의 경우 담당하시는 분이 Flutter를 공부하셨기도 했고, Flutter는 AOS,IOS를 한번에 구현할 수 있기 때문에 유저층을 넓게 확보할 수 있어서 좋을 것이라고 생각해 채택했다. 백의 경우 특별히 제약 사항이 없는데, 서버 담당 둘 다 사용할 수 있는 건 Spring Boot뿐이었고, 실시간 서비스를 구현하는 등의 이유로 node.js를 고려해봐야하거나 하는 상황도 아니기 때문에 Spring Boot로 채택했다.

DB의 경우 일단 나는 NoSQL도 사용해봤지만 다른 팀원분은 RDB만 사용해보시기도 했고, 기획 및 설계를 우선적으로 다 끝내고 개발을 쭉 진행하는 일정으로 계획했기 때문에 중간에 기획사항이 변경되는 상황이 생길 것 같지도 않았다. 또한 앱의 특성상 다양한 자료구조가 필요할 것 같지 않다고 예상(했었는데 아니었다...ㅠㅠ)해서 RDB를 사용하기로 했고, MySQL을 채택했다. 이건 둘 다 MySQL만 써봤기 때문에 당연하게도 이렇게 됐다.

인프라는 이전에 사용해본 적 있고, 가장 대중적으로 많이 쓰이는 AWS를 사용하기로 했다. 사실 인프라 구조나 스케일에 대해서는 고민이 좀 있었는데, 이 부분은 멘토님의 도움을 좀 받았다. 이 내용은 후술하곘다.


프로젝트 기획 및 설계

세부 기획

주제를 선정하고, 대략적인 방향성을 정했으므로 세부 기획을 해야했다. 더불어 UI도 디자인해야했다. 주제가 교육용 어플리케이션이고, 그와 관련해서는 아는 것이 별로 없다보니 기획 단계에서 어려움이 좀 있었다. 특히 어떤 방식이 문해력 학습에 효과적일것인가?에 대해서 의문이 있었다. 우선 기존 앱을 분석해보았을때, 크게 두 가지로 분류할 수 있었다.

1)문해력 교육앱(우리 앱과 주제 및 목적이 일치)
2)국어 교육앱

전자의 경우 기획 단계에서는 무료 앱은 1개(유료 앱은 몇개 더 있었던 것 같은데 유료라서 확인해보지는 못했다.), 후자의 경우 몇 개 있었다. 문해력 교육앱은 매일매일 하나씩 제공되는 글을 읽고 문제를 푸는 방식이었고, 게임도 있었다. 특이한 점은 시선추적 기능을 활용해서 사용자의 읽기 습관을 분석해준다는 점이다. 그리고, 국어 교육앱의 경우 그냥 글 읽고 문제 푸는 방식이었다. 즉, 기존 앱들은 글을 읽고 문제를 푸는 방식이 대부분이었다.

하지만 여기서 우리는 의문점이 생겼는데, 문해력이라는 것이 정말 글을 읽고 문제를 푼다고 해서 해결이 될까? 라는 지점이었다. 그를 위해서 관련 책이나 논문들을 찾아봤다. 공통적으로 강조하는 부분들은 직접 글을 읽고, 그 내용을 이해하고, 비판적으로 사고하는 것. 그리고 글을 읽은 후에 자신의 의견 등을 직접 글로 써보는 것 등이었다.

그래서 최종적으로 우리가 기획한 방향성은 사용자가 직접 글을 읽고, 키워드나 주제문 등을 찾아본 후, 직접 요약문을 작성하는 방식이었다. 또한 이에 대해 모범 답안용으로 예시 요약문도 제공해주기로 하였다. 우리는 사용자가 직접 글을 읽고 요약문을 작성하는 과정에서 스스로의 방식으로 글을 이해하는 것, 즉 내재화할 수 있을 것을 기대했다.

이렇게 방향성을 설정하고 나서, 다음으로 고민했던 부분은 문해력의 수준, 얼마나 문해력이 향상됐는지에 대한 정량적 지표의 제공이었다. 그래서 문해력 평가 방식에 대해서 앞서 언급했던 책 등을 참고했을 때 특정 하나의 테스트를 기준으로 모든 사용자의 수준을 일률적으로 판단하기에는 어려움이 있고, 그렇다고 해서 개별적으로 판단하기에는 현실적으로 한계가 있었다. 따라서 별도의 테스트 항목을 생성하지는 않고, 통계 등을 제공하여 자신의 수준이나 학습 성과를 스스로 판단할 수 있도록 하였다.

또한 기존의 앱에서 시선 추적 기능을 사용하고 있었는데, <난독의 시대>에 따르면 난독인 사람과 그렇지 않은 사람은 읽기 방식에 차이가 있다고 했다. 그래서 그 부분에 대해서는 여유가 생긴다면 구현해보기로 결정하였다.

UI 디자인

사실 UI 디자인이 진짜 힘들었다. 전체적인 색감은 파란색으로 잡았고, 최대한 심플한 느낌으로 디자인을 했다. 우리끼리만 보면 나쁘지 않은데...싶지만 또 다른 사람들이 보기엔 별로일 수도 있으니까, 주변에 디자인을 보여주고 피드백도 많이 받았었다.

설계

기획과 디자인을 같이 하고나서, 본격적으로 설계에 들어갔다. 백엔드단에서는 우선 나는 API, 다른 팀원분은 DB를 맡아서 각자 API 명세 작성, DB 스키마 설계를 진행했다. 그동안 교수님, 멘토님 피드백 받으면서 기획이 조금 수정되기도 해서 그 부분에 대해서 API/DB 스키마 설계는 조금씩 바뀌기도 했다.

이 과정에서 제일 고민했던 부분 중 하나가 '주제문 선택' 기능이었다. 키워드나 요약문의 경우 사용자가 직접 입력을 하는 방식이니 상관이 없었는데, 주제문의 경우 글의 각 문장 중에서 사용자가 주제문이라고 생각하는 문장을 선택하도록 하기 때문에 DB 설계를 어떻게 해야할지에 대해 고민이 많았다. 고민이 있었던 지점은 1)몇 개까지 선택할 수 있게 할 것이냐 2)다중 선택한다면 어떻게 저장할 것이냐 3)본문의 문장별 파싱은 어떻게 할 것이냐 였다.

키워드의 경우 3개까지만 선택할 수 있도록 기획을 했지만, 주제문의 경우 사용자에 따라 생각하는 주제문의 갯수가 다를 수 있기 때문에 상한선을 몇개로 둬야할지에 대해 고민이 많았다. 결국 고민을 하다가 제한을 두지 않기로 했는데, 이 경우에 그럼 DB에는 어떻게 저장을 해야하나? 하는 고민도 생겼다. NoSQL의 경우 배열 형식으로 저장을 할 수 있는데, MySQL에서는 JSON형식으로 저장하는 방법이 있기는 했지만 배열 형식으로는 불가능했다. 특수문자를 구분자로 넣어서 문장을 구분하고 파싱해서 넘겨주는 방식으로 결정했다.(좀 더 좋은 방법은 없을까 해서 멘토님께도 여쭤봤었는데 그냥 그렇게 하면 된다고 하셔서 그렇게 하기로 했다.)

파싱의 경우 서버단에서 할 수도 있고 클라이언트단에서 할 수도 있는데, 서버에서 처리해줄 수 있는 부분은 서버에서 해주는 게 좋을 거라고 생각해서 서버에서 해주기로 했다.


개발

1차 개발 (~중간 데모)

개발은 우선 내가 API를 맡아서 전반적인 개발을 진행하고, 인프라 세팅을 했다. 다른 서버 팀원분이 DB 세팅, 샘플 데이터 적재등을 맡으셨다.

로그인/로그아웃은 spring security+JWT를 이용했다. 앱 개발이기 때문에 세션보다는 JWT가 적절하다고 판단해서이다. 이외의 기능들도 CRUD 기반의 간단한 기능들이 많아서 전반적으로는 빠르게 개발을 끝냈다. 인프라 역시 EC2로 서버 올리고, GitHub Actions 기반으로 CI/CD 적용해주었다.

이번에 새롭게 구현해보게된 기능은 사진 업로드 기능이었는데, 마이페이지의 프로필 사진 기능이 있어서 구현하게 됐다. MultiPartFile형식으로 클라이언트단에서 파일을 받아와서 S3에 업로드 해주고, 그 링크를 DB에 저장한 후 response 로 링크를 보내주는 방식으로 구현했다.

개발 과정에서 고민이 있었던 부분들은 다음과 같다.

1) EC2 내부에 MySQL 설치 vs RDS 사용
EC2 인스턴스 내부에 MySQL을 설치해서 사용할지, 아니면 RDS를 사용할지에 대한 고민이 있었다. 어차피 서버가 단일 인스턴스이기 때문에, DB도 같은 인스턴스에 쓰는 것 자체는 큰 문제가 되지 않을 것 같았다. 하지만 굳이 RDS까지 쓸 필요가 있을까? 에 대한 의문이 들었다. 캡스톤이 아니었다면 비용도 문제였겠지만, 비용은 지원금이 나와서 큰 문제가 되지는 않았다. RDS를 쓰는 것은 여러 부가기능들이나 확장이 용이하다는 장점이 있어서인데, 우리 프로젝트에 그런 기능이 굳이 필요할 것 같지는 않았고, 실 유저가 들어올 것도 아니다보니 확장이 필요할 것 같지도 않았다. 그래서 어느쪽이 적절한지에 대해 고민하다가(사실 정답은 없을 것 같지만...) 멘토님께 여쭤보니 이 정도 사이즈에서는 굳이 RDS까지는 사용하지 않아도 괜찮을 것 같다고 조언해주셔서 EC2 내부에 MySQL을 설치해서 사용하기로 했다.

2) EC2 인스턴스 유형
인스턴스 유형이 항상 가장 고민이긴 하다. t2-micro를 쓰기에는 DB까지 들어있어서 그런지 서버가 쉽게 느려졌다. 이 부분도 멘토님께 조언을 구했는데, 어차피 지원금도 나오고 라이브 데모때 혹시나 문제가 발생하면 안 되니까 인스턴스 유형은 넉넉하게 잡는게 좋을 것 같다고 하셔서 t2-medium으로 설정했다.

3) 외부 API 연동
사전 기능 구현을 위해서 국립국어원 표준국어대사전 API를 사용했다. 사용자가 검색을 하면 해당 검색어를 외부 API와 연동해서 검색 결과를 보내주는 형식이었는데, 멘토님이 이 과정에서 혹시 통신 오류가 발생하거나, API 호출 횟수를 초과하는 경우가 발생할 수 있으니 자체 DB에 데이터를 백업용으로 적재해두고 호출을 실패하면 자체 DB에 있는 데이터를 반환해주는 형식으로 구현해주는 것이 좋다고 하셨다. 마침 표준국어대사전에서는 전체 데이터를 csv/json 파일 등으로 제공해주고 있어서 해당 데이터를 우리 DB에 적재해줬는데(DB 담당 팀원분이 고생해주셨다!) 문제가 발생했다...

특정 한 단어는 1,2,3... 이런식으로 뜻이 여러개로 나뉠 수 있는데(ex.의사1:일정한 자격을 가지고 병을 고치는 것을 직업으로 하는 사람., 의사2:무엇을 하고자 하는 생각. 이 경우 단어 코드는 의사1, 의사2가 다름.) , 단어1에 대해서도 내부적으로 뜻이 여러개(ex.의사1:일정한 자격을 가지고 병을 고치는 것을 직업으로 하는 사람.,『법률』 서양 의술과 양약으로 병을 고치는 것을 직업으로 하는 사람) 일 수 있다. 이 경우 API를 통해서 호출하면 단어1에 있는 뜻은 하나만 반환되는데(ex.의사1에 대해 '일정한 자격을 가지고 병을 고치는 것을 직업으로 하는 사람'만 반환) , 우리 DB의 데이터는 모든 의미가 다 저장 (단어 코드 A에 대해서 의사1 일정한 자격을 가지고 병을 고치는 것을 직업으로 하는 사람, 의사1 『법률』 서양 의술과 양약으로 병을 고치는 것을 직업으로 하는 사람 두 데이터가 다 저장돼있음.) 돼있어서 PK의 설정과 북마크 구현에 문제가 생겼다.

당연히 단어 코드를 기준으로 PK를 설정하려고 했는데 그게 안돼서 일단은 auto increment키를 하나 더 설정해주면서 해당 문제는 해결했다. 문제는 북마크였다. 사용자가 단어를 검색한 후에 한 번 더 보고싶은 단어는 북마크를 해서 등록할 수 있는데, 북마크를 볼때마다 api를 연동할 수는 없으니 북마크에 있는 단어는 DB에 있는 정보를 join하는 형식으로 구현돼있었다. 그런데 1)api 호출했을 때 DB에 있는 PK(auto increment key)는 알 수 없는데, 단어 코드 기준으로 join도 안됨 2)통신 상태에 따라 사용자가 확인한 단어의 뜻이 달라질 수 있음(API 결과 or DB 결과) 의 문제가 있어서, 결국 북마크 데이터를 저장할 때 단어 뜻도 함께 저장해줘서 그걸 그대로 반환해주는 방식으로 수정해서 문제를 해결할 수 있었다.

4) 주제문 문장 파싱
앞서 언급했던 문장 파싱이다. 처음엔 온점을 기준으로 파싱하면 될 줄 알았는데 생각해보니 문장의 형식이 매우매우 다양해서... 정규표현식을 하나 만들어서 파싱을 해줬다.

^“[가-힣]*.*”\s-\s[가-힣|\s]*|“*[ㄱ-ㅎ가-힣a-zA-Z0-9\s\[\]\‘\’\\'\",\“\”\-\\(\)\『\』\~\·\*]*”*[?|!][\s]|“*[ㄱ-ㅎ가-힣a-zA-Z0-9\s\[\]\‘\’\\'\",\“\”\-\\(\)\『\』\~\·\*\?\!]*”*[.]

으로 했다. 인용구까지 포함해서 대부분은 이 정규표현식으로 전부 문장별 파싱이 되는데, !,?와 같은 경우는 문장의 끝에 나오는 것과 중간에 나오는 것이 구분이 안 돼서... 이 부분은 구분하려면 문장의 끝에 나오는 경우에는 뒤에 띄어쓰기를 해주고 중간에 나오는 건 뒤에 띄어쓰기를 빼줘야한다.

사실 좀 더 빠르게 구현을 끝낼 수 있었을 것 같은데, 학기 중이라 다른 과목들도 있다보니까 이 정도 속도가 최선이었던 것 같다. 서버는 이렇게 마무리가 됐고... 클라이언트 작업까지 해서 중간 데모가 12주차였는데, 이때까지는 계획한 부분은 전부 마무리가 됐다!

2차 개발 (~최종 데모)

중간 데모까지 끝내고나서 15주차 최종 데모까지 시간이 좀 남아서, 뭘 더 해볼지에 대해 고민하다가 이전에 얘기했던 시선 추적 기능을 넣기로 했다. 처음부터 구현하기에는 우리 전부 인공지능/딥러닝 경험이 딱히 없었기 때문에 당연히 무리였고, 그래서 오픈소스나 외부 SDK를 찾았다. 그래서 찾은게 시소 SDK 였다. 일단 시선추적 SDK자체가 별로 없었는데... 이 SDK는 무료이기도 하고, 우리가 flutter를 쓰고있어서 flutter에서도 가능할 SDK를 찾고있다보니 해당 SDK가 적합해보였다. 그래서 이 SDK를 사용해서 시선 추적 기능을 넣기로 했다.

공식 문서가 정리가 잘 안 돼있고 기술에 대해서 설명도 잘 안 돼있어서 처음엔 좀 헤매긴했는데 샘플 앱 코드 올라온 코드들 확인하면서 우리 앱에도 해당 SDK를 붙일 수 있었다. (클라이언트 팀원분이 정말 고생해주셨다!ㅠㅠ)

일단 시선추적 기능을 붙이긴 했는데, 이걸 결과로 어떻게 보여줘야 하나?에 대한 고민이 있었다. 그래서 정량적으로 어떻게 분석해야할지 알아보려고 관련 논문을 찾아봤었다. 참고한 논문으로는 <능숙한 독자와 미숙한 독자의 읽기 방식 차이:어휘의 난이도와 적절성을 중심으로, 신명선, 신희성> , <시선추적 기법을 활용한 한국어 읽기과정 분석 연구 -평균적인 독자와 읽기에 능숙한 독자를 중심으로- , 김현진> 이 있었다. 후자의 논문에서는 능숙한 독자가 평균적인 독자보다 읽기 고정 시간이 짧다는 연구 결과를 밝혔는데, 전자의 논문에서는 미숙한 독자가 오히려 읽기를 포기하여 읽기 고정 시간이 능숙한 독자보다 더 짧았다는 연구 결과를 밝혔다. 이렇게 상반된 연구결과를 보건대, 우리는 단순히 시선 고정 시간 등과 같은 수치로는 제대로된 분석 결과가 될 수 없겠다고 판단했다. 따라서 그냥 시선이 머물렀던 곳에 대해 시각화를 해주기로 결정했다.

시소 SDK를 사용하면 실시간으로 시선을 추적한 위치 좌표값을 받아오는데, 이를 기반으로 시선이 가장 많이 머물렀던 곳들을 저장하고 시각화하여 보여줄 수 있겠다고 생각했다. 서버 개발이 끝났으니까 나도 이 부분은 같이 고민을 했는데, 고민했던 부분은 좌표가 너무 많이 들어온다는 것이었다. 실시간으로 끊임없이 값이 들어오는데, 이걸 전부 배열 형식으로 저장해서 하나하나 카운트하기에는 메모리 용량 문제가 있을 것으로 보였다. 그래서 다같이 고민하다가 결정한 구현 방식은 다음과 같았다.

우리 앱에서 글을 보여줄 때 기본 폰트 크기는 18pt였다. 가장 낮은 DPI를 기준으로 1 픽셀의 크기, 그리고 18pt일 때 한 글자는 몇 픽셀인지를 기준으로 좌표값을 나눠줬다. 예컨대 0-30을 전부 0, 31-60을 전부 1, 이런 식으로 계산하는 방식이었다. 그렇게 해서 좌표값의 범위를 줄여서 카운팅한 후, 가장 상위의 20곳에 대해 시각화해주는 방식으로 구현하였다. 좌표값의 범위는 기기에 따라서 달라질 수 있으니 최대한 넓은 값으로 배열을 설정해줬다.

이런 방식으로 시선 추적기능까지 성공적으로 구현할 수 있었다!

이후

다사다난한(?) 한 학기간의 캡스톤을 성공적으로 마무리할 수 있었다. 좋은 팀원분들을 만나서 팀플 과정에서 문제도 정말 전혀 없었고, 성공적으로 마무리에다 추가적인 개발까지 할 수 있어서 정말 뜻깊었던 것 같다. 이전까지는 팀 프로젝트라고 해도 해커톤처럼 빠른 기간동안 만들거나, 혹은 백엔드는 나 혼자이거나 한 경험뿐이었는데 이번 캡스톤을 통해 (물론 캡스톤도 아주 시간이 여유롭지는 않았지만) 한 학기라는 상대적으로 긴 시간동안 프로젝트를 진행했고, 또 백엔드도 다른 팀원분과 분담해서 진행해서 새로운 경험이기도 했다. 기획이 많이 엎어졌어서 그런지 수정사항도 꽤 많아서 문서화가 정말 중요하다는 것도 깨닫기도 했다. 또한 Spring Boot를 이용한 프로젝트는 두 번째여서, 이번 프로젝트를 계기로 Spring Boot를 조금 더 익숙하게 사용할 수 있게 되기도 했다. 개발적으로도, 개발 외적으로도 배운 점이 많았던 프로젝트였다.

아무튼 이전의 프로젝트에 비해서 상대적으로 긴 시간동안 진행된 프로젝트이기는 하지만 학기 중이라 캡스톤에 쓸 절대적인 시간이 많지는 않았어서(...) 추가적인 리팩토링같은 경우는 방학 때 기회가 된다면 좀 더 진행해보고 싶기는 하다!

profile
개발자, 학생

0개의 댓글