"어?~" 왜 메서드 호출 안됨?
...
Udemy
에서 node.js
강의를 듣고있는데, 파일시스템이나 데이터베이스에서 저장한 데이터를 불러온 뒤 객체로 파싱하면 메서드 사용이 불가능했다.
이 포스트에서 불가능했던 이유와 해결 방법을 기록했다.
간단한 클래스를 정의하고 파일/DB에서 저장 및 불러오고 메서드를 사용하는 예시를 들어보겠다.
User
클래스 정의User
클래스는 name
과 age
속성, getName()
과 getAge()
메서드를 제공하는 클래스다.
class User {
constructor(name, age) {
this.name = name;
this.age = age;
}
getName() {
return this.name;
}
getAge() {
return this.age;
}
}
fs
모듈을 이용한다.
JSON.stringify()
와 JSON.parse()
로 문자열, 객체로 파싱 후 저장 및 불러온다.
const fs = require('fs');
// 객체 생성 및 파일 저장
const user = new User('John Doe', 25);
fs.writeFileSync('user.json', JSON.stringify(user));
// 파일에서 객체 불러오기
const data = fs.readFileSync('user.json', 'utf8');
const loadedUser = JSON.parse(data);
// 메서드 호출 - 에러발생!
console.log(loadedUser.getName()); // Error: loadedUser.getName is not a function
console.log(loadedUser.getAge()); // Error: loadedUser.getAge is not a function
mongodb
사용 예시로 DB 연결 관련 코드는 축약하고 논리 위주로 작성하였다.
const db = getDb(); // 데이터베이스 connection 얻기
// 객체 생성 및 데이터베이스에 저장
async function insertUser(name, age) {
const user = new User(name, age);
await db.collection('users').insertOne(user);
}
// 데이터베이스에서 객체 불러오기
async function findUser(userId) {
const user = await db
.collection('users')
.findOne({ _id: userId });
return user;
}
async function main() {
insertUser('John', 25);
const userId = '임의의 유저 아이디';
const loadedUser = await findUser(userId);
// 에러 발생!
console.log(loadedUser.getName()); // Error: loadedUser.getName is not a function
console.log(loadedUser.getAge()); // Error: loadedUser.getAge is not a function
}
main();
이와 같은 에러는 직렬화와 역직렬화에 대한 이해가 필요하다.
아래 코틀린 공식문서에 직렬화에 및 역직렬화에 대한 설명이 있다.
Serialization | Kotlin Documents
(Serialization)
응용 프로그램에서 쓰는 데이터를 네트워크를 통해 전송하거나 DB 또는 파일에 저장 가능한 형식으로 바꾸는 프로세스
(Deserialization)
외부 소스에서 데이터를 읽고 이를 런타임 객체로 바꾸는 반대 프로세스
즉 파일시스템이나 데이터베이스에 데이터를 전송 및 불러오는 행위를 각각 직렬화와 역직렬화로 생각하면 된다.
이번 문제를 해결하려면 JavaScript
에서의 직렬화 / 역직렬화가 어떻게 이루어지는지 알아야한다.
JavaScript
객체의 직렬화 & 역직렬화우선 파일 및 데이터베이스에 저장하는 경우 객체의 속성만 저장된다.
{"name":"John Doe","age":25}
{"_id":{"$oid":"64a132951d6cb2e438455023"},"name":"John","age":{"$numberInt":"25"}}
instanceof don't work for JSON
파일이나 데이터베이스에서 데이터를 불러와 객체로 파싱(역직렬화)하면 해당 객체는 no prototype
객체가 된다. 즉, 순수하게 객체의 속성만 담고있는 객체가 되어버린다.
no prototype
객체spread(...)
문법 사용 불가능 - no iterator
에러객체 instanceof 클래스 === false
- 해당 클래스의 인스턴스가 아닌 것으로 간주객체의 상태만을 포함한 JSON 형태의 데이터나 데이터베이스 레코드는 해당 클래스의 메서드 코드를 포함하고 있지 않기 때문에 메서드 호출이 불가능한 것이다
또한, no prototype
객체라서 spread
문법이나 instanceof
도 불가능하다.
객체의 속성을 추출해 new
연산자로 클래스의 인스턴스를 생성해서 이용하면 된다.
객체 구조분해할당을 통해 간결하고 이해하기 쉬운 코드를 작성할 수 있다.
const { name, age } = loadedUser; // 구조분해할당
const newUser = new User(name, age);
console.log(newUser.getName()); // "John"
console.log(newUser.getAge()); // 25
내가 듣는 강의에서는 프로젝트를 mvc
패턴으로 구성했다.
미들웨어를 등록해 사용자를 얻어 req.user
에 저장
이후 controller
에서 req.user
에 접근해 user
를 사용할 수 있도록 하는 코드이다.
model
에서 클래스를 정의하는데, 주어진 id
로 user
를 반환하는 함수 findById
메서드에서 이 해결방안을 적용할 수 있었다.
findById
에서 User
클래스의 인스턴스를 넘겨주므로 req.user
로 얻은 사용자 객체는 메서드를 사용할 수 있다.
models/user.js
: findById
메서드에 해결 방안 적용class User {
constructor(name, email, cart, id) {
this.name = name;
this.email = email;
this.cart = cart; // {items: []}
this._id = id ? new ObjectId(id) : null;
}
... (다른 메서드들)
getCart() { ... } // 사용자의 장바구니를 얻는 메서드
static async findById(userId) {
try {
const db = getDb();
const user = await db
.collection('users')
.findOne({ _id: new ObjectId(userId) });
// 데이터베이스나 파일에서 불러온 객체는 단순히 데이터의 상태만을 가지고 있음
// 따라서 User의 메서드를 사용할 수 있도록
// 객체 생성에 필요한 속성들을 가져온 후 new User로 인스턴스 반환
const { name, email, cart, _id } = user;
return new User(name, email, cart, _id);
} catch (err) {
console.log(err);
}
}
}
app.js
: req.user
에 저장해 controller
에서 접근할 수 있도록 하는 미들웨어 등록app.use((req, res, next) => {
User.findById('사용자 아이디')
.then((user) => {
req.user = user; // req 객체에 담으면 다음 미들웨어에서 접근 가능
next(); // next()를 호출해 다음 미들웨어로 요청이 이동하도록함
})
.catch((err) => console.log(err));
});
controllers/shop.js
: app.js
에서 등록한 user
를 요청 객체(req
)에서 불러와 메서드 사용 가능module.exports = {
... (다른 컨트롤러들)
postCartDeleteProduct: async (req, res, next) => {
const prodId = req.body.productId;
try {
const cart = await req.user.getCart(); // User 클래스의 인스턴스라 메서드 사용 가능
... (나머지 로직)
res.redirect('/cart');
} catch (err) {
console.log(err);
}
},
}
JavaScript
에서 파일 및 데이터베이스에 데이터를 저장(직렬화) / 불러오기(역직렬화)를 하면 객체의 속성만 저장되고 불러옴
불러온 객체는 메서드를 지니지 않으며 no prototype
- spread
문법과 instanceof
불가
객체의 속성을 받아 new
연산자로 새로운 인스턴스를 생성해야함