Swith 팀 프로젝트 회고록

dev_jubby·2022년 9월 12일
1

회고록

목록 보기
1/1

같은 목표를 가지고 같이 공부할 사람을 찾는 웹 사이트 구현 프로젝트 (2022/08/17 ~ 2022/09/01)


💡 주제 선정

각 팀원이 하고 싶은 주제를 몇 가지 제안하고, 그중 투표를 통해서 주제가 선정되었다. 맛집 리뷰사이트, 일정 관리사이트, 문제 풀이 사이트, 퀴즈 만들기 사이트 등 여러 주제가 제안되었고, 감사하게도 그중에 내가 제안했던 스터디그룹을 찾는 사이트가 주제로 선정되었다!



참고 웹 사이트 선정

위스터디 https://westudy.kr/
공부작당소 https://www.gongzakso.com/
위 두 개의 사이트와 앱을 참고하게 되었다. 우리가 개발하는 것은 웹 사이트이지만, 앱에서 제공하는 좋은 UI&UX가 있다면 참고해서 웹 사이트에 적용해도 좋을 것 같다는 생각이었다.



📄 기능 설계

처음 어떤 서비스를 제공하는 사이트를 운영할 것인지 팀원들과 회의를 통해 주요 기능을 정하고, 추가로 진행할 수 있을 때 제공할 부가 가능으로 나누어서 정해보았다.

후에 세부 기능을 작성해보자고 해서, 나는 구글 시트로 간단하게 요구사항 정의서 같은 템플릿을 제작해서 팀원들에게 공유했다. 위에서 크게 정했던 주요 기능을 조금 더 세부적으로 나누어 프론트에서 필요한 이벤트들이나, 백에서 처리해야 할 것들을 조금 더 자세하게 설계해보았다.



💬 업무 분담

리더는 시하님이, Front-end는 세준님, 유하님, 미진님이 맡게 되었고 Back-end은 시하님과 내가 맡게 되었다. 하지만 프로젝트를 진행하다 보니 Front-end가 할 일이 많아 거의 Full-stack으로 하게 되었다(?)



💌 내가 담당한 기능과 이슈


1. 논리적 데이터 모델링

설계 기간에 시하님과 함께 DB 모델링을 했다. 주요 기능을 미리 설계해두었더니, DB 구조를 생각하는 데 훨씬 수월했다. 처음엔 User, Studygroup, Studymember, Likes 정도의 table만 설계했었다.

❗ 하지만 개발을 진행하면서 생각해보니, 스터디 가입과 가입한 스터디 그룹을 관리하는 기능만 있고 그룹 멤버들 간에 서로 소통할 수 있는 창구가 없다는 걸 깨닫게 되었다. 그래서 가입한 멤버들끼리 게시물을 올리고 댓글을 달 수 있는 Studylounge라는 공간을 추가로 개발하기로 결정하게 되었고, 나중에 StudyloungeReply table을 설계하게 되었다.


2. 물리적 데이터 모델링

/* init.sql file example*/

CREATE DATABASE swith; -- DB 생성 

USE swith; -- DB 사용
 
-- TABLE 생성
CREATE TABLE user (
	user_id VARCHAR(20) NOT NULL PRIMARY KEY, -- 유저 id
    user_password VARCHAR(45) NOT NULL, -- 유저 비밀번호
    hint VARCHAR(10) NOT NULL, -- 비밀번호 찾기 질문 (1, 2, 3, ..)
    hint_answer VARCHAR(50) NOT NULL, -- 비밀번호 찾기 답
    user_name VARCHAR(6) NOT NULL, -- 유저 닉네임
    user_email VARCHAR(45) NOT NULL, -- 유저 이메일
    user_image VARCHAR(100) DEFAULT 'user_default.jpg', -- 유저 프로필 이미지
    category1 VARCHAR(20) NOT NULL, -- 관심 카테고리 1
    category2 VARCHAR(20) NOT NULL, -- 관심 카테고리 2
    category3 VARCHAR(20) NOT NULL, -- 관심 카테고리 3
    join_date DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP -- 유저 가입 날짜
);

먼저, DB 설계를 토대로 MySQL문법으로 databasetable을 생성하는 코드를 작성했다. StudyloungeReply table 생성 SQL문은 시하님이, 나머지는 내가 작성했다.

