Subset์ Model์ ๋ฉ์๋๋ฅผ ํตํด ์ฌ์ฉํฉ๋๋ค.
Model ๋ฉ์๋์ Subset
findById - ๋จ์ผ ๋ ์ฝ๋ ์กฐํ
// Subset 1๊ฐ
const user = await UserModel.findById(1, ["P"]);
// ํ์
: UserSubsetMapping["P"]
// Subset ์ฌ๋ฌ ๊ฐ (Union)
const user = await UserModel.findById(1, ["A", "P"]);
// ํ์
: UserSubsetMapping["A"] | UserSubsetMapping["P"]
findOne - ์กฐ๊ฑด์ผ๋ก ๋จ์ผ ๋ ์ฝ๋
const user = await UserModel.findOne({
where: [["email", "john@test.com"]],
subsetKey: "P",
});
// ํ์
: UserSubsetMapping["P"] | undefined
findMany - ์ฌ๋ฌ ๋ ์ฝ๋ ์กฐํ
const users = await UserModel.findMany({
listParams: {
page: 1,
pageSize: 20,
},
subsetKey: "L",
});
// ํ์
: UserSubsetMapping["L"][]
getSubsetQueries๋ก Subset ์ฟผ๋ฆฌ ์์
getSubsetQueries๋ฅผ ์ฌ์ฉํ์ฌ Subset ๊ธฐ๋ฐ ์ฟผ๋ฆฌ๋ฅผ ์์ํฉ๋๋ค:
// Subset P์ ์ฟผ๋ฆฌ ๋น๋ ํ๋
const { qb, onSubset } = UserModel.getSubsetQueries("P");
// ์ถ๊ฐ ์กฐ๊ฑด ์ ์ฉ
qb.where("users.role", "admin")
.where("users.is_verified", true)
.orderBy("users.created_at", "desc");
// executeSubsetQuery๋ก ์คํ
const result = await UserModel.executeSubsetQuery({
subset: "P",
qb,
params: { num: 20, page: 1 },
});
// ํ์
: ListResult<{ num: number; page: number }, UserSubsetMapping["P"]>
getSubsetQueries("P")๋ Subset P์ ์ ์๋ JOIN๊ณผ SELECT๋ฅผ ์๋์ผ๋ก ์ ์ฉํ ์ฟผ๋ฆฌ ๋น๋(qb)๋ฅผ ๋ฐํํฉ๋๋ค.
Subset ์ฟผ๋ฆฌ ํ์ฅ
WHERE ์กฐ๊ฑด ์ถ๊ฐ
const { qb } = UserModel.getSubsetQueries("L");
qb.where("users.role", "admin")
.where("users.is_verified", true);
const result = await UserModel.executeSubsetQuery({
subset: "L",
qb,
params: { num: 20, page: 1 },
});
// Subset L์ ๊ธฐ๋ณธ ํ๋ + WHERE ์กฐ๊ฑด
ORDER BY ์ ๋ ฌ
const { qb } = UserModel.getSubsetQueries("L");
qb.orderBy("users.created_at", "desc");
const result = await UserModel.executeSubsetQuery({
subset: "L",
qb,
params: { num: 10, page: 1 },
});
๋ณตํฉ ์กฐ๊ฑด
const { qb, onSubset } = UserModel.getSubsetQueries("P");
qb.where("users.role", "normal")
.orderBy("employee.salary", "desc");
// onSubset์ผ๋ก ํน์ Subset์ ํ
์ด๋ธ์ ์ ๊ทผ
onSubset("P").where("employee__department.name", "Engineering");
onSubset("P").where("employee.salary", ">", "60000");
// Subset P๋ employee.department.name์ ํฌํจํ๋ฏ๋ก
// JOIN์ด ์๋์ผ๋ก ์ค์ ๋จ
Subset ํ์
์์ ์ฑ
์ปดํ์ผ ํ์ ๊ฒ์ฆ
// โ
OK: Subset P๋ employee__department ํ
์ด๋ธ ํฌํจ
const { qb, onSubset } = UserModel.getSubsetQueries("P");
onSubset("P").where("employee__department.name", "Engineering");
// โ Type Error: Subset L์ employee ํ
์ด๋ธ ๋ฏธํฌํจ
const { qb: qbL, onSubset: onSubsetL } = UserModel.getSubsetQueries("L");
onSubsetL("L").where("employee__department.name", "Engineering");
// ^^^^^^^^^^^^^^^^^^^^^^^^^ Property does not exist
์๋ ํ์
์ถ๋ก
const user = await UserModel.findById(1, ["P"]);
// TypeScript๊ฐ ์๋์ผ๋ก ํ์
์ถ๋ก
user.id; // number
user.username; // string
user.employee; // { salary: string; department: { name: string } } | null
// โ Compile Error
user.password; // Property 'password' does not exist
Subset ๊ต์งํฉ (Multiple Subsets)
์ฌ๋ฌ Subset์ ๋์์ ์ง์ ํ๋ฉด, TypeScript๋ ๊ต์งํฉ ํ์
์ ์๋์ผ๋ก ๊ณ์ฐํฉ๋๋ค.
๊ธฐ๋ณธ ๊ต์งํฉ
// Subset A์ P์ ๊ต์งํฉ
const user = await UserModel.findById(1, ["A", "P"]);
// ํ์
: ๋ Subset์ ๊ณตํต์ผ๋ก ์๋ ํ๋๋ง ์ฌ์ฉ ๊ฐ๋ฅ
user.id; // โ
OK (A, P ๋ชจ๋ ํฌํจ)
user.username; // โ
OK (A, P ๋ชจ๋ ํฌํจ)
user.email; // โ
OK (A, P ๋ชจ๋ ํฌํจ)
user.employee; // โ Type Error (A์๋ ์์, P์๋ง ์์)
user.bio; // โ Type Error (P์๋ ์์, A์๋ง ์์)
๊ต์งํฉ์ด ๋์ํ๋ ์ด์ :
["A", "P"]๋ Union์ด ์๋ Intersection ํ์
์ ์์ฑ
- ๋ฐํ์์๋ ๋ Subset ์ค ๋ ํฐ Subset์ ๋ฐ์ดํฐ๊ฐ ๋ฐํ๋จ
- ํ์ง๋ง ํ์
์์คํ
์ ๊ณตํต ํ๋๋ง ๋ณด์ฅ
์ฟผ๋ฆฌ์์์ ๊ต์งํฉ
// ์ฟผ๋ฆฌ ์์: A Subset ๊ธฐ๋ฐ
const { qb, onSubset } = UserModel.getSubsetQueries("A");
// โ
OK: ๊ณตํต ํ๋๋ง WHERE ์กฐ๊ฑด ๊ฐ๋ฅ
qb.where("users.username", "like", "%john%");
qb.where("users.email", "john@example.com");
// onSubset์ผ๋ก ์ฌ๋ฌ Subset์ ๊ต์งํฉ ํ์
์ฌ์ฉ
onSubset(["A", "P"]).where("users.id", 1);
// โ Type Error: employee๋ A์ P ๊ต์งํฉ์ ์์ (A์ ์์)
// onSubset(["A", "P"]).where("employee__department.name", "Engineering");
// ^^^^^^^^^^^^^^^^^^^^^^^^^ Property does not exist
์ค์ ์์ : ์กฐ๊ฑด๋ถ Subset ์ ํ
async getUserData(userId: number, includeEmployeeInfo: boolean) {
// ์กฐ๊ฑด์ ๋ฐ๋ผ Subset ์ ํ
const subsets = includeEmployeeInfo ? ["P"] : ["A"];
const user = await UserModel.findById(userId, subsets);
// TypeScript ํ์
:
// - includeEmployeeInfo๊ฐ true๋ฉด: UserSubsetMapping["P"]
// - includeEmployeeInfo๊ฐ false๋ฉด: UserSubsetMapping["A"]
// - ํ์ง๋ง ๋ณ์ subsets์ ํ์
์ด ["P"] | ["A"]์ด๋ฏ๋ก
// ์ค์ ๋ก๋ ๊ต์งํฉ ํ์
์ด ์ถ๋ก ๋จ
return {
id: user.id,
username: user.username,
email: user.email,
// employee ํ๋๋ ํ์
์ฒดํฌ ํต๊ณผ ์๋จ (๊ต์งํฉ์ ์์)
};
}
๊ต์งํฉ ์ฐํ ๋ฐฉ๋ฒ:
// ๋ฐฉ๋ฒ 1: ํ์
๊ฐ๋ ์ฌ์ฉ
async getUserDataWithGuard(userId: number, includeEmployeeInfo: boolean) {
if (includeEmployeeInfo) {
const user = await UserModel.findById(userId, ["P"]);
// ์ฌ๊ธฐ์ user๋ UserSubsetMapping["P"] ํ์
return {
...user,
department: user.employee?.department?.name,
};
} else {
const user = await UserModel.findById(userId, ["A"]);
// ์ฌ๊ธฐ์ user๋ UserSubsetMapping["A"] ํ์
return user;
}
}
// ๋ฐฉ๋ฒ 2: ์ ๋ค๋ฆญ ์ฌ์ฉ
async getUserDataGeneric<T extends UserSubsetKey>(
userId: number,
subset: T
): Promise<UserSubsetMapping[T]> {
const user = await UserModel.findById(userId, [subset]);
return user;
}
// ์ฌ์ฉ
const userA = await getUserDataGeneric(1, "A"); // ํ์
: UserSubsetMapping["A"]
const userP = await getUserDataGeneric(1, "P"); // ํ์
: UserSubsetMapping["P"]
onSubset ๋ฉ์๋์ ๊ต์งํฉ
onSubset ๋ฉ์๋๋ก Subset ์กฐ๊ฑด์ ๋์ ์ผ๋ก ์ ์ฉํ ๋๋ ๊ต์งํฉ์ด ๊ณ์ฐ๋ฉ๋๋ค:
const { qb, onSubset } = UserModel.getSubsetQueries("A");
// Subset A์ P ์กฐ๊ฑด ๋ชจ๋ ์ ์ฉ (๊ต์งํฉ ํ์
)
onSubset(["A", "P"]).where("users.username", "like", "%john%"); // โ
OK
// โ Type Error: employee๋ ๊ต์งํฉ์ ์์
// onSubset(["A", "P"]).where("employee__department.name", "Engineering");
onSubset์ ํ์
์ถ๋ก :
const { qb, onSubset } = UserModel.getSubsetQueries("A");
// ๋จ์ผ Subset
onSubset("A").where("users.id", 1);
// qb๋ Subset A์ ๋ชจ๋ ํ๋ ์ฌ์ฉ ๊ฐ๋ฅ
// ๋ณต์ Subset (๊ต์งํฉ)
onSubset(["A", "P"]).where("users.id", 1);
// qb๋ A์ P์ ๊ต์งํฉ ํ๋๋ง ์ฌ์ฉ ๊ฐ๋ฅ
// ๊ต์งํฉ์ด ๋น์ด์์ผ๋ฉด?
onSubset(["L", "P"]).where("users.id", 1);
// L๊ณผ P์ ๊ณตํต ํ๋๋ง ์ฌ์ฉ ๊ฐ๋ฅ
// ๋ง์ฝ ๊ณตํต ํ๋๊ฐ ๊ฑฐ์ ์๋ค๋ฉด, ์ฌ์ฉ ๊ฐ๋ฅํ ํ๋๊ฐ ๋งค์ฐ ์ ํ์
๊ต์งํฉ ์ฌ์ฉ ์ ์ฃผ์์ฌํญ:
- ๋ฐํ์ ๋ฐ์ดํฐ โ ํ์
: ๋ฐํ์์๋ ๋ ํฐ Subset์ ๋ชจ๋ ํ๋๊ฐ ๋ฐํ๋์ง๋ง, ํ์
์์คํ
์ ๊ต์งํฉ๋ง ๋ณด์ฅ
- ์๋ํ์ง ์์ ๊ต์งํฉ: ์ฌ๋ฌ Subset์ ๋ฐฐ์ด๋ก ์ ๋ฌํ ๋ ์ค์๋ก ๊ต์งํฉ์ด ๋ฐ์ํ ์ ์์
- ํ์
๊ฐ๋ ๊ถ์ฅ: ์กฐ๊ฑด๋ถ๋ก Subset์ ์ ํํ ๋๋ ํ์
๊ฐ๋๋ฅผ ์ฌ์ฉํ์ฌ ์ ํํ ํ์
์ถ๋ก
์ธ์ ๊ต์งํฉ์ ์ฌ์ฉํ๋์?
- ์ฌ๋ฌ API์์ ๊ณตํต์ผ๋ก ํ์ํ ํ๋๋ง ์กฐํํ ๋
- ํ์
์์ ์ฑ์ ๊ทน๋ํํ๊ณ ์ถ์ ๋
- ๋ถํ์ํ ํ๋ ๋
ธ์ถ์ ๋ฐฉ์งํ๊ณ ์ถ์ ๋
๋๋ถ๋ถ์ ๊ฒฝ์ฐ ๋จ์ผ Subset ์ฌ์ฉ์ ๊ถ์ฅํฉ๋๋ค.
์ค์ ์์
์ฌ์ฉ์ ๋ชฉ๋ก API
// Model ๋ด๋ถ ๋ฉ์๋ (๊ถ์ฅ ํจํด)
class UserModelClass extends BaseModelClass<...> {
@api({ httpMethod: "GET", clients: ["axios", "tanstack-query"] })
async findMany<T extends UserSubsetKey>(
subset: T,
rawParams?: UserListParams
): Promise<ListResult<UserListParams, UserSubsetMapping[T]>> {
const params = { num: 24, page: 1, ...rawParams };
const { qb, onSubset } = this.getSubsetQueries(subset);
// ์ญํ ํํฐ
if (params.role) {
qb.where("users.role", params.role);
}
// ๊ฒ์
if (params.keyword) {
qb.where("users.username", "like", `%${params.keyword}%`);
}
// ์ ๋ ฌ
qb.orderBy("users.created_at", "desc");
return this.executeSubsetQuery({ subset, qb, params });
}
}
ํ๋กํ ์กฐํ API
@api({ httpMethod: "GET", clients: ["axios", "tanstack-query"] })
async findById<T extends UserSubsetKey>(
subset: T,
id: string
): Promise<UserSubsetMapping[T]> {
const { rows } = await this.findMany(subset, { id, num: 1, page: 1 });
if (!rows[0]) {
throw new NotFoundException("User not found");
}
return rows[0];
}
// ์ฌ์ฉ ์์
const user = await UserModel.findById("P", "123");
// user.employee?.department?.name ์ ๊ทผ ๊ฐ๋ฅ
๋ณตํฉ ๊ฒ์ API
async searchUsers(params: {
role?: UserRole;
departmentName?: string;
minSalary?: number;
}) {
const { qb, onSubset } = this.getSubsetQueries("P");
if (params.role) {
qb.where("users.role", params.role);
}
if (params.departmentName) {
// onSubset์ผ๋ก Subset P์ ํ
์ด๋ธ์ ํ์
์์ ํ๊ฒ ์ ๊ทผ
onSubset("P").where(
"employee__department.name",
"like",
`%${params.departmentName}%`
);
}
if (params.minSalary) {
onSubset("P").where("employee.salary", ">=", String(params.minSalary));
}
qb.orderBy("users.username", "asc");
return this.executeSubsetQuery({
subset: "P",
qb,
params: { num: 100, page: 1 },
});
}
Subset ์กฐํฉ
์ฌ๋ฌ Subset ์ฌ์ฉ (Union)
// ์ํฉ์ ๋ฐ๋ผ ๋ค๋ฅธ Subset ์ฌ์ฉ
async getUser(userId: number, detailed: boolean) {
if (detailed) {
return await UserModel.findById(userId, ["P"]);
// ํ์
: UserSubsetMapping["P"] | undefined
} else {
return await UserModel.findById(userId, ["SS"]);
// ํ์
: UserSubsetMapping["SS"] | undefined
}
}
์กฐ๊ฑด๋ถ Subset ์ ํ
async getUserData(userId: number, role: UserRole) {
const subsets = role === "admin" ? ["A"] : ["P"];
return await UserModel.findById(userId, subsets);
// ํ์
: UserSubsetMapping["A"] | UserSubsetMapping["P"] | undefined
}
์ฑ๋ฅ ์ต์ ํ
ํ์ํ Subset๋ง ์ฌ์ฉ
// โ ๋์จ: ๋ชฉ๋ก์์ Subset A ์ฌ์ฉ (๊ณผ๋ํ ๋ฐ์ดํฐ)
const users = await UserModel.findMany({
listParams: { page: 1, pageSize: 20 },
subsetKey: "A", // ๋ชจ๋ ํ๋ ๋ก๋ฉ
});
// โ
์ข์: ๋ชฉ๋ก์๋ Subset L ์ฌ์ฉ
const users = await UserModel.findMany({
listParams: { page: 1, pageSize: 20 },
subsetKey: "L", // ํ์ํ ํ๋๋ง
});
JOIN ์ต์ํ
// โ ๋์จ: ๋ถํ์ํ JOIN (Subset P)
// Subset P๋ employee, department ํ
์ด๋ธ๋ JOINํจ
const { qb } = UserModel.getSubsetQueries("P");
const result = await UserModel.executeSubsetQuery({
subset: "P",
qb,
params: { num: 100, page: 1 },
});
// โ
์ข์: JOIN ์๋ Subset (SS)
// Subset SS๋ users ํ
์ด๋ธ๋ง ์กฐํ
const { qb: qbSS } = UserModel.getSubsetQueries("SS");
const resultSS = await UserModel.executeSubsetQuery({
subset: "SS",
qb: qbSS,
params: { num: 100, page: 1 },
});
Subset๊ณผ Raw Puri ๋น๊ต
Subset ์ฌ์ฉ
// โ
Subset - ํ์
์์ , ๊ฐ๊ฒฐ
const { qb, onSubset } = UserModel.getSubsetQueries("P");
qb.where("users.role", "admin");
const result = await UserModel.executeSubsetQuery({
subset: "P",
qb,
params: { num: 20, page: 1 },
});
// ํ์
: ListResult<..., UserSubsetMapping["P"]>
// JOIN ์๋ ์ค์
// SELECT ์๋ ์ค์
Raw Puri ์ฌ์ฉ
// โ ๏ธ Raw Puri - ์๋ ์ค์
const puri = UserModel.getPuri("r");
const users = await puri
.table("users")
.leftJoin("employees", "users.id", "employees.user_id")
.leftJoin("departments", "employees.department_id", "departments.id")
.select({
id: "users.id",
username: "users.username",
employee__salary: "employees.salary",
employee__department__name: "departments.name",
})
.where("users.role", "admin")
.many();
// ํ์
: { id: number; username: string; ... }[]
// JOIN ์๋ ์ค์
// SELECT ์๋ ์ค์
// Hydrate ์๋ ์ ์ฉ ํ์
Subset์ ์ฌ์ฉํ์ธ์!
- ํ์
์์ ์ฑ ๋ณด์ฅ
- ์ฝ๋ ์ฌ์ฌ์ฉ์ฑ ํฅ์
- ์ ์ง๋ณด์ ํธ๋ฆฌ
- ์ฟผ๋ฆฌ ์ผ๊ด์ฑ ์ ์ง
๋ค์ ๋จ๊ณ