๋ฉ”์ธ ์ฝ˜ํ…์ธ ๋กœ ๊ฑด๋„ˆ๋›ฐ๊ธฐ
Frame์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค Entity์™€ ์—ฐ๊ฒฐ๋˜์ง€ ์•Š์€ ๋…๋ฆฝ์ ์ธ API ์—”๋“œํฌ์ธํŠธ๋ฅผ ๋งŒ๋“ค ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

Frame ๊ฐœ์š”

Entity ๋ถˆํ•„์š”

DB ํ…Œ์ด๋ธ” ์—†์ด ์ž‘๋™์ž์œ ๋กœ์šด API ๊ตฌํ˜„

๊ฐ„๋‹จํ•œ ๊ตฌ์กฐ

BaseFrameClass ์ƒ์†@api ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ์‚ฌ์šฉ

๋‹ค์–‘ํ•œ ์šฉ๋„

ํ—ฌ์Šค์ฒดํฌ, ์œ ํ‹ธ๋ฆฌํ‹ฐ์™ธ๋ถ€ API ํ”„๋ก์‹œ

๋…๋ฆฝ์  ๋กœ์ง

๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง๋งŒ ๊ตฌํ˜„Entity ์ œ์•ฝ ์—†์Œ

Model vs Frame

Model (BaseModelClass)

// Entity ๊ธฐ๋ฐ˜ API
class UserModelClass extends BaseModelClass<
  UserSubsetKey,
  UserSubsetMapping,
  typeof userSubsetQueries,
  typeof userLoaderQueries
> {
  constructor() {
    super("User", userSubsetQueries, userLoaderQueries); // โ† Entity์™€ ์—ฐ๊ฒฐ
  }
  
  @api({ httpMethod: "GET" })
  async list(): Promise<User[]> {
    // users ํ…Œ์ด๋ธ” ์กฐํšŒ
    const rdb = this.getPuri("r");
    return rdb.table("users").select("*");
  }
  
  @api({ httpMethod: "POST" })
  async create(params: UserSaveParams): Promise<{ userId: number }> {
    // users ํ…Œ์ด๋ธ”์— ์‚ฝ์ž…
    const wdb = this.getPuri("w");
    wdb.ubRegister("users", params);
    const [userId] = await wdb.ubUpsert("users");
    return { userId };
  }
}
Model์˜ ํŠน์ง•:
  • Entity(DB ํ…Œ์ด๋ธ”)์™€ 1:1 ๋งคํ•‘
  • CRUD ์ž‘์—… ์ค‘์‹ฌ
  • UpsertBuilder, Subset ํ™œ์šฉ
  • ํƒ€์ž… ์•ˆ์ „์„ฑ ๋ณด์žฅ

Frame (BaseFrameClass)

// Entity ์—†๋Š” ๋…๋ฆฝ์  API
class HealthFrame extends BaseFrameClass {
  frameName = "Health"; // โ† Entity์™€ ๋ฌด๊ด€
  
  @api({ httpMethod: "GET" })
  async check(): Promise<{
    status: "ok" | "error";
    timestamp: Date;
    uptime: number;
  }> {
    // DB ์ ‘๊ทผ ์—†์ด ์„œ๋ฒ„ ์ƒํƒœ๋งŒ ๋ฐ˜ํ™˜
    return {
      status: "ok",
      timestamp: new Date(),
      uptime: process.uptime(),
    };
  }
  
  @api({ httpMethod: "GET" })
  async ping(): Promise<{ pong: string }> {
    return { pong: "pong" };
  }
}
Frame์˜ ํŠน์ง•:
  • Entity์™€ ๋ฌด๊ด€ํ•œ ๋…๋ฆฝ์  ๋กœ์ง
  • ์œ ํ‹ธ๋ฆฌํ‹ฐ, ํ—ฌ์Šค์ฒดํฌ, ํ”„๋ก์‹œ ๋“ฑ
  • ์ž์œ ๋กœ์šด API ๊ตฌ์กฐ
  • ๊ฐ„๋‹จํ•œ ๊ตฌํ˜„

๋น„๊ต ํ‘œ

ํŠน์ง•ModelFrame
Entity ์—ฐ๊ฒฐโœ… ํ•„์ˆ˜โŒ ๋ถˆํ•„์š”
DB ์ ‘๊ทผCRUD ์ค‘์‹ฌ์„ ํƒ์ 
์‚ฌ์šฉ ์‚ฌ๋ก€๋ฐ์ดํ„ฐ ๊ด€๋ฆฌ์œ ํ‹ธ๋ฆฌํ‹ฐ, ํ”„๋ก์‹œ
๋ณต์žก๋„๋†’์Œ๋‚ฎ์Œ
UpsertBuilderโœ… ์‚ฌ์šฉโŒ ๋ถˆํ•„์š”
Subsetโœ… ์‚ฌ์šฉโŒ ๋ถˆํ•„์š”

Frame ์‚ฌ์šฉ ์‹œ๊ธฐ