❗ 개발을 진행하다 보니 생각보다 문자열의 길이 제한을 수정하는 일이 많았다. 예를 들면, 유저 닉네임을 넉넉히 받으려고 했는데 디자인적으로 통일감을 주기 위해 6자로 다시 제한하는 수정이 필요했다.


3. Model

/* model/User.js file example */

//foreign key 설정
    User.associate = models => {
        // 1:N 관계, studygroup의 head_id가 user의 user_id를 참조 하고 있다.
        User.hasMany(models.Studygroup, {foreignKey: "head_id", sourceKey: 'user_id', onDelete: 'cascade', onUpdate: 'cascade'});
        // N:M 관계 
        User.belongsToMany(models.Studygroup, { through: 'Studymember' });
        // N:M 관계 
        User.belongsToMany(models.Studygroup, { through: 'Likes' });
        // N:M 관계 
        User.belongsToMany(models.Studygroup, { through: 'Studylounge' });
        // N:M 관계 
        User.belongsToMany(models.Studylounge, { through: 'Reply' });
    };

모델은 sequelize를 이용해서 생성했다. 나는 index, User, Studymember, Studygroup, Studylounge, Reply, Likes 이렇게 총 7개의 파일을 생성했다. (어쩌다 보니 다 만들게 되었다)
migration은 따로 설정해주지 않았다. index.js 파일에 모든 모델의 관계 설정 코드를 작성하려니 너무 길고 보기에 깔끔하지 않아서 foreign key 설정은 index.js 파일이 아닌 각 모델 파일에서 설정해 주었다.

sequelize를 이용하여 DB 관계를 정의해주는 부분이 조금 어려웠다. DB 설계 단계에서는 관계형 데이터베이스에 대한 이해가 필요했다면, sequelize에서는 migration을 사용하지 않고 foregin key를 설정해주는 방법이 필요했다. 1:N 관계 정의는 hasMany()belongsTo()를, N:MbelongsToMany(), belongsToMany()를 사용하면 된다는 것을 알게 되었다.

❗ 또한 처음 N:M table을 생성할 때(ex. Studymember table) primary key를 사용할 일이 없다고 생각해 따로 생성해주지 않았다. 그랬더니 나중에 Controller에서 query 문을 실행하니 해당 테이블에서 PK를 임의로 만들고, 그 키가 없다는 오류가 뜨는 것이다. 찾아보니 기본키가 없는 것이 규칙에 어긋나는 것은 아니지만, 관계형 모델을 따르려면 한 테이블에 있는 특정 로우와 나머지 로우를 구별할 수 있어야 하므로 기본키가 있어야 한다는 것을 알게 되었다. 그래서 모든 N:M tableautoIncrement를 사용해서 기본키 컬럼을 생성해주었다.


4. Controller

처음엔 회원가입 페이지 SignupController를, 그 후에는 게시물 상세 조회 페이지 DetailedPostController와 게시물 관리페이지 ManagementController의 백을 진행했고, sequelizeCRUD를 진행했다.

회원가입 페이지에서는 사용자에게 입력받은 데이터를 User table에 저장하고, 닉네임과 아이디 중복확인 기능을 구현했다.

스터디 그룹 모집 게시물을 보여주는 상세 조회 페이지에서는 유저에 따라 view에서 보여줘야 하는 데이터가 달랐다.
1. 게시물을 작성한 그룹장이 조회한 경우
2. 이미 해당 스터디 그룹에 가입한 멤버인 경우
3. 아직 해당 스터디 그룹에 가입하지 않은 경우

그래서 findOne()을 이용해서 check하고, 해당 페이지로 바로 렌더링하는 형식으로 구현했다.

❗ 이 페이지에서는 스터디 그룹 정보 + 그룹장 정보 + 가입한 멤버의 정보 + 현재 유저의 해당 게시물 좋아요 여부까지 한 번에 보내줘야 했기 때문에 join을 사용하고도 union 까지 사용하게 되었는데, 좋은 코드는 아니지 않았나 생각한다.

sequelize innerjoin(=include)을 사용하고 싶었는데, 위와 같은 이유로 union에 이중join을 사용했기 때문에 너무 복잡해서 raw query를 사용하게 되었다.

