Sequelize Getter

Sequelize에서 아래와 같이 모델의 컬럼별로 getter를 사용할 수 있습니다.
예를 들어, username을 항상 대문자로 보고싶다면 username컬럼의 getter에서 대문자로 변환할수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
const User = sequelize.define("user", {
// Let's say we wanted to see every username in uppercase, even
// though they are not necessarily uppercase in the database itself
username: {
type: DataTypes.STRING,
get() {
const rawValue = this.getDataValue("username");
return rawValue ? rawValue.toUpperCase() : null;
},
},
});

Sequelize Getter Async 사용 불가

안타깝게도 getterasync로 지정할 수 없고 당연하게도 async 메서드를 호출하여 정상적으로 사용할 수 없습니다.

그래도 getter에서 async메서드를 호출해야 한다면 getter의 기능을 하면서도 async를 사용할 수 있는 아래에서 소개할 hook를 사용하는 걸 고려해 볼 수 있습니다.

Sequelize Hook로 대체

Sequelize에서 제공하는 Hook을 통해 async메서드를 포함하면서도 getter의 기능하는 아주 간단한 예제를 수행해보도록 하겟습니다.

만약에 “판매가는 무조건 공급가에 설정한 마진율을 곱한 가격이다.” 라는 정책이 있는 경우 해당 요구사항을 충족할 수 있도록 getter에서 설정을 가져와 공급가에 마진율을 곱해준 값을 판매가로 설정한다는 getter를 만들수 있습니다. 하지만 아래의 코드처럼 마진율 설정값을 가져오는 메서드가 async라면 getter를 사용할 수 없습니다.

따라서 afterFind라는 hook를 지정하여 findOne, findAll 혹은 findByPk등의 메서드를 호출한 경우 hook를 실행하여 해당 요구사항을 만족시킬 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import Sequelize, { DataTypes } from "sequelize";
import ConfigService from "../services/config.js";

class Product extends Sequelize.Model {
static init(sequelize) {
return super.init(
{
productNo: {
type: DataTypes.INTEGER,
primaryKey: true,
allowNull: false,
autoIncrement: true,
},
supplyPrice: {
type: DataTypes.DECIMAL(17, 2)
}
price: {
type: DataTypes.VIRTUAL
},
...
},
{
sequelize,
charset: "utf8mb4",
collate: "utf8mb4_unicode_ci",
hooks: {
afterFind: async (record, options) => {
if (!record) {
return;
}

const marginRatio = await ConfigService.getConfig("MARGIN_RATIO");

if (Array.isArray(record)) {
record.forEach((each) => {
const supplyPrice = each.dataValues.supplyPrice;
each.dataValues.price = Math.floor(Math.floor(supplyPrice * marginRatio) / 10) * 10;
});
} else {
const supplyPrice = record.dataValues.supplyPrice;
record.dataValues.price = Math.floor(Math.floor(supplyPrice * marginRatio) / 10) * 10;
}
},
},
},
);
}

static associate(db) {
// Association
}
}

export default Product;

Array.isArray(record)

위의 코드 중간에 있는 배열 여부에 관한 분기를 통해 단일 레코드와 배열을 모두 처리할 수 있습니다. 만약 이 조건을 빠뜨린다면 오류가 발생할 수 있으니 주의 바랍니다.

1
2
3
4
5
6
7
if (Array.isArray(record)) {
record.forEach((each) => {
// find의 결과가 배열인 경우 ex) findAll()
});
} else {
// 단일 레코드인 경우 ex) findOne()
}

Sequelize Hook의 종류

afterFind이외에 다양한 Hook가 존재하니 Sequelize 문서를 참고하시면 좋겠습니다.