MongoDB - Document를 JavaScript Object로 변환하기

modolee·2020년 9월 11일
12
post-thumbnail

빠르고 간단하게 API를 개발 할 때 Node.js + Express.js + mongoDB 조합을 많이 사용하는 편입니다. 추가로 mongoDB ODM(Object Document Mapping)인 mongoose를 활용하여, DB를 관리합니다.

mongoose를 이용해서 데이터를 조회한 후 해당 데이터를 json 형태로 변환해서 바로 response를 보낼 때에는 별 문제가 없지만, 해당 데이터를 spread opreator로 분해를 하거나 unit 테스트 할 때 종종 원치 않는 데이터가 포함 된 것을 확인할 수 있습니다.

이게 다 뭐지?

예상 했던 데이터와 실제 데이터에는 차이가 있습니다.

예상 했던 것

{
    "name": "모도리",
    "position": "개발자",
    "roles": [
      "ADMIN"
    ]
}

실제로 담겨 있는 것

{
  "$__": {
    "$options": {
      "defaults": true
    },
    "$setCalled": {},

    // ... (생략)

    "backup": {
      "activePaths": {
        "default": {
          "_id": true
        },
        "modify": {
          "name": true,
          "position": true,
          "roles": true
        }
      }
    },
    "cachedRequired": {},

    // ... (생략)

    "getters": {},
    "inserting": true,
    "pathsToScopes": {
      "roles": "[Circular reference found] Truncated by IDE"
    },
    "session": null,
    "strictMode": true,
    "validating": null,
    "wasPopulated": false
  },
  "$locals": {},
  "$op": null,
  "_doc": {
    "__v": 0,
    "_id": "[Circular reference found] Truncated by IDE",
    "name": "모도리",
    "position": "개발자",
    "roles": [
      "ADMIN"
    ]
  },
  "isNew": false
}

Mongoose Document

그 이유는 mongoose Query를 통해서 조회 된 데이터는 단순한 Plain Old JavaScript Objects(POJO)가 아닌 Moogoose Document 형태이기 때문입니다.

Mongoose Document는 아래와 같은 것들을 포함하고 있습니다.

  • Change tracking
  • Casting and validation
  • Getters and setters
  • Virtuals
  • save()

이런 식의 추가 정보와 메서드들을 포함하고 있기 때문에 Query 결과 값을 가지고 수정한 후 save() 메서드를 통해서 수정 된 내용을 DB에 반영할 수 있는 것입니다.

const findAndUpdateName = async (oldName, newName) => {
  const user = await User.findOne({ name: oldName });
	user.name = newName;
	const savedUser = await user.save();

  return savedUser;
};

Plain Old Javascript Object (POJO)

그런데 만약 다른 정보나 메서드를 사용하지 않는다면 굳이 Mongoose Document를 사용 할 필요가 없습니다. 그래서 저는 보통 toObject()메서드를 사용하여 POJO로 변환하여 사용하곤 했습니다. 그런데 이번에 lean() 메서드를 알게 되었고 훨씬 더 효율적이라는 것도 알게 되었습니다.

toObejct() 메서드를 이용하는 경우 Query 결과를 Mongoose Document로 만들기 위한 과정을 거쳤다가 다시 POJO로 변환되는 반면, lean() 메서드를 이용할 경우 중간 과정 없이 바로 POJO를 반환합니다.

toObject() 메서드 이용

const findByName = async (name) => {
  const user = await User.findOne({ name });
	return user.toObejct();
};

lean() 메서드 이용

const findByName = async (name) => {
  const user = await User.findOne({ name }).lean();
	return user;
};

Mongoose Document와 POJO의 용량 비교

  • 10배 이상의 차이를 보입니다.
const schema = new mongoose.Schema({ name: String });
const MyModel = mongoose.model('Test', schema);

await MyModel.create({ name: 'test' });

// Module that estimates the size of an object in memory
const sizeof = require('object-sizeof');

const normalDoc = await MyModel.findOne();
// To enable the `lean` option for a query, use the `lean()` function.
const leanDoc = await MyModel.findOne().lean();

sizeof(normalDoc); // >= 1000
sizeof(leanDoc); // 86, 10x smaller!

// In case you were wondering, the JSON form of a Mongoose doc is the same
// as the POJO. This additional memory only affects how much memory your
// Node.js process uses, not how much data is sent over the network.
JSON.stringify(normalDoc).length === JSON.stringify(leanDoc.length); // true

lean() 메서드의 사용

물론 lean() 메서드를 이용해서 얻은 데이터는 Mongoose Document가 아니기 때문에 추가 정보나 save()와 같은 메서드들은 사용이 불가능합니다. 그래서 아래와 같은 경우 사용을 권장합니다.

  • Express response와 같이 어떠한 수정 없이 Query 결과를 바로 전송하는 경우.
  • Query 결과를 수정하지 않고 custom getter를 사용하지 않는 경우

기존에 toObject() 메서드를 사용했던 코드들을 lean() 메서드로 수정한다면 프로그램 효율 개선에 도움이 될 것 같습니다.

참고

profile
기초가 탄탄한 백엔드 개발자를 꿈꿉니다.

2개의 댓글

comment-user-thumbnail
2022년 7월 25일

그동안 이게 뭔지도 모르고 찾은 인스턴스 spread 뒤에 ._doc으로 값을 받아왔다가 이제서야 찾아봅니다! 궁금한 점이 시원하게 해결됐어요 좋은 정리 감사합니다^^

1개의 답글