그룹장일 경우에만 조회할 수 있는 게시물 관리 페이지에서는 게시물 정보를 수정하거나 삭제할 수 있는 기능을 구현했다.

❗ 게시물을 삭제할 때, 스터디 그룹 정보 studygroup table의 내용을 삭제하는 것뿐만 아니라 가입한 멤버의 정보 studymember table, 스터디 라운지 정보studylounge table, 여기에 댓글 정보 reply table까지 삭제해버려야 했다. 하지만 이 table들은 N:M 관계로 연결되어 있어 ondelete 'cascade' 설정을 해줄 수 없었다. 결국 일일이 먼저 삭제해줘야 비로소 studygroup 정보를 삭제할 수 있었다.


5. View

Front-end에선 크게 메인페이지, 네비바, 회원가입 페이지, 스터디 상세조회 페이지, 스터디 관리 페이지를 맡게 되었다.

메인 페이지front 담당이신 세준님이 구현해 주셨다. 구현된 페이지를 보니 fullpage.js 라이브러리를 사용하면 어울릴 것 같다는 조언을 듣고, 저번 클론코딩 때 사용해 본 적이 있던 내가 라이브러리 적용을 하게 되었다.

네브바에서 로그인 한 상태에선 로그아웃이, 그 외에는 로그인으로 띄워서 로그인 페이지로 넘어갈 수 있게 끔 구현했다. 세션 값을 이용해서 세션 값이 있을 경우 로그아웃으로 보이게 설정하고, 로그아웃 클릭 시 LoginController에서 destroy()로 세션값을 삭제해주었다.

❗ 하지만 이렇게 세션값을 삭제하니, 로그인의 "아이디 기억하기" 기능이 제대로 실행되지 않았다. 그래서 세션값을 아예 삭제하지 않고, 비어있는 문자열로 바꾸어주었다.

회원가입 페이지에서는 css부분을 제외한 axios, form tag처리를 진행했다. 서버로 프로필 사진을 업로드 하는 기능은 multer 미들웨어로 구현했다.

❗ 특히 프로필 사진을 올리면 바로 해당 이미지를 띄우고 싶었다. 그러려면 동적 폼 전송으로는 불가능하고, axios를 사용해서 비동기 처리를 해주어야 했다. formdata 객체를 사용하여 datapost로 보내주고 받은 파일명을 회원가입시 User table에 같이 저장해주었다.

스터디 상세조회 페이지에서는 script와 약간의 css, 데이터를 ejs 문법을 사용해서 불러오는 것, 해시태그 띄우는 부분을 구현했다. 추가로 카카오 공유 API를 사용해서 공유하기 기능을 구현했다.

스터디 관리 페이지에서는 Axios 처리와 같은 sciprt 부분과 데이터 불러오는 것을 구현했다.

❗ 데이터를 불러올 떼 날짜나 카테고리 정보 같은 경우는 DB에 저장된 정보를 그대로 input창에 띄울 수 없었다. jQueryprop("checked", true) 를 통해 각 데이터가 이미 선택되어 있는 것 처럼 보이도록 했다.


6. 계속되는 오류 잡기

❗ 회원가입과 로그인은 물론 스터디 그룹 게시물 등록과 수정, 스터디라운지에서의 게시물 등록 등 입력된 데이터를 insert, update 하는 기능이 많았다. 하지만 이런 기능은 바로 query 조회로 이어지기 때문에 데이터의 유효성 검사가 필요했다. 유효성 검사 프레임 워크를 따로 사용하진 않고, 직접 코드를 구현했다. 그리고 sweetalert 라이브러리를 이용해서 유저에게 알림창을 띄우도록 구현했다.

❗ 회원가입시 유저의 프로필이미지, 스터디 그룹 모집 게시물 작성시 그룹 이미지를 받을 때 모두 이미지를 업로드 하지 않았을 때를 고려해야 했다. 또한 그렇게 저장되었을 때 조회나 수정 페이지에서 어떤 사진을 띄워야 하는지도 고려해야 했다. 그래서 임의로 기본 이미지를 고르고, 이미지를 업로드 하지 않았을 때 해당 사진을 default로 가지도록 설계했다.

