오늘은 SSUEB 프로젝트에 대한 회고를 해보려고 한다.
프로젝트 설명
SSUEB은 총 7주 간 진행됐으며, SSAFY 2학기 과정 중 공통 프로젝트에 해당한다.
SSUEB은 반려인과 전문가를 매칭해 실시간 화상 상담을 진행할 수 있는 서비스이다. 여기서 전문가는 곧 실행 예정인 국가자격증 반려동물행동지도사를 취득한 사람을 뜻한다. 핵심 기능은 openvidu를 이용한 실시간 화상 상담 서비스이다.
좋았고 배울 수 있었던 점
협업
기획 단계가 매우 순조로웠고, 모든 팀원이 자신의 의견을 적극적으로 이야기했다. 또한, 서로 의견을 긍정적으로 받아들였으며, 적절한 피드백도 주고받았다. 기획뿐만 아니라 이후 개발 과정에서도 서로 의견 충돌이나 갈등은 없었다. 팀원 모두가 서로의 의견을 존중하고, 소통하려는 태도를 갖고 있던 것이 순조로운 진행에 큰 기여를 했다고 생각한다.
팀원 6명을 앉은자리대로 2인씩 나눠, 개발을 진행했는데, 나와 함께한 팀원과 역할 분담을 잘했다고 생각한다. 서로 맡은 바에 책임감을 갖고 시간 내에 완성했으며, 추가 기능도 서로 잘할 수 있는 부분을 맡아 구현했다. 각자의 오류를 공유하고 함께 해결하기도 했다. 본인이 경험했던 오류를 보면 빠르게 해결해 줄 수 있기도 했다. 이 과정에서 협업의 중요성을 다시 한번 깨닫기도 했다.
프로젝트 후반부에는 1~2시간 주기로 한 기능의 Backend와 Frontend를 번갈아 가며 개발하기도 했는데 이때 호흡이 잘 맞는다고 느꼈다.
추가 기능 구현
처음 맡았던 회원 관리 기능을 약 2주 정도 빨리 끝내, 다른 팀원을 돕고 추가 기능을 구현할 수 있었다. SSAFY에서 주어진 시간에 개발에 집중한 덕에, 회원 관리 기능을 빠르게 끝낼 수 있었다.
이후 했던 일은 첫 번째로는 추가 기능이었던 공지사항과 자유 게시판을 구현했다. Backend는 다른 팀원이 미리 구현해 둬, 나는 Frontend 구현을 맡아 커뮤니티 기능을 완성했다.
두 번째는 관리자 페이지 구현이었다. 추가 기능이었기에, 기존에 구현해 두었던 모듈을 활용하는 방향으로 구현을 진행했다. 문자 및 이메일 전송, 회원 탈퇴 메서드를 재사용하고, 그 외에 회원 목록 조회 메서드를 만들어 API를 구현했다.
맡은 부분을 끝까지 완성했고, 추가로 다른 부분까지 맡아 개발 경험을 더 쌓을 수 있어 좋았다.
휴대폰 인증 구현
Redis와 Naver Cloud Platform Open API를 사용해 휴대폰 인증을 구현한 점이 제일 보람 있었다. (휴대폰 인증 구현 코드) 처음으로 Redis라는 새로운 데이터베이스에 대해 학습했고, NoSQL을 사용해 보는 것은 처음이었기에 더 흥미로웠다. 휴대폰 인증 과정은 다음과 같다.
- 사용자가 휴대폰 번호 입력 후 인증하기 클릭
- 서버로 휴대폰 번호 전송 후 Key(휴대폰 번호), Value(인증번호, 6자리 난수)로 Redis에 3분 간 저장
- 인증번호를 휴대폰 문자 메시지로 전송
- 사용자가 화면에 인증번호를 입력하면 서버로 전달해 Redis에서 휴대폰 번호와 인증번호를 비교해 인증 확인
- 인증 완료 시, Frontend에 인증 완료 Flag를 true로 변경
어려운 구현 과정은 아닐 수 있지만, 나에게 Redis, Naver Cloud Platform API와 같이 새로운 부분이 많았다는 점이 인상 깊었다.
Frontend 구현
우리 팀 프로젝트 CSS와 Vuetify의 약 90%는 내가 구현하지 않았을까 하는 생각이 있다. 백엔드를 지망하는 6명의 교육생이 모인 상황이라 Frontend에 다소 관심이 떨어지는 분위기가 있었다.
나는 서비스에서 사용자에게 더 많은 영향을 미치는 부분은 Frontend라고 생각했고, 백엔드 못지않게 Frontend도 중요하다는 생각이 있었다. 이에 관련 라이브러리를 적극 활용해 적은 시간으로 최대의 효율을 발휘해 Frontend의 완성도를 높여보고자 했다.
그 결과, Vuetify와 SweetAlert로 사용자 편의를 높이고 빠른 시간 내 Frontend 작업을 해낼 수 있었고 라이브러리 활용 능력을 기를 수 있었다. 팀원들이 Frontend 걱정을 많이 했는데 모두 만족스럽다며 칭찬해, 팀에 조금이나마 도움이 됐다는 생각에 뿌듯했다.
유효성 검사
회원가입 구현을 맡아 정보를 입력받고 모든 정보에 대해 유효성 검사를 수행했다. 어디서 유효성 검사를 할지 고민하던 중, 반 컨설턴트님께서 유효성 검사는 프론트와 백 모두에서 하는 것이 좋다고 조언해 주셨다.
- 프론트엔드에서만 검사
개발자 도구로 손쉽게 유효성 검사를 뚫을 수 있다. - 백엔드에서만 검사
에러가 발생할 데이터를 미리 검사하지 않아 요청을 매번 보내는 것은 비효율적이다. 요청을 기다려야 해 사용자도 불편할 수 있다.
따라서 회원가입 시 프론트와 백 모두에서 꼼꼼히 검증을 거치도록 구현했고, 파일의 경우 형식과 용량, 개수 등을 모두 검사했다. 이전 프로젝트에서는 백에서만 유효성 검사를 수행했는데, 바람직하지 않은 방법이었다는 것도 알게 됐다.
Vue의 생명주기
Vue의 생명주기에 대해 제대로 다시 공부해 볼 수 있었다. created()
이후 mounted()
가 실행되는 것으로 생각했으나 동기로 처리돼 created()
가 끝나지 않았어도 mounted()
가 실행되는 경우가 존재했다. 따라서 created()
에 async를 걸어 API 요청을 보내 데이터를 가져오는 동안은 화면에 Loading을 띄우고 API 요청이 끝나면 화면을 띄우도록 설정했다.
async created() {
this.loaded = false;
this.boardSummaryList = await getNoticeBoard();
this.loaded = true;
}
어렵고 아쉬웠던 점
협업
다른 기능을 담당했던 팀원이 <br />
로 모든 줄 바꿈을,  
로는 빈 공간을 만들고 정렬을 했으며,
요소를 3열로 보여주기 위해 요소 3개씩 for문을 여러 개 쓴 HTML 코드와
적용되지 않는 CSS와 Vuetify Class가 가득한 코드를 짜두었다.
심지어 pagination, date-picker 등 일부 요소는 구현조차 돼 있지 않았다.
코드를 보고 수많은 생각이 들었다. 어떻게 상황을 풀어나가야 할지 어려웠다. Working Day로 8일이 남아있었는데, 아직 구현하지 못한 기능도 있었고 본래 계획은 4일 뒤 기본 기능을 완성하는 것이었기에 맘이 급했다.
우선 코드를 정리했는데, 다행히 코드를 정리하고 Vuetify 요소를 적절히 변경하는 것만으로 대부분의 문제가 해결됐다. 대표적으로 팀원이 알려줬던 문제는 아래와 같았다.
- 캘린더 삽입 불가 → v-calendar를 사용하니 해결 (비어있는 채로 받아서 왜 안 됐던 것인지는 모르겠다.)
- Expansion panels 정렬 문제 → 띄어쓰기로 정렬돼 있던 요소
v-col
로 수정해 해결 - Vuetify Dialogs 정렬 문제 → 적용되지 않는 class와 css 정리 후 dialog에
width
를 설정해 해결
되돌아보니 협업 능력이 부족해서 위와 같은 문제가 발생했었다고 생각한다.
우선 서로 진행 상황을 상세히 공유하고 적극적으로 소통해 진행이 잘 안 되고 있는 부분은 서로 도와야 했다. 이처럼 했다면 8일밖에 남지 않은 상황이 되어서야 알게 돼 급하게 문제를 해결하는 일은 없었으리라 생각한다.
또한, 이를 해결하는 과정에서도 내 행동이 올바른 협업과는 거리가 멀었던 것 같다. 나는 코드를 받은 뒤, 기존 코드의 95% 이상을 혼자 새로 작성했다. 당시에는 시간이 부족하다고 생각해 혼자 빨리 해결하는 것이 옳다고 판단했다. 하지만, 되돌아보니 기존 코드를 작성한 팀원분께서는 내가 수정한 코드를 봤는지 모른다. 아마 오류를 해결한 이유도 명확히 모를 수도 있으리라 생각한다. 시간이 부족하더라도 함께 이야기하며 차분히 코드를 수정해 갔다면 내가 수정한 방향보다 더 좋은 방향으로 수정할 수 있었으리라 생각한다.
더 나아가 매주 코드 리뷰를 해뒀다면 이렇게 급박한 상황을 방지할 수 있었으리라 생각된다. 다음에 프로젝트를 진행한다면 매주 코드 리뷰를 진행하면 좋을 듯싶다.
Image File 보여주기
회원 관리 기능을 구현하면서 너무 많은 시간을 소비해 아쉬웠던 부분이다. file을 다루는 방법과 Infra에 대한 지식이 부족해, 시간을 오래 보내지 않았나 싶다. 이번 프로젝트에서는 파일 저장이 쓰이는 곳이 많았다. 예약 첨부파일, 프로필 파일, 반려동물 파일, 게시판 글 첨부파일을 저장 및 수정하는 기능이 있었다.
- Backend resources 내에 파일을 저장하고, Vue에서 해당 폴더로 접근
- 처음 생각했던 방법은 Backend에서 절대경로로 파일을 저장하고, Frontend에서도 절대경로로 접근하는 것이었다.
Not allowed to load local resource
오류 발생- Vue에서는 local file 접근 불가능, Vue Project 내 폴더만 접근 가능
- 절대경로로 Backend에서 Vue asset 내에 파일을 저장하고, 해당 폴더로 접근
- Front가 무거워짐
- 계속해서 파일이 쌓일 텐데, Front에 많은 파일이 들어가는 것은 좋지 않은 방법이라 생각해 제외
- base24
- 이미지를 base64로 인코딩해 json으로 다른 데이터와 함께 API 응답으로 전송
- 프로필 사진과 같이 파일 크기와 용량을 조절해도 괜찮다면 이 방법을 사용해도 괜찮다고 컨설턴트님께서 말씀
- 그러나 4번의 방식을 더 추천해 주셔서 반려
localhost:port/files/
로 접근 시, 폴더로 접근하도록 설정- 컨설턴트님께서 먼저 이미지 파일은 어떻게 했냐고 여쭤봐 주셨고, 알려주신 방법으로 해결할 수 있었다.
- 특정 폴더에 파일을 저장한 뒤,
domain/image/
로 접근 시 NginX 설정으로 해당 폴더를 바라보도록 설정하라고 알려주셨다. - local에서도 비슷하게 작업할 수 있게 설정할 수 있는 방법을 알아봤고, Spring Boot에서 Config 파일로 하는 방법을 알게 됐다.
- 아래 코드를 WebConfig 파일에 넣으면,
/api/files/**
로 요청이 들어오면filePath
로 접근한다.// WebConfig.java @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/api/files/**") .addResourceLocations("file:///"+filePath); }
아쉽게도, NginX 설정은 못하고 Spring 설정을 유지한 채로 프로젝트는 마무리됐지만, 파일 처리에 대해 많이 배울 수 있었다.
JPA
처음으로 JPA를 사용해 볼 수 있었고, 프로젝트 첫 주에는 JPA 학습을 중점적으로 시간을 보냈다. 사용해 보니 JPA는 직접 쿼리문을 작성하지 않아도 되어 편리하다는 가장 큰 장점이 있었다. 이전에 사용했던 MyBatis와 비교해 봤을 때 특히 기본적인 CRUD 구현 시간을 대폭 줄여줬다.
public class Attach {
// 생략
// reservation 테이블의 PK인 reservationNo을 가져왔지만 연관 관계 설정 없음
@Column(name="reservation_no")
@ApiModelProperty(value = "이 파일과 관련된 상담 번호", dataType = "int", example="0")
int reservationNo;
// 생략
}
그러나 첫 구현인 만큼 아쉬운 부분도 많았다. 먼저 Entity 간의 연관 관계 설정을 전혀 하지 않았다. 따라서 여러 테이블에서 값을 가져올 때 nativeQuery로 일일이 Join 쿼리문을 작성한 경우가 있었다. 최대 3개의 테이블을 Join 했었기에 JPA를 사용하는 것이 생산성 측면에서 더 좋지 않았을까 싶다.
또한, QueryDSL을 import 해두고 사용하지 못한 점도 아쉬웠다. 처음 프로젝트 시작 전 SSAFY에서 제공하는 Skeleton을 기반으로 프로젝트를 진행했는데 기본적으로 QueryDSL이 붙어있었다. JPA에 대한 학습이 부족해 QueryDSL의 존재를 알지 못했고, JPA에 대한 학습이 부족해 QEntity를 모두 생성해 둔 채 아무도 사용하지 않았다.
팀원 간 정보 공유
매일 오전, 오후 조회 시간에 반 컨설턴트님께서 프로젝트에 도움 되는 조언들을 몇 개 해주셨고, 프로젝트 진행에 정말 많은 도움이 됐다. 대표적으로 앞에서 언급한 파일 처리, 유효성 검사가 있고 그 외에도 API CALL 횟수, API 설계 방법 등 다양한 정보를 얻고 개발 시 도움을 받을 수 있었다.
다만, 이 정보를 들은 뒤 실행에 옮긴 팀원도 있었고, 그렇지 않은 팀원도 있었다. 나 역시 전부 반영했다고 장담하진 못하지만 이 정보를 회의 때 다시 한번 공유했으면 좋았겠다는 생각이 들었다. 새로 알게 된 내용을 항상 함께 공유하는 것도 협업의 중요한 부분이라는 것을 깨달았다.
실제로 일부에서 DTO가 아닌 Entity를 그대로 반환하거나 API에서 유효성 검사를 전혀 하지 않기도 했다. 컨설턴트님께서 말씀해 주신 부분인 만큼 더 신경 써서 정보 공유, 아니면 코드 리뷰만 했더라도 이 부분을 함께 고쳐나갔을 텐데라는 아쉬움이 남았다.
버그 수정
겉으로 봤을 때는 완성된 것 같지만, 사실은 보이지 않는 많은 오류를 안고 있는 프로젝트이기도 하다. 대표적으로 쉽게 발견할 수 있는 오류는 아래와 같다.
- 화상 상담 시, 화상 상담 URL로 창을 여러 개 띄우면 페이지 속 나를 비추고 있는 영상이 띄운 창 개수만큼 늘어난다.
- 무한 스크롤이나 페이지네이션이 구현돼 있지 않아 10,000건이 넘는 목록을 띄우면 화면이 멈춘다.
- 원인은 정확히 파악하지 못했으나 결제가 제대로 안 되는 경우가 있다.
이 외에도 실제로 서비스를 사용한 사용자에게 피드백을 받았다면 더 많은 오류가 있지 않았을까 싶다. 해결하지 못한 이유는 시간 부족, 지식 부족이 가장 컸던 것 같다. 세 가지 모두 프로젝트 마감날 알게 된 오류였고 시간이 부족했다. 혹시라도 나중에 DEVELOP을 하게 된다면 이 부분을 보충하고 싶다.
화상 상담 기능
이 프로젝트의 가장 핵심 기능인 화상 미팅을 팀장님께서 전부 개발하셨다. 먼저 도와드릴 부분이 있는지 묻기도 했지만, 거절당했다. 내가 먼저 이미 올려두신 코드를 파악해 조금씩 도와드리거나 함께 해야 하는 이유를 명확히 말씀드려 설득했다면 도와드릴 수 있지 않았을까 하는 아쉬움이 있다.
역할 분담을 할 때 자신이 없어 핵심 기능을 함께 하겠다고 적극적으로 나서지 못한 것도 아쉽다. 화상 상담을 구현해 보는 경험은 해보지 못한 것이라 도움이 많이 됐으리라 생각한다. 나중에라도 이와 관련된 기능을 구현해 보면 좋을 듯하다. 마지막주까지 화상 미팅이 완성되지 못했었는데, 핵심 기능인만큼 여러 명이 함께 했으면 더 좋은 결과가 있지 않았을까 싶다.