๋ฉ”์ธ ์ฝ˜ํ…์ธ ๋กœ ๊ฑด๋„ˆ๋›ฐ๊ธฐ
Subset์€ Entity์˜ ํŠน์ • ํ•„๋“œ ์กฐํ•ฉ์„ ๋ฏธ๋ฆฌ ์ •์˜ํ•˜์—ฌ API ์‘๋‹ต์„ ํšจ์œจ์ ์œผ๋กœ ๊ด€๋ฆฌํ•˜๋Š” ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค. Sonamu UI์˜ Subset ๊ด€๋ฆฌ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๋ฉด ์‹œ๊ฐ์ ์œผ๋กœ Subset์„ ์ •์˜ํ•˜๊ณ  ํŽธ์ง‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Subset์ด๋ž€?

Subset์€ Entity์˜ ์ผ๋ถ€ ํ•„๋“œ๋งŒ ์„ ํƒ์ ์œผ๋กœ ๊ฐ€์ ธ์˜ค๋Š” ์‚ฌ์ „ ์ •์˜๋œ ํ•„๋“œ ๊ทธ๋ฃน์ž…๋‹ˆ๋‹ค.

์‚ฌ์šฉ ์ด์œ 

๋ฌธ์ œSubset ํ•ด๊ฒฐ์ฑ…
๋ถˆํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ ์ „์†กํ•„์š”ํ•œ ํ•„๋“œ๋งŒ ์„ ํƒ
์ผ๊ด€์„ฑ ์—†๋Š” ์‘๋‹ตํ‘œ์ค€ํ™”๋œ ํ•„๋“œ ์กฐํ•ฉ
๋ณต์žกํ•œ JOIN๊ด€๊ณ„ ๋ฐ์ดํ„ฐ ํฌํ•จ ์—ฌ๋ถ€ ์ œ์–ด
์„ฑ๋Šฅ ์ €ํ•˜์ตœ์†Œ ๋ฐ์ดํ„ฐ๋งŒ ์กฐํšŒ

์˜ˆ์‹œ

// โŒ ๋ชจ๋“  ํ•„๋“œ ์กฐํšŒ (๋น„ํšจ์œจ์ )
const users = await UserModel.findMany();

// โœ… Subset์œผ๋กœ ํ•„์š”ํ•œ ํ•„๋“œ๋งŒ ์กฐํšŒ
const users = await UserModel.findMany({ subset: "A" });
// โ†’ id, email, name๋งŒ ๋ฐ˜ํ™˜

Subset ๊ด€๋ฆฌ ํ™”๋ฉด

Subset ๊ด€๋ฆฌ ํ™”๋ฉด

Subset ๊ด€๋ฆฌ ํ™”๋ฉด์€ ๋‘ ๊ฐ€์ง€ ์ฃผ์š” ์˜์—ญ์œผ๋กœ ๊ตฌ์„ฑ๋ฉ๋‹ˆ๋‹ค:
  • ์™ผ์ชฝ Sidebar: Entity ๋ชฉ๋ก๊ณผ ๊ฐ Entity์˜ Subset ๋ชฉ๋ก
  • ์˜ค๋ฅธ์ชฝ Content: ์„ ํƒํ•œ Subset์˜ ํ•„๋“œ ์„ ํƒ ์ฒดํฌ๋ฐ•์Šค

Subset ์ƒ์„ฑ

1. Entity ์„ ํƒ

์™ผ์ชฝ ๋ชฉ๋ก์—์„œ Subset์„ ์ถ”๊ฐ€ํ•  Entity๋ฅผ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.

2. Subset ์ถ”๊ฐ€

[Add Subset] ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜์—ฌ ์ƒˆ Subset์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
ํ•„๋“œ์„ค๋ช…์˜ˆ์‹œ
Subset IDSubset ์‹๋ณ„์ž (A-Z)A, B, C
DescriptionSubset ์„ค๋ช…๊ธฐ๋ณธ ๋ชฉ๋ก์šฉ, ์ƒ์„ธ ์กฐํšŒ์šฉ
Subset ID๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ ์•ŒํŒŒ๋ฒณ ํ•œ ๊ธ€์ž(A, B, Cโ€ฆ)๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

3. ํ•„๋“œ ์„ ํƒ

์ƒ์„ฑ๋œ Subset์—์„œ ํฌํ•จํ•  ํ•„๋“œ๋ฅผ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค: ๊ธฐ๋ณธ ํ•„๋“œ:
โ˜‘ id          โ† ํ•„์ˆ˜ (ํ•ญ์ƒ ํฌํ•จ)
โ˜‘ email
โ˜‘ name
โ–ก password    โ† ๋ฏผ๊ฐ ์ •๋ณด ์ œ์™ธ
โ–ก bio
โ˜‘ created_at
๊ด€๊ณ„ ํ•„๋“œ:
โ˜ posts โ†’ Post.Subset A
โ˜ profile โ†’ Profile.Subset B