❗ 스터디 그룹 모집 게시물을 등록할 때 소개글은 textarea로 입력 받았기 때문에 개행문자 \n가 포함되어 DB에 저장되었는데, 상세 조회 페이지에서 <div>태그로는 data개행문자가 인식되지 않았다. 그래서 \n</br>로 바꿔주는 작업을 통해 해결했다.



😂 아쉬운점

Team이란..
Team convention을 먼저 정하고 진행하지 못했던 점이 아쉽다. Github을 사용하는 것은 commit이나 merge를 할 때 내가 어떤 작업을 했는 지 다른 팀원에게 명확하게 알려줄 수 있다는 장점이 있는데, convention 없이 각자 중구난방으로 진행한 것이 매우매우 아쉽다.

Back-end로써..
1. 데이터베이스에서 테이블과 여러 컬럼이 생성되고 수정되는 상황에 더 안정적인 서비스를 운영하기 위한 트랜잭션 처리를 해주지 못한 점이 아쉽다. 처음에는 막상 migration이 꼭 필요하지 않을 것이라 생각했는데, 개발을 진행하다 보니 롤백 기능이 있는 migration을 그냥 같이 했었으면,,,하는 생각이 들었다.
2. 보안 관련 처리를 하지 못했다. 가장 흔한 password 암호화 마저 구현하지 못했다. 규리쌤이 알려주신다고 할 때 일단 배워두고 볼 걸 그랬다.

전체적으로 갈 길이 멀다..
줄줄이 얘기해보자면 일단 중복된 코드 정리도 안되어 있다. navbarcss, 라이브러리와 같이 중복된 script마저..
그리고 try .. catch문을 적극적으로 활용해서 에러 핸들링 처리를 했다면 손해보는 건 없었을 듯 하다.
또한 테스트 코드를 구현할 생각은 전혀 해보지 못했다. 테스팅 라이브러리 종류가 많던데 나중에 사용해보고 싶다. 일단 시작하기 편하다는 Jest로?
그리고 css 스타일 초기화(Reset.css, Normalize.css)를 할 수 있다는 사실을 프로젝트가 끝나고 나서 알게 되었다. 미리 알았더라면 적용한 후에 진행해보았을 텐데 아쉬웠다.



😎 잘한 점

이번 프로젝트를 진행하면서 API를 사용해보고 싶었는데, 크게 어려운 건 아니였지만 카카오톡 공유 API를 사용해보았다는 점이 뿌듯하다. 흔하게 사용하는 로그인 API도 디벨롭할 때 꼭 적용해보고 싶다.

개인적으로는 코드를 구현하면서 주석을 다 달아주었는데, 나중에 돌아보니 가장 잘한 일이 아닌 가 싶다. 남는 건 기록 뿐.

그래도 훌륭한 팀원들과 으쌰으쌰하면서 짧은 기간동안 이만큼의 기능을 일단 구현했다는 것 자체가 자랑스럽다! 앞으로도 계속해서 디벨롭시켜 더 더 안정적인 서비스로 구축해보고 싶다.



🧐 배운점(느낀점)

확실히 데이터베이스를 좀 더 깊게 배울 수 있었다. 관계형 데이터베이스에 대한 지식도 부족했었고, join이나 서브쿼리 등 쿼리를 다루는 것에 조금은 두려움이 있었는 데 프로젝트를 진행하면서 많이 친근해지고, 만들고 싶은 쿼리문을 작성할 수 있게 되었다.

클라이언트와 서버 사이의 동작과정을 이해하고, 비동기 HTTP요청과 응답을 구현할 수 있게 되었다.

맡은 역할은 백엔드 였지만, 상황에 따라 유동적으로 프론트 처리를 같이 진행했더니 오히려 양측면에서 모두 실력이 오른 것 같아서 역량이 많이 커진 것 같다.

그리고 지금 회고록 작성을 일기처럼 꼼꼼하게 작성하다 보니 굉장히 시간이 많이 소요되고 힘들다... 다음에는 그때그때 작성해두면 더 좋을 듯 싶다!



🔗 프로젝트 관련 링크

Github https://github.com/SeSAC43-Project/StudyWith
Notion https://hushed-hospital-019.notion.site/StudyWith-e93a4cc462c24d248243f0695eeaca56

profile
신입 개발자 쥬비의 기술 블로그 입니다.

0개의 댓글