๋ฉ”์ธ ์ฝ˜ํ…์ธ ๋กœ ๊ฑด๋„ˆ๋›ฐ๊ธฐ
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์˜ ๊ณตํ†ต ํ•„๋“œ๋งŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
// ๋งŒ์•ฝ ๊ณตํ†ต ํ•„๋“œ๊ฐ€ ๊ฑฐ์˜ ์—†๋‹ค๋ฉด, ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ํ•„๋“œ๊ฐ€ ๋งค์šฐ ์ œํ•œ์ 
๊ต์ง‘ํ•ฉ ์‚ฌ์šฉ ์‹œ ์ฃผ์˜์‚ฌํ•ญ:
  1. ๋Ÿฐํƒ€์ž„ ๋ฐ์ดํ„ฐ โ‰  ํƒ€์ž…: ๋Ÿฐํƒ€์ž„์—๋Š” ๋” ํฐ Subset์˜ ๋ชจ๋“  ํ•„๋“œ๊ฐ€ ๋ฐ˜ํ™˜๋˜์ง€๋งŒ, ํƒ€์ž… ์‹œ์Šคํ…œ์€ ๊ต์ง‘ํ•ฉ๋งŒ ๋ณด์žฅ
  2. ์˜๋„ํ•˜์ง€ ์•Š์€ ๊ต์ง‘ํ•ฉ: ์—ฌ๋Ÿฌ Subset์„ ๋ฐฐ์—ด๋กœ ์ „๋‹ฌํ•  ๋•Œ ์‹ค์ˆ˜๋กœ ๊ต์ง‘ํ•ฉ์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Œ
  3. ํƒ€์ž… ๊ฐ€๋“œ ๊ถŒ์žฅ: ์กฐ๊ฑด๋ถ€๋กœ 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์„ ์‚ฌ์šฉํ•˜์„ธ์š”!
  • ํƒ€์ž… ์•ˆ์ „์„ฑ ๋ณด์žฅ
  • ์ฝ”๋“œ ์žฌ์‚ฌ์šฉ์„ฑ ํ–ฅ์ƒ
  • ์œ ์ง€๋ณด์ˆ˜ ํŽธ๋ฆฌ
  • ์ฟผ๋ฆฌ ์ผ๊ด€์„ฑ ์œ ์ง€

๋‹ค์Œ ๋‹จ๊ณ„