Subset์ Entity์ ๋ฐ์ดํฐ๋ฅผ ๋ค์ํ ํํ๋ก ์กฐํํ๊ธฐ ์ํ ๋ฏธ๋ฆฌ ์ ์๋ ์ฟผ๋ฆฌ ํ
ํ๋ฆฟ์
๋๋ค.
Subset ๊ฐ์
์ ์ฐํ ์กฐํ
์ํฉ์ ๋ง๋ ๋ฐ์ดํฐ ํ์A, P, SS ๋ฑ
ํ์
์์ ์ฑ
์ปดํ์ผ ํ์ ๊ฒ์ฆ์๋ ํ์
์ถ๋ก
๊ด๊ณ ๋ก๋ฉ
JOIN๊ณผ Loader ํตํฉ1:N, N:M ์ง์
์ฑ๋ฅ ์ต์ ํ
ํ์ํ ํ๋๋ง ์ ํN+1 ๋ฌธ์ ํด๊ฒฐ
Subset์ด ํ์ํ ์ด์
๋ฌธ์ : ๋จ์ผ ์ฟผ๋ฆฌ์ ํ๊ณ
API๋ง๋ค ํ์ํ ๋ฐ์ดํฐ ํ์์ด ๋ค๋ฆ
๋๋ค:
// โ ๋ฌธ์ 1: ๋ชฉ๋ก ์กฐํ - ๋๋ฌด ๋ง์ ๋ฐ์ดํฐ
const users = await db.table("users").selectAll();
// โ ๋ชจ๋ ์ปฌ๋ผ์ ๊ฐ์ ธ์ด (password, bio, ๋ถํ์ํ ์ ๋ณด ํฌํจ)
// โ ๋ฌธ์ 2: ์์ธ ์กฐํ - ๊ด๊ณ ๋ฐ์ดํฐ ๋ก๋ฉ ๋ณต์ก
const user = await db.table("users").where("id", 1).first();
const employee = await db.table("employees").where("user_id", user.id).first();
const department = await db.table("departments").where("id", employee.department_id).first();
// โ N+1 ๋ฌธ์ , ์ฝ๋ ์ค๋ณต
// โ ๋ฌธ์ 3: ํ์
์์ ์ฑ ๋ถ์กฑ
type UserList = {
id: number;
username: string;
role: string;
}; // ์๋์ผ๋ก ํ์
์ ์ ํ์
ํด๊ฒฐ: Subset์ผ๋ก ํตํฉ
// โ
Subset A: ์ ์ฒด ์ ๋ณด
const admin = await UserModel.findById(1, ["A"]);
// ํ์
: { id, created_at, email, username, role, bio, ... }
// โ
Subset P: ํ๋กํ + ๊ด๊ณ
const profile = await UserModel.findById(1, ["P"]);
// ํ์
: { id, username, employee: { salary, department: { name } } }
// โ
Subset SS: ์์ฝ ์ ๋ณด
const summary = await UserModel.findById(1, ["SS"]);
// ํ์
: { id, username, role, last_login_at }
Subset์ ๊ตฌ์ฑ ์์
1. SubsetQuery - ๊ธฐ๋ณธ ์ฟผ๋ฆฌ
Entity์์ ์ ์๋ ํ๋ ์ ํ:
{
"subsets": {
"A": ["id", "username", "email", "role"],
"P": ["id", "username", "employee.department.name"],
"SS": ["id", "username"]
}
}
2. LoaderQuery - ๊ด๊ณ ๋ก๋ฉ
1:N, N:M ๊ด๊ณ ๋ฐ์ดํฐ ๋ก๋ฉ:
// Loader ์ ์ (์๋ ์์ฑ๋จ)
const loaders = {
employees: {
as: "employees",
refId: "department_id",
qb: (qb, fromIds) =>
qb.table("employees")
.whereIn("department_id", fromIds)
.select({ id: "id", name: "username" }),
},
};
3. Hydrate - ๊ฒฐ๊ณผ ๋ณํ
Flat ๋ฐ์ดํฐ โ ์ค์ฒฉ ๊ฐ์ฒด:
// Flat ๊ฒฐ๊ณผ (DB์์ ์กฐํ)
{
id: 1,
username: "john",
employee__department__name: "Engineering"
}
// โ Hydrate ๋ณํ
// ์ค์ฒฉ ๊ฐ์ฒด (์ต์ข
๊ฒฐ๊ณผ)
{
id: 1,
username: "john",
employee: {
department: {
name: "Engineering"
}
}
}
Subset ๋ช
๋ช
๊ท์น
์ผ๋ฐ์ ์ธ Subset ์ด๋ฆ ํจํด:
| Subset | ์๋ฏธ | ์ฌ์ฉ ์์ |
|---|
| A | All (์ ์ฒด) | ๊ด๋ฆฌ์ ํ์ด์ง ์์ธ |
| P | Profile (ํ๋กํ) | ์ฌ์ฉ์ ํ๋กํ ์กฐํ |
| L | List (๋ชฉ๋ก) | ๋ชฉ๋ก API, ํ
์ด๋ธ ํ |
| SS | Summary (์์ฝ) | ๋๋กญ๋ค์ด, ๊ฐ๋จํ ์ ๋ณด |
| C | Card (์นด๋) | ์นด๋ UI ์ปดํฌ๋ํธ |
Subset ์ด๋ฆ์ ์์ ๋กญ๊ฒ ์ ์ํ ์ ์์ต๋๋ค. ํ ๋ด์์ ์ผ๊ด๋ ๊ท์น์ ์ฌ์ฉํ์ธ์.
์ค์ ์์
์ฌ์ฉ์ ๋ชฉ๋ก API
// GET /users - ์ฌ์ฉ์ ๋ชฉ๋ก
async list(params: UserListParams) {
const users = await UserModel.findMany({
listParams: params,
subsetKey: "L", // List Subset ์ฌ์ฉ
});
// ํ์
: { id: number; username: string; role: string; created_at: Date }[]
return users.map(user => ({
id: user.id,
username: user.username,
role: user.role,
createdAt: user.created_at,
}));
}
์ฌ์ฉ์ ํ๋กํ API
// GET /users/:id/profile
async getProfile(userId: number) {
const user = await UserModel.findById(userId, ["P"]);
if (!user) {
throw new Error("User not found");
}
// ํ์
: {
// id: number;
// username: string;
// employee: {
// salary: string;
// department: { name: string }
// }
// }
return {
id: user.id,
username: user.username,
department: user.employee?.department?.name,
salary: user.employee?.salary,
};
}
Subset vs Raw Puri
Subset ์ฌ์ฉ (๊ถ์ฅ)
// โ
Subset ์ฌ์ฉ
const user = await UserModel.findById(1, ["P"]);
// - ํ์
์์
// - ์ฝ๋ ์ฌ์ฌ์ฉ
// - ์ ์ง๋ณด์ ์ฌ์
Raw Puri ์ฌ์ฉ
// โ ๏ธ Raw Puri (ํน์ํ ๊ฒฝ์ฐ๋ง)
const user = await UserModel.getPuri("r")
.table("users")
.join("employees", "users.id", "employees.user_id")
.leftJoin("departments", "employees.department_id", "departments.id")
.select({
id: "users.id",
username: "users.username",
deptName: "departments.name",
})
.where("users.id", 1)
.first();
// - ํ์
์ถ๋ก ๋ณต์ก
// - ์ฝ๋ ์ค๋ณต
// - Subset์ผ๋ก ํํ ์ ๋๋ ํน์ ์ฟผ๋ฆฌ๋ง ์ฌ์ฉ
์ธ์ Raw Puri๋ฅผ ์ฌ์ฉํ๋์?
- Subset์ผ๋ก ํํํ ์ ์๋ ๋ณต์กํ ์ฟผ๋ฆฌ
- ์ฑ๋ฅ ์ต์ ํ๊ฐ ํ์ํ ํน์ ์ผ์ด์ค
- ์ผํ์ฑ ๋ฐ์ดํฐ ๋ง์ด๊ทธ๋ ์ด์
์ผ๋ฐ์ ์ธ CRUD๋ ํญ์ Subset ์ฌ์ฉ์ ๊ถ์ฅํฉ๋๋ค.
๋ค์ ๋จ๊ณ