โœ… Frame์„ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ

  1. ํ—ฌ์Šค์ฒดํฌ API
    // GET /api/health/check
    // Entity ์—†์ด ์„œ๋ฒ„ ์ƒํƒœ๋งŒ ํ™•์ธ
    
  2. ์œ ํ‹ธ๋ฆฌํ‹ฐ API
    // POST /api/utils/hash
    // ๋ฌธ์ž์—ด ํ•ด์‹œ ๊ณ„์‚ฐ ๋“ฑ ๋‹จ์ˆœ ๊ธฐ๋Šฅ
    
  3. ์™ธ๋ถ€ API ํ”„๋ก์‹œ
    // GET /api/weather/current
    // ์™ธ๋ถ€ ๋‚ ์”จ API๋ฅผ ๋‚ด๋ถ€ API๋กœ ๋ž˜ํ•‘
    
  4. ์ง‘๊ณ„ ํ†ต๊ณ„
    // GET /api/stats/dashboard
    // ์—ฌ๋Ÿฌ ํ…Œ์ด๋ธ”์˜ ํ†ต๊ณ„๋ฅผ ์ง‘๊ณ„
    
  5. ์ธ์ฆ/๊ถŒํ•œ ์ฒดํฌ
    // POST /api/auth/verify-token
    // JWT ํ† ํฐ ๊ฒ€์ฆ๋งŒ ์ˆ˜ํ–‰
    

โŒ Model์„ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ

  1. Entity CRUD ์ž‘์—…
    // User, Product, Order ๋“ฑ์˜ ์ƒ์„ฑ/์กฐํšŒ/์ˆ˜์ •/์‚ญ์ œ
    
  2. ๊ด€๊ณ„ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ
    // User โ†’ Profile, Order โ†’ OrderItems
    
  3. ๋ณต์žกํ•œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง
    // ์ฃผ๋ฌธ ์ฒ˜๋ฆฌ, ๊ฒฐ์ œ ๋“ฑ ์—ฌ๋Ÿฌ Entity ์กฐ์ž‘
    

๊ตฌ์กฐ ์ฐจ์ด

Model ๊ตฌ์กฐ

api/src/application/
  user/
    โ”œโ”€โ”€ user.entity.json       # โ† Entity ์ •์˜
    โ”œโ”€โ”€ user.model.ts          # โ† Model ํด๋ž˜์Šค
    โ”œโ”€โ”€ user.types.ts          # โ† ํƒ€์ž… ์ •์˜
    โ””โ”€โ”€ subsets/
        โ””โ”€โ”€ user-list.subset.json

Frame ๊ตฌ์กฐ

api/src/frames/
  health/
    โ””โ”€โ”€ health.frame.ts        # โ† Frame ํด๋ž˜์Šค๋งŒ
  utils/
    โ””โ”€โ”€ utils.frame.ts
Frame์€ Entity ์ •์˜ ์—†์ด .frame.ts ํŒŒ์ผ๋งŒ ์žˆ์œผ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

๊ฐ„๋‹จํ•œ ์˜ˆ์ œ

ํ—ฌ์Šค์ฒดํฌ Frame

class HealthFrame extends BaseFrameClass {
  frameName = "Health";
  
  @api({ httpMethod: "GET" })
  async check(): Promise<{
    status: "ok" | "error";
    timestamp: Date;
    database: "connected" | "disconnected";
  }> {
    // DB ์—ฐ๊ฒฐ ์ฒดํฌ
    let dbStatus: "connected" | "disconnected" = "disconnected";
    
    try {
      const rdb = this.getPuri("r");
      await rdb.raw("SELECT 1");
      dbStatus = "connected";
    } catch (error) {
      console.error("Database connection failed:", error);
    }
    
    return {
      status: dbStatus === "connected" ? "ok" : "error",
      timestamp: new Date(),
      database: dbStatus,
    };
  }
  
  @api({ httpMethod: "GET" })
  async ping(): Promise<{ message: string }> {
    return { message: "pong" };
  }
}

// ์—”๋“œํฌ์ธํŠธ:
// GET /api/health/check
// GET /api/health/ping

์œ ํ‹ธ๋ฆฌํ‹ฐ Frame

import crypto from "crypto";

class UtilsFrame extends BaseFrameClass {
  frameName = "Utils";
  
  @api({ httpMethod: "POST" })
  async hash(params: {
    text: string;
    algorithm: "md5" | "sha256" | "sha512";
  }): Promise<{ hash: string }> {
    const hash = crypto
      .createHash(params.algorithm)
      .update(params.text)
      .digest("hex");
    
    return { hash };
  }
  
  @api({ httpMethod: "POST" })
  async uuid(): Promise<{ uuid: string }> {
    return { uuid: crypto.randomUUID() };
  }
}

// ์—”๋“œํฌ์ธํŠธ:
// POST /api/utils/hash
// POST /api/utils/uuid

์žฅ๋‹จ์ 

์žฅ์ 

๋น ๋ฅธ ๊ตฌํ˜„

Entity ์ •์˜ ๋ถˆํ•„์š”๊ฐ„๋‹จํ•œ ๊ตฌ์กฐ

์œ ์—ฐ์„ฑ

