1차 팀프로젝트 ] LMS(학습관리시스템)
LMS(학습 관리 시스템) 팀 프로젝트 목표 및 구현 요청사항
- 사용자는 모든 수업을 조회할 수 있다(GET)
- 사용자는 특정 분류의 수업을 조회할 수 있다(예: 강의자/ 수업명 / 수업코드 등)(GET)
- 사용자는 수업을 수강신청 할 수 있다(POST)
- 사용자는 모든 수강중인 수업을 조회할 수 있다(GET)
- 사용자는 이메일 정보와 같은 개인정보를 변경할 수 있다(PUT)->(PATCH)
- 사용자의 타입이 강의자일 경우 새로운 수업을 생성할 수 있다(POST)
- 사용자는 수업에 대한 수강신청을 취소 할 수 있다(DELETE)
- 7개의 구현 사항중 나는 3,7번을 구현하기로 하였다.
DAY 1 ( 데이터베이스 ERD 작성 및 API 문서 작성)
최초 ERD 작성
이슈 1 ) 위 ERD는 처음 작성된 ERD이다, USERS 테이블에서 역할을 구분하지않고, 역할(roles) 테이블을 따로 만들어 관리 하여고 하였으나, 팀 프로젝트의 목표인 LMS는 역할이 학생, 교수로 구분하는 것으로 가정하고 다시 아래와 같은 ERD를 작성 하였다.
API 문서 작성
기능별 API 문서를 구성하였다.
그 중 내가 맡은 수강 신청, 취소에 관련된 API 명세는 아래와 같다.
결과 : API 문서를 만들면서 데이터베이스 관계가 명확할수록 작성이 편하다는 것을 느꼇고, 우리는 잘 만들었다고 생각했으나, 구현 단계에서 user_id 같은 고유의 값을 토큰으로 넘겨받으면서 Request parameter에 있던 path로 받을 예정이였던 user_id를 삭제하였다.
위 구현사항은 수강신청은 Request.body로 class_id를 받아왔고, 수강취소는 path로 class_id를 받아왔으나, 특별한 이유가 있던 것은 아니고, 여러가지 방법을 코딩해보고 싶어서 작성하였다.
이슈2) class 테이블 내에 professer_name 컬럼 생성에 대한 찬/반
찬성 : class 테이블 내에 professer_name 컬럼을 생성하면, class의 조건검색(강사명 검색)시 한번에 검색이가능하다
반대 : professer_name컬럼이 없더라도, user테이블과의 join을 통한 데이터 검색이 가능하다.
결과 : professer_name컬럼을 테이블에 생성
이슈2_1) 실제 구현도중 문제가 될것이라 예상되었던 강의조회 부분에서 SQL의 join문을 사용해 하나의 쿼리로 기능 작성을 완료하여 professer_name의 사용 이유가 없어졌고, 더불러 professer_name이외의 user정보 자체를 다 가져오는것이 유연하게 정보검색이 가능할것으로 보여 최종적으로 professer_name컬럼 삭제
이슈3) 회원정보 수정 구현시 email값만 변경될것으로 가정, 이후 PUT,PATCH중 어느 메서드를 사용할것 인지에 대해 논의
결론 : 회원 정보에서 email만 수정하므로 PATCH가 적절하다고 판단
추가(내가몰라서 하는 정리) : PUT은 리소스를 완전히 대체 하거나 생성하는데 사용되는 반면, PATCH는 기존 리소스를 부분 적으로 업데이트하거나 수정하는데에 사용
- ERD 작성 중 increment라는 속성을 알게되었다. [자동으로 1씩 증가]
- 리눅스에서 fastify를 설치하고 사용하는 방법을 알게되었다 [npm install fastify-cli --global]
DAY2(기능 구현)
기능 구현은 각자 맡은 부분을 구현하였다. 나는 요구 과제중 수강 신청과 수강 취소 부분을 맡았고, 아래와 같은 결과물을 완성은 하였지만, 팀원 분들이 많이 도와주셔서 전체를 이해하면서 작성한 코드는 아니였고, 완성 후에 코드 이해를 하였다.
module.exports = async function (fastify, opts) {
fastify.post("/", async function (request, reply) {
let client = undefined;
//POST 수강신청 본문
try {
// 클래스ID를 body에서 얻어옴
const { class_id } = request.body;
// 유저ID를 얻기위한 토큰값
const { authorization } = request.headers;
// 토큰 해석 리스트(key: 토큰, value 'users tale'의 id
const TokenKeyValue = {
AAA: 1,
BBB: 3,
};
//토큰으로부터 user_id를 얻어옴
const user_id = TokenKeyValue[authorization];
//수강신청 쿼리
const insertQuery = ` INSERT INTO public.users_class(user_id, class_id) VALUES (${user_id},${class_id});`;
client = await fastify.pg.connect();
await client.query(insertQuery);
reply.code(200).send({ class_id });
} finally {
if (client)
client.release();
else
reply.code(400).send({});
}
});
}
module.exports = async function (fastify, opts) {
fastify.delete("/:id", async function (request, reply) {
let client = undefined;
//수강취소 본문
try {
//수강취소 하기 위한 class_id를 path에서 얻어옴
const class_id = request.params.id;
//인증을 위한 토큰 값
const { authorization } = request.headers;
// 토큰 해석 리스트(key: 토큰, value 'users tale'의 id
const TokenKeyValue = {
AAA: 1,
BBB: 3,
};
//토큰으로부터 user_id를 얻어옴
const user_id = TokenKeyValue[authorization];
//수강취소 쿼리
const insertQuery = `DELETE FROM users_class WHERE user_id = ${user_id} AND class_id = ${class_id}`;
client = await fastify.pg.connect();
await client.query(insertQuery);
reply.code(204).send();
} finally {
client.release();
}
});
}
- 코딩을 해본지가 너무 오래되어서 '시작'하는데에 문제가 있었다. 다행히도 팀원 분께서 시작하는 코드를 보여주셔서 일부 참고하여, 작성하였다.
- 토큰으로 특정 값을 넘겨 받는법을 알게 되었다.
- path로 특정 값을 얻어와 그것으로 처리하는 것을 알게 되었다.
- 최초 코드 작성 중 client를 try블록안에서 선언하여, finally에서 종료가 불가능하여, client를 먼저 선언하여 오류를 해결하였다.
Troubleshooting
문제 : 데이터는 정상적으로 데이터베이스에 들어가나 에러 출력(Promise errored, but reply.sent = true was set)
원인 : 작성 된 코드 중 client 선언이 try 안에서 되어 finally에서 커넥트된 client를 찾을 수 없어서 발생한 오류
결과 : client 선언을 try밖에서 하여, try와, finally에서 사용가능하게 변경함 에러없는 것 확인
프로젝트를 마치며,
처음 시작할 때에는 어디서부터 시작을 해야할지 모르는 상태로 시작을 했기 때문에 무언가를 만든다라기보다 부담이 먼저왔던 것 같다.
팀프로젝트가 완성이되고, 다른분들에 비해 오류처리도 미흡하고, 코드 이해하는데에도 시간이 많이 소요되었던 것 같다.
하지만, 팀프로젝트가 완성이 되고, 원하는 방식으로 흘러가는 것을 확인 했을때 작성한 코드를 이해했고, 다만 DB설계부터 중복방지를 하여 수강신청에 중복으로 강의가 들어가는 상황을 막았으면 더욱 좋았을 것 같다는 아쉬움이 남는다.