Subset ์ข…๋ฅ˜

Subset A (๊ธฐ๋ณธ ๋ชฉ๋ก)

์šฉ๋„: ๋ชฉ๋ก ์กฐํšŒ ์‹œ ์‚ฌ์šฉ ํฌํ•จ: ํ•„์ˆ˜ ์ •๋ณด + ์š”์•ฝ ์ •๋ณด
// User.Subset A
{
  id: true,
  email: true,
  name: true,
  created_at: true,
}
์‚ฌ์šฉ ์˜ˆ์‹œ:
// ์‚ฌ์šฉ์ž ๋ชฉ๋ก
const users = await UserModel.findMany({ subset: "A" });

Subset B (์ƒ์„ธ ์กฐํšŒ)

์šฉ๋„: ๋‹จ์ผ ๋ ˆ์ฝ”๋“œ ์ƒ์„ธ ์กฐํšŒ ํฌํ•จ: ๋ชจ๋“  ์ผ๋ฐ˜ ์ •๋ณด (๋ฏผ๊ฐ ์ •๋ณด ์ œ์™ธ)
// User.Subset B
{
  id: true,
  email: true,
  name: true,
  bio: true,
  profile_image: true,
  created_at: true,
  updated_at: true,
}
์‚ฌ์šฉ ์˜ˆ์‹œ:
// ์‚ฌ์šฉ์ž ์ƒ์„ธ
const user = await UserModel.findById(1, { subset: "B" });

Subset C (๊ด€๊ณ„ ํฌํ•จ)

์šฉ๋„: ๊ด€๊ณ„ ๋ฐ์ดํ„ฐ๋ฅผ ํฌํ•จํ•œ ์กฐํšŒ ํฌํ•จ: ๊ธฐ๋ณธ ์ •๋ณด + ๊ด€๊ณ„ Entity
// Post.Subset C
{
  id: true,
  title: true,
  content: true,
  author: "User.B",     // User์˜ Subset B ํฌํ•จ
  comments: "Comment.A", // Comment์˜ Subset A ํฌํ•จ
  created_at: true,
}
์‚ฌ์šฉ ์˜ˆ์‹œ:
// ๊ฒŒ์‹œ๊ธ€ + ์ž‘์„ฑ์ž + ๋Œ“๊ธ€
const post = await PostModel.findById(1, { subset: "C" });

๊ด€๊ณ„ ํ•„๋“œ ์„ค์ •

belongsTo ๊ด€๊ณ„

์˜ˆ์‹œ: Post โ†’ User (์ž‘์„ฑ์ž)
โ˜ author โ†’ User.Subset B
์„ ํƒํ•˜๋ฉด Post๋ฅผ ์กฐํšŒํ•  ๋•Œ ์ž‘์„ฑ์ž(User) ์ •๋ณด๋„ ํ•จ๊ป˜ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค:
// ๊ฒฐ๊ณผ
{
  id: 1,
  title: "Hello World",
  author: {
    id: 10,
    email: "[email protected]",
    name: "John Doe",
    // User.Subset B์˜ ๋‚˜๋จธ์ง€ ํ•„๋“œ...
  }
}

hasMany ๊ด€๊ณ„

์˜ˆ์‹œ: User โ†’ Post[] (์ž‘์„ฑํ•œ ๊ธ€ ๋ชฉ๋ก)
โ˜ posts โ†’ Post.Subset A
์„ ํƒํ•˜๋ฉด User๋ฅผ ์กฐํšŒํ•  ๋•Œ ์ž‘์„ฑํ•œ Post ๋ชฉ๋ก๋„ ํ•จ๊ป˜ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค:
// ๊ฒฐ๊ณผ
{
  id: 10,
  email: "[email protected]",
  name: "John Doe",
  posts: [
    { id: 1, title: "First Post", ... },
    { id: 2, title: "Second Post", ... },
  ]
}
N+1 ๋ฌธ์ œ ์ฃผ์˜: hasMany ๊ด€๊ณ„๋ฅผ Subset์— ํฌํ•จํ•˜๋ฉด ์ถ”๊ฐ€ ์ฟผ๋ฆฌ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•„์š”ํ•œ ๊ฒฝ์šฐ์—๋งŒ ํฌํ•จํ•˜์„ธ์š”.

Subset ์ˆ˜์ •

ํ•„๋“œ ์ถ”๊ฐ€/์ œ๊ฑฐ

  1. Subset ์„ ํƒ
  2. ํ•„๋“œ ์ฒดํฌ๋ฐ•์Šค ํด๋ฆญ
  3. [Save] ๋ฒ„ํŠผ ํด๋ฆญ
๋ณ€๊ฒฝ์‚ฌํ•ญ ์ฆ‰์‹œ ๋ฐ˜์˜:
Before: { id, email, name }
After:  { id, email, name, bio, created_at }

Subset ์‚ญ์ œ

  1. Subset ์„ ํƒ
  2. [Delete] ๋ฒ„ํŠผ ํด๋ฆญ
  3. ํ™•์ธ ๋ชจ๋‹ฌ์—์„œ [Confirm] ํด๋ฆญ
์‚ฌ์šฉ ์ค‘์ธ Subset ์‚ญ์ œ: ์‚ญ์ œํ•˜๋ ค๋Š” Subset์„ ์‚ฌ์šฉํ•˜๋Š” API๊ฐ€ ์žˆ๋‹ค๋ฉด ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์‹ค์ „ ์˜ˆ์ œ

์‚ฌ์šฉ์ž ๊ด€๋ฆฌ ์‹œ์Šคํ…œ

// User Entity Subsets

// Subset A: ๋ชฉ๋ก์šฉ (์ตœ์†Œ ์ •๋ณด)
{
  id: true,
  email: true,
  name: true,
  role: true,
  is_active: true,
}

// Subset B: ์ƒ์„ธ์šฉ (์ „์ฒด ์ •๋ณด, ๋ฏผ๊ฐ ์ •๋ณด ์ œ์™ธ)
{
  id: true,
  email: true,
  name: true,
  role: true,
  bio: true,
  profile_image: true,
  phone: true,
  is_active: true,
  created_at: true,
  updated_at: true,
}

// Subset C: ๊ด€๋ฆฌ์ž์šฉ (๋ชจ๋“  ์ •๋ณด)
{
  ...Subset B,
  last_login_at: true,
  login_count: true,
  ip_address: true,
}

๋ธ”๋กœ๊ทธ ์‹œ์Šคํ…œ

// Post Entity Subsets

// Subset A: ๋ชฉ๋ก์šฉ (์š”์•ฝ)
{
  id: true,
  title: true,
  excerpt: true,
  author: "User.A",
  view_count: true,
  created_at: true,
}

// Subset B: ์ƒ์„ธ์šฉ (์ „์ฒด ๋‚ด์šฉ)
{
  id: true,
  title: true,
  content: true,
  author: "User.B",
  category: "Category.A",
  tags: "Tag.A",
  view_count: true,
  created_at: true,
  updated_at: true,
}

// Subset C: ๋Œ“๊ธ€ ํฌํ•จ
{
  ...Subset B,
  comments: "Comment.A",
}

Model์—์„œ ์‚ฌ์šฉ

Subset์„ ์ •์˜ํ•˜๋ฉด Model API์—์„œ ์ž๋™์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

findMany์—์„œ ์‚ฌ์šฉ

class UserModelClass extends BaseModelClass {
  @api({ httpMethod: "GET" })
  async findMany(params: ListParams<User>) {
    const { qb } = this.getSubsetQueries("A");
    
    return this.executeSubsetQuery({
      subset: "A",
      qb,
      params,
    });
  }
}

findById์—์„œ ์‚ฌ์šฉ

class UserModelClass extends BaseModelClass {
  @api({ httpMethod: "GET" })
  async findById(id: number) {
    const { qb } = this.getSubsetQueries("B");
    qb.where("id", id);
    
    return this.executeSubsetQuery({
      subset: "B",
      qb,
      params: { num: 1, page: 1 },
    }).then(result => result.rows[0]);
  }
}

์„ฑ๋Šฅ ์ตœ์ ํ™”

ํ•„์š”ํ•œ ํ•„๋“œ๋งŒ ์„ ํƒ

// โŒ ๋ชจ๋“  ํ•„๋“œ ์กฐํšŒ (๋А๋ฆผ)
SELECT * FROM users

// โœ… Subset A ์‚ฌ์šฉ (๋น ๋ฆ„)
SELECT id, email, name FROM users

๊ด€๊ณ„ ๋กœ๋”ฉ ์ตœ์†Œํ™”

// โŒ ๋ถˆํ•„์š”ํ•œ ๊ด€๊ณ„ ํฌํ•จ
{
  posts: "Post.C",  // Post์˜ ๋ชจ๋“  ๊ด€๊ณ„๊นŒ์ง€ ๋กœ๋”ฉ
}

// โœ… ํ•„์š”ํ•œ ๊ด€๊ณ„๋งŒ
{
  posts: "Post.A",  // Post์˜ ๊ธฐ๋ณธ ์ •๋ณด๋งŒ
}

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