시작하기에 앞서,
"어느 기업에 갔는지"와 "어떤 사이트를 프로젝트로 진행했는지"는 밝히지 않겠습니다.
코드 또한 일부분만 공개가 가능합니다.
NFT 거래 사이트입니다.
저희가 파견되었을 때 배포가 진행중이었고 서비스 출시는 하지 않은 상태였습니다.
웹 사이트 기능 작동을 위해 빠르게 구현된 상태였고 Testing, Error Handling 모두 구현되어 있지 않은 상태였습니다.
BE의 목표는 구현된 API를 이해하고 Layered Pattern으로 분리 및 모듈화였습니다.
BE팀은 총 6개의 API를 리팩토링하였고 저는 3개의 API를 리팩토링하였습니다.
News - 최신소식, 뉴스를 보여주는 API
Search - NFT 및 제작자를 검색하는 API
Wishlist - NFT의 장바구니 API
Controller에 모두 구현된 API를
DB에 접근하는 부분인 Action,
Error Handling 부분인 Exception,
response만 보내는 부분인 Controller
그리고 유효성 검사를 위한 Validator
이렇게 4부분으로 분리하였습니다.
시간이 부족하여 JUnit을 활용한 Testing은 실시하지 못하였고 Postman
을 활용하여 Integration Testing을 실시하였습니다.
MongoDB에 연결할 때 localhost DB의 경로를 추가하려면 authSource=admin
을 추가해야 한다.
MONGODB=mongodb://(ID):(PASSWORD)@localhost:27017/(DB명)?authSource=admin&authMechanism=DEFAULT
mongoose
의 find()
메소드로 MongoDB에서 해당 id의 객체를 검색할 때 objectId castError
가 발생한 경우가 있었다.
Document의 _id
는 ObejctId 자료형인데 req.params.id
는 string 자료형으로 받아와서 비교하다보니 에러가 발생한 것이었다.
const mongoose = require('mongoose');
const ObjectId = mongoose.Types.ObjectId;
const id_obj = ObjectId(req.params.id);
이렇게 간단히 해결!
MongoDB의 ObjectId는 BSON 자료형으로 binary serialization format이며 12bytes 혹은 24hex의 character이다.
path parameter로 해당 nft를 검색하는 api에서 잘못된 id값을 넘길시 발생했는데, objectIdLength라는 Validator를 만들어 해당 Error를 Handling 하였다.
module.exports = {
assertion : (string) => {
if (string.length !== 24){
throw new ObjectIdLengthError(
'NEWS_ID_VALUE_ERROR'
);
}
}
}
한 개의 response만 보낼 수 있는데 두 개 이상의 response를 보내게 되면 HTTP Error
가 발생한다.
Scope 문제인 경우가 많은데 나도 그 경우에 해당되었다.
return res.json으로 해결
promise 객체의 .catch()
메소드의 Scope는 해당 객체이다.
따라서 .catch()
메소드로 잡은 error를 throw해도 해당 function 전체가 exit되지 않는다.(이렇게 되면 res.json을 두 번 보내는 경우도 있기 때문에 또 HTTP error
가 발생한다.)
error를 잡았을 때 해당 function을 탈출하는 것이 목표였기 때문에 async/await 방식으로 구현한 뒤, try-catch
문을 사용하였다.
wishlist API에서 update
함수를 구현할 때 발생한 에러이다.
condition이라는 인자를 통해 해당 Document를 update하는 부분인데 이미 존재하는 ObjectId를 계속 생성하려고 해서 발생한 error이다.
초반에 구현할 때에는 "...(spread syntax)를 활용하여 condition을 합치면 되지 않을까?"라고 생각해서 그렇게 구현했는데 두 개로 나뉘어야 할 condition의 객체들이 합쳐져서 정상적으로 작동하지 않았다.
//wishlist action
const updateWishlist = async(wallet, cond) => {
try{
const updatedWishlist = await db.wishlist.updateOne({wishlist_wallet : wallet}, cond);
return updatedWishlist;
}catch(error){
throw new DatabaseError('DB_ACCESS_ERROR');
}
}
module.exports = {
Update : async(wallet, art_id) => {
return updateWishlist(wallet, {wishlist_updated: Date.now(), $push:{wishlist_arts:{ $each: [art_id], $position : 0} }});
}
구현 로직을 변경해서 위의 코드와 같이 변경하니 정상작동하였다.
findOne()
- if query matches, first document is returned, otherwise null.find()
- no matter the number of documents matched, a cursor is returned, never null.