์ œ์•ฝ ์—†๋Š” ๋กœ์ง์ž์œ ๋กœ์šด API ์„ค๊ณ„

๋…๋ฆฝ์„ฑ

DB ์Šคํ‚ค๋งˆ ๋…๋ฆฝ์ ์™ธ๋ถ€ API ์—ฐ๋™ ์šฉ์ด

ํ…Œ์ŠคํŠธ ์šฉ์ด

๋‹จ์ˆœํ•œ ๋กœ์งMock ๋ถˆํ•„์š”

๋‹จ์ 

ํƒ€์ž… ์•ˆ์ „์„ฑ ๊ฐ์†Œ

Entity ํƒ€์ž… ๋ฏธ์‚ฌ์šฉ์ˆ˜๋™ ํƒ€์ž… ๊ด€๋ฆฌ

์ž๋™ ์ƒ์„ฑ ๋ฏธ์ง€์›

Subset, Types ์ž๋™ ์ƒ์„ฑ ์—†์Œ์ˆ˜๋™ ์ž‘์„ฑ ํ•„์š”

์‹ค์ „ ํŒ

1. Frame์€ ๊ฐ„๋‹จํ•˜๊ฒŒ ์œ ์ง€

// โœ… ์ข‹์Œ: ๋‹จ์ˆœํ•œ ๋กœ์ง
class HealthFrame extends BaseFrameClass {
  @api({ httpMethod: "GET" })
  async check(): Promise<{ status: string }> {
    return { status: "ok" };
  }
}

// โŒ ๋‚˜์จ: ๋ณต์žกํ•œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง
class OrderFrame extends BaseFrameClass {
  @api({ httpMethod: "POST" })
  async process(params: ComplexOrderParams): Promise<OrderResult> {
    // 100์ค„์˜ ๋ณต์žกํ•œ ๋กœ์ง...
    // ์ด๋Ÿฐ ๊ฒฝ์šฐ๋Š” Model๋กœ ๋งŒ๋“ค์–ด์•ผ ํ•จ
  }
}

2. ์ ์ ˆํ•œ ๋„ค์ด๋ฐ

// โœ… ์ข‹์Œ: ๋ช…ํ™•ํ•œ ์ด๋ฆ„
class HealthFrame extends BaseFrameClass {
  frameName = "Health";
}

class UtilsFrame extends BaseFrameClass {
  frameName = "Utils";
}

// โŒ ๋‚˜์จ: ๋ชจํ˜ธํ•œ ์ด๋ฆ„
class MiscFrame extends BaseFrameClass {
  frameName = "Misc";
}

3. ๊ด€๋ จ ๊ธฐ๋Šฅ ๊ทธ๋ฃนํ™”

// โœ… ์ข‹์Œ: ๊ด€๋ จ๋œ ๊ธฐ๋Šฅ์„ ํ•˜๋‚˜์˜ Frame์—
class CryptoUtilsFrame extends BaseFrameClass {
  frameName = "CryptoUtils";
  
  @api({ httpMethod: "POST" })
  async hash(params: HashParams): Promise<{ hash: string }> { /* ... */ }
  
  @api({ httpMethod: "POST" })
  async encrypt(params: EncryptParams): Promise<{ encrypted: string }> { /* ... */ }
  
  @api({ httpMethod: "POST" })
  async decrypt(params: DecryptParams): Promise<{ decrypted: string }> { /* ... */ }
}

์–ธ์ œ Model๋กœ ์ „ํ™˜ํ• ๊นŒ?

Frame์œผ๋กœ ์‹œ์ž‘ํ–ˆ์ง€๋งŒ ๋‹ค์Œ ์ƒํ™ฉ์ด ๋˜๋ฉด Model๋กœ ์ „ํ™˜ ๊ณ ๋ ค:
  1. DB ๋ฐ์ดํ„ฐ๋ฅผ ์ž์ฃผ ์กฐํšŒ/์ˆ˜์ •
    • ์ฒ˜์Œ์—” ๋‹จ์ˆœ ์กฐํšŒ์˜€์ง€๋งŒ CRUD๊ฐ€ ํ•„์š”ํ•ด์ง
  2. ํƒ€์ž… ์•ˆ์ „์„ฑ์ด ์ค‘์š”ํ•ด์ง
    • ๋ณต์žกํ•œ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ๊ฐ€ ์ƒ๊น€
    • Entity ํƒ€์ž… ์ž๋™ ์ƒ์„ฑ์ด ํ•„์š”
  3. ๊ด€๊ณ„ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ํ•„์š”
    • ์—ฌ๋Ÿฌ ํ…Œ์ด๋ธ” ๊ฐ„์˜ ๊ด€๊ณ„ ์ฒ˜๋ฆฌ
    • JOIN, ์™ธ๋ž˜ํ‚ค ๋“ฑ
  4. ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์ด ๋ณต์žกํ•ด์ง
    • ๋‹จ์ˆœ ์œ ํ‹ธ๋ฆฌํ‹ฐ๋ฅผ ๋„˜์–ด์„ฌ
    • ํŠธ๋žœ์žญ์…˜ ์ฒ˜๋ฆฌ ํ•„์š”

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