What is Hackers' Pub?

Hackers' Pub is a place for software engineers to share their knowledge and experience with each other. It's also an ActivityPub-enabled social network, so you can follow your favorite hackers in the fediverse and get their latest posts in your feed.

0
0
0

๐ŸŽ‰ๆœฌๆ—ฅใ€ใ‚ฏใƒญใ‚นใƒ•ใ‚ฉใƒชใ‚ชๅฐ‚็”จSNSใ€ŒXissmieใ€ใ‚’ใƒชใƒชใƒผใ‚นใ„ใŸใ—ใพใ—ใŸ๐ŸŽ‰
Xissmieใฎใƒชใƒชใƒผใ‚นใซใ‚ใ‚ใ›ใฆใ€ใ‚ฏใƒญใ‚นใƒ•ใ‚ฉใƒชใ‚ชๅŠๅ…ฌๅผใ‚ขใ‚ซใ‚ฆใƒณใƒˆ๏ผˆไฟฎ่กŒไธญ๏ผ‰ใฏioใงใฎไฟฎ่กŒใ‚’็ต‚ใˆใ€Xissmieใซใฆๅ…ฌๅผใ‚ขใ‚ซใ‚ฆใƒณใƒˆใจใ—ใฆๆดปๅ‹•ใ—ใฆใ„ใใพใ™
โ€‹:blobcat_drawing:โ€‹

ใ“ใ‚Œใพใงใ”ๆ”ฏๆดใใ ใ•ใฃใŸ็š†ๆง˜ใ€ๆœฌๅฝ“ใซใ‚ใ‚ŠใŒใจใ†ใ”ใ–ใ„ใพใ—ใŸ๏ผ
Xissmieใฏไฝœๅ“ไฟ่ญทใชใฉใฎ่ฆณ็‚นใ‹ใ‚‰ใ€ๅฝ“้ขใฏไป–ใฎMisskeyใ‚ตใƒผใƒใƒผใชใฉใจใฎ้€ฃๅˆใฏไบˆๅฎšใ—ใฆใŠใ‚Šใพใ›ใ‚“ใฎใงใ€ๅŠๅ…ฌๅผใ‚ขใ‚ซใ‚ฆใƒณใƒˆใฎใใฎๅพŒใฎๆง˜ๅญใ‚’่ฆ‹ใŸใ„ๆ–นใฏใ€ใ‚‚ใ—ใ‚ˆใ‚ใ—ใ‘ใ‚ŒใฐXissmieใพใง้Šใณใซๆฅใฆใ‚‚ใ‚‰ใˆใ‚‹ใจใ†ใ‚Œใ—ใ„ใงใ™๏ผ
โ€‹:blobcatheartbongo:โ€‹

ๆœ€ๅพŒใซใชใ‚Šใพใ™ใŒใ€Xใ‹ใ‚‰ใฎไฝœๅ“ๅ–ใ‚Š่พผใฟ็ฅญใ‚Šใฎๆ™‚ใชใฉใ€ใฟใชใ•ใพใ‹ใ‚‰ใ„ใŸใ ใ„ใŸใ”ๅฃฐๆดใจๅ‹‡ๆฐ—ใฏไธ€็”Ÿๅฟ˜ใ‚Œใพใ›ใ‚“ใ€‚
ๆœฌๅฝ“ใซใ‚ใ‚ŠใŒใจใ†ใ”ใ–ใ„ใพใ—ใŸ
โ€‹:ablobcatnodmeltcry:โ€‹
ไปŠๅพŒใจใ‚‚ใ‚ฏใƒญใ‚นใƒ•ใ‚ฉใƒชใ‚ชใ‚’ไฝ•ๅ’ใ‚ˆใ‚ใ—ใใŠ้ก˜ใ„ใ„ใŸใ—ใพใ™๏ผ

0

ใจใ„ใ†ไธปใซใ‚คใƒฉใ‚นใƒˆใƒปใƒžใƒณใ‚ฌใฎๅ‰ตไฝœ่€…ๅ‘ใ‘๏ผˆ๏ผŸ๏ผ‰ใฎใƒ—ใƒฉใƒƒใƒˆใƒ•ใ‚ฉใƒผใƒ ใŒใ€ :misskey: ใ‚’ใƒ™ใƒผใ‚นใซใ—ใŸ็‹ฌ่‡ชใฎ SNS ใ‚’ๅ…ฌ้–‹ใ—ใŸใจใฎใ“ใจใ€‚

่‡ชๅˆ†ใฎ ใ‹ใ‚‰ใ‚‚ xissmie.xfolio.jp ใฎใƒ‰ใƒกใ‚คใƒณใฎใ‚ขใ‚ซใ‚ฆใƒณใƒˆใƒใƒฉใƒ›ใƒฉ่ฆ‹ใˆใ‚‹ใฎใง :fediverse: ใซใ‚‚้€ฃๆบใ—ใฆใ„ใ‚‹ใฟใŸใ„ :tony_happy:

xfolio.jp/information/335

0
0
0
0
0

ๆœ€่ฟ‘(์ตœ๊ทผ) 2ๅนด(๋…„) ๋™์•ˆ TypeScript๋กœ ์„œ๋ฒ„ ้–‹็™ผ(๊ฐœ๋ฐœ)์„ ํ•˜๋ฉด์„œ Kysely์™€ Drizzle ORM์„ ๋‘˜ ๋‹ค ์จ๋ดค๋Š”๋ฐ, ๊ทธ๋Ÿฌ๋ฉด์„œ ็ถ“้ฉ—(๊ฒฝํ—˜)ํ•œ ๊ฒƒ์„ ๅœŸ่‡บ(ํ† ๋Œ€)๋กœ ๋‘˜์„ ๆฏ”่ผƒ(๋น„๊ต)ํ•˜๋Š” ๊ธ€์„ ์จ ๋ณด์•˜์Šต๋‹ˆ๋‹ค.

https://hackers.pub/@hongminhee/2025/drizzle-orm-vs-kysely

0
0

ๆœ€่ฟ‘(์ตœ๊ทผ) 2ๅนด(๋…„) ๋™์•ˆ TypeScript๋กœ ์„œ๋ฒ„ ้–‹็™ผ(๊ฐœ๋ฐœ)์„ ํ•˜๋ฉด์„œ Kysely์™€ Drizzle ORM์„ ๋‘˜ ๋‹ค ์จ๋ดค๋Š”๋ฐ, ๊ทธ๋Ÿฌ๋ฉด์„œ ็ถ“้ฉ—(๊ฒฝํ—˜)ํ•œ ๊ฒƒ์„ ๅœŸ่‡บ(ํ† ๋Œ€)๋กœ ๋‘˜์„ ๆฏ”่ผƒ(๋น„๊ต)ํ•˜๋Š” ๊ธ€์„ ์จ ๋ณด์•˜์Šต๋‹ˆ๋‹ค.

https://hackers.pub/@hongminhee/2025/drizzle-orm-vs-kysely

0

ๆดช ๆฐ‘ๆ†™ (Hong Minhee) :nonbinary: shared the below article:

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฐœ๋ฐœ ์ธก๋ฉด์—์„œ ๋ณธ Drizzle ORM ๋Œ€ Kysely ๋น„๊ต

ๆดช ๆฐ‘ๆ†™ (Hong Minhee) @hongminhee@hackers.pub

TypeScript๋กœ ๋ฐฑ์—”๋“œ ์„œ๋ฒ„๋ฅผ ๊ฐœ๋ฐœํ•˜๋ฉด์„œ ์ ์ ˆํ•œ ORM ์„ ํƒ์€ ํ•ญ์ƒ ์ค‘์š”ํ•œ ๊ฒฐ์ • ์ค‘ ํ•˜๋‚˜์ž…๋‹ˆ๋‹ค. ์ตœ๊ทผ ์ œ ํ”„๋กœ์ ํŠธ์—์„œ Drizzle ORM๊ณผ Kysely๋ฅผ ๋ชจ๋‘ ์‚ฌ์šฉํ•ด ๋ณผ ๊ธฐํšŒ๊ฐ€ ์žˆ์—ˆ๋Š”๋ฐ, ๊ฐœ์ธ์ ์œผ๋กœ๋Š” Drizzle ORM์ด ๋” ํŽธ๋ฆฌํ•˜๊ณ  ์ƒ์‚ฐ์„ฑ์ด ๋†’์•˜๋˜ ๊ฒฝํ—˜์„ ๊ณต์œ ํ•˜๊ณ ์ž ํ•ฉ๋‹ˆ๋‹ค.

๋‘ ORM์— ๋Œ€ํ•œ ๊ฐ„๋žตํ•œ ์†Œ๊ฐœ

Drizzle ORM์€ TypeScript์šฉ ORM์œผ๋กœ, ํƒ€์ž… ์•ˆ์ „์„ฑ๊ณผ ์ง๊ด€์ ์ธ API๋ฅผ ๊ฐ•์ ์œผ๋กœ ๋‚ด์„ธ์šฐ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์Šคํ‚ค๋งˆ ์ •์˜๋ถ€ํ„ฐ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜, ์ฟผ๋ฆฌ ๋นŒ๋”๊นŒ์ง€ ํ’€์Šคํƒ ๊ฐœ๋ฐœ ๊ฒฝํ—˜์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

Kysely๋Š” โ€œํƒ€์ž… ์•ˆ์ „ํ•œ SQL ์ฟผ๋ฆฌ ๋นŒ๋”โ€๋กœ ์ž์‹ ์„ ์†Œ๊ฐœํ•˜๋ฉฐ, ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ์˜ ํƒ€์ž… ์‹œ์Šคํ…œ์„ ํ™œ์šฉํ•ด ์ฟผ๋ฆฌ ์ž‘์„ฑ ์‹œ ํƒ€์ž… ์•ˆ์ „์„ฑ์„ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค.

๋‘ ๋„๊ตฌ ๋ชจ๋‘ ํ›Œ๋ฅญํ•˜์ง€๋งŒ, ์ œ ๊ฐœ๋ฐœ ๊ฒฝํ—˜์— ๋น„์ถ”์–ด ๋ณผ ๋•Œ Drizzle ORM์ด ๋ช‡ ๊ฐ€์ง€ ์ธก๋ฉด์—์„œ ๋” ํŽธ๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค.

Drizzle ORM์„ ์„ ํ˜ธํ•˜๊ฒŒ ๋œ ์ด์œ 

์Šคํ‚ค๋งˆ ์ •์˜์˜ ์ง๊ด€์„ฑ

Drizzle ORM์˜ ์Šคํ‚ค๋งˆ ์ •์˜ ๋ฐฉ์‹์€ ๋งค์šฐ ์ง๊ด€์ ์ด๊ณ  ์„ ์–ธ์ ์ž…๋‹ˆ๋‹ค:

import { pgTable, serial, text, integer } from 'drizzle-orm/pg-core';

export const users = pgTable('users', {
  id: serial('id').primaryKey(),
  name: text('name').notNull(),
  email: text('email').unique().notNull(),
  age: integer('age')
});

Drizzle ORM์€ ์ด ์Šคํ‚ค๋งˆ ์ •์˜๋กœ๋ถ€ํ„ฐ ์ž๋™์œผ๋กœ CREATE TABLE SQL์„ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์–ด, ์Šคํ‚ค๋งˆ์™€ ์ฝ”๋“œ๊ฐ€ ํ•ญ์ƒ ๋™๊ธฐํ™”๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

๋ฐ˜๋ฉด Kysely๋Š” ํƒ€์ž… ์ •์˜์— ๋” ์ค‘์ ์„ ๋‘๊ณ  ์žˆ์–ด ์Šคํ‚ค๋งˆ์™€ ํƒ€์ž… ์ •์˜๊ฐ€ ๋ถ„๋ฆฌ๋˜๋Š” ๊ฒฝํ–ฅ์ด ์žˆ์Šต๋‹ˆ๋‹ค:

interface Database {
  users: {
    id: Generated<number>;
    name: string;
    email: string;
    age: number | null;
  };
}

์ด ํƒ€์ž… ์ •์˜๋Š” TypeScript ์ฝ”๋“œ์—์„œ ํƒ€์ž… ์•ˆ์ „์„ฑ์„ ์ œ๊ณตํ•˜์ง€๋งŒ, ์ด ํƒ€์ž… ์ •์˜๋งŒ์œผ๋กœ๋Š” CREATE TABLE SQL์„ ์ƒ์„ฑํ•  ์ˆ˜ ์—†๋‹ค๋Š” ๊ฒƒ์ด ๊ฒฐ์ •์ ์ธ ๋‹จ์ ์ž…๋‹ˆ๋‹ค. ์‹ค์ œ๋กœ ํ…Œ์ด๋ธ”์„ ์ƒ์„ฑํ•˜๋ ค๋ฉด ๋ณ„๋„์˜ SQL ์Šคํฌ๋ฆฝํŠธ๋‚˜ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ํƒ€์ž…๊ณผ ์‹ค์ œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์Šคํ‚ค๋งˆ ๊ฐ„์˜ ๋ถˆ์ผ์น˜ ๊ฐ€๋Šฅ์„ฑ์„ ๋†’์ž…๋‹ˆ๋‹ค.

Drizzle์˜ ์ ‘๊ทผ ๋ฐฉ์‹์ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์Šคํ‚ค๋งˆ์™€ TypeScript ํƒ€์ž…์„ ๋” ๊ธด๋ฐ€ํ•˜๊ฒŒ ์—ฐ๊ฒฐํ•ด์ฃผ์–ด ๊ฐœ๋ฐœ ๊ณผ์ •์—์„œ ํ˜ผ๋ž€์„ ์ค„์—ฌ์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.

๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๊ฒฝํ—˜

Drizzle ORM์˜ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๋„๊ตฌ(drizzle-kit)๋Š” ์ •๋ง ์ธ์ƒ์ ์ด์—ˆ์Šต๋‹ˆ๋‹ค. ์Šคํ‚ค๋งˆ ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ์ž๋™์œผ๋กœ ๊ฐ์ง€ํ•˜๊ณ  SQL ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํŒŒ์ผ์„ ์ƒ์„ฑํ•ด์ฃผ๋Š” ๊ธฐ๋Šฅ์ด ๊ฐœ๋ฐœ ์›Œํฌํ”Œ๋กœ์šฐ๋ฅผ ํฌ๊ฒŒ ๊ฐœ์„ ํ–ˆ์Šต๋‹ˆ๋‹ค:

npx drizzle-kit generate:pg

์ด ๋ช…๋ น์–ด ํ•˜๋‚˜๋กœ ์Šคํ‚ค๋งˆ ๋ณ€๊ฒฝ์‚ฌํ•ญ์— ๋Œ€ํ•œ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํŒŒ์ผ์ด ์ƒ์„ฑ๋˜๋ฉฐ, ์ด๋ฅผ ๊ฒ€ํ† ํ•˜๊ณ  ์ ์šฉํ•˜๋Š” ๊ณผ์ •์ด ๋งค์šฐ ๊ฐ„๋‹จํ–ˆ์Šต๋‹ˆ๋‹ค.

๋ฐ˜๋ฉด Kysely์˜ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์€ ๋ณธ์งˆ์ ์œผ๋กœ ์ˆ˜๋™์ ์ž…๋‹ˆ๋‹ค. ๊ฐœ๋ฐœ์ž๊ฐ€ ์ง์ ‘ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํŒŒ์ผ์„ ์ž‘์„ฑํ•ด์•ผ ํ•˜๋ฉฐ, ์Šคํ‚ค๋งˆ ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ์ž๋™์œผ๋กœ ๊ฐ์ง€ํ•˜๊ฑฐ๋‚˜ SQL์„ ์ƒ์„ฑํ•ด์ฃผ๋Š” ๊ธฐ๋Šฅ์ด ์—†์Šต๋‹ˆ๋‹ค:

// Kysely์˜ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์˜ˆ์‹œ
async function up(db: Kysely<any>): Promise<void> {
  await db.schema
    .createTable('users')
    .addColumn('id', 'serial', (col) => col.primaryKey())
    .addColumn('name', 'text', (col) => col.notNull())
    .addColumn('email', 'text', (col) => col.unique().notNull())
    .addColumn('age', 'integer')
    .execute();
}

async function down(db: Kysely<any>): Promise<void> {
  await db.schema.dropTable('users').execute();
}

์ด๋Ÿฌํ•œ ์ˆ˜๋™ ๋ฐฉ์‹์€ ๋ณต์žกํ•œ ์Šคํ‚ค๋งˆ ๋ณ€๊ฒฝ์—์„œ ์‹ค์ˆ˜ํ•  ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์•„์ง€๊ณ , ํŠนํžˆ ํฐ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ์ž‘์—…๋Ÿ‰์ด ์ƒ๋‹นํžˆ ์ฆ๊ฐ€ํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ Kysely์˜ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์—๋„ ๋‘ ๊ฐ€์ง€ ์ค‘์š”ํ•œ ์žฅ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค:

  1. TypeScript ๊ธฐ๋ฐ˜ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜: Kysely์˜ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์Šคํฌ๋ฆฝํŠธ๋Š” TypeScript๋กœ ์ž‘์„ฑ๋˜๊ธฐ ๋•Œ๋ฌธ์—, ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๋กœ์ง์— ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋กœ์ง์„ ํ†ตํ•ฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, S3์™€ ๊ฐ™์€ ์˜ค๋ธŒ์ ํŠธ ์Šคํ† ๋ฆฌ์ง€์˜ ๋ฐ์ดํ„ฐ๋„ ํ•จ๊ป˜ ๋งˆ์ด๊ทธ๋ ˆ์ดํŠธํ•˜๋Š” ๋ณต์žกํ•œ ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฐ˜๋ฉด Drizzle ORM์€ SQL ๊ธฐ๋ฐ˜ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์ด๋ฏ€๋กœ ์ด๋Ÿฌํ•œ ํ†ตํ•ฉ์ด ๋ถˆ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

  2. ์–‘๋ฐฉํ–ฅ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜: Kysely๋Š” up๊ณผ down ํ•จ์ˆ˜๋ฅผ ๋ชจ๋‘ ์ •์˜ํ•˜์—ฌ ์—…๊ทธ๋ ˆ์ด๋“œ์™€ ๋‹ค์šด๊ทธ๋ ˆ์ด๋“œ๋ฅผ ๋ชจ๋‘ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ํŠนํžˆ ํŒ€ ํ˜‘์—… ํ™˜๊ฒฝ์—์„œ ์ค‘์š”ํ•œ๋ฐ, ๋‹ค๋ฅธ ๊ฐœ๋ฐœ์ž์˜ ๋ณ€๊ฒฝ์‚ฌํ•ญ๊ณผ ์ถฉ๋Œ์ด ๋ฐœ์ƒํ•  ๊ฒฝ์šฐ ๋กค๋ฐฑ์ด ํ•„์š”ํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. Drizzle ORM์€ ํ˜„์žฌ ์—…๊ทธ๋ ˆ์ด๋“œ๋งŒ ์ง€์›ํ•˜๋ฉฐ, ๋‹ค์šด๊ทธ๋ ˆ์ด๋“œ ๊ธฐ๋Šฅ์ด ์—†์–ด ํ˜‘์—… ์‹œ ๋ถˆํŽธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ฐธ๊ณ ๋กœ, Python ์ƒํƒœ๊ณ„์˜ SQLAlchemy ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๋„๊ตฌ์ธ Alembic์€ ํ›จ์”ฌ ๋” ๋ฐœ์ „๋œ ํ˜•ํƒœ์˜ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. Alembic์€ ๋น„์„ ํ˜•์ ์ธ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๊ฒฝ๋กœ(๋ธŒ๋žœ์น˜ํฌ์ธํŠธ ์ƒ์„ฑ ๊ฐ€๋Šฅ)๋ฅผ ์ง€์›ํ•˜์—ฌ ๋ณต์žกํ•œ ํŒ€ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ๋„ ์œ ์—ฐํ•˜๊ฒŒ ๋Œ€์‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด์ƒ์ ์œผ๋กœ๋Š” JavaScript/TypeScript ์ƒํƒœ๊ณ„์˜ ORM๋„ ์ด๋Ÿฌํ•œ ์ˆ˜์ค€์˜ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๋„๊ตฌ๋ฅผ ์ œ๊ณตํ•˜๋Š” ๊ฒƒ์ด ๋ฐ”๋žŒ์งํ•ฉ๋‹ˆ๋‹ค.

๊ด€๊ณ„ ์„ค์ •์˜ ์šฉ์ด์„ฑ

Drizzle ORM์—์„œ ํ…Œ์ด๋ธ” ๊ฐ„ ๊ด€๊ณ„ ์„ค์ •์ด ๋งค์šฐ ์ง๊ด€์ ์ด์—ˆ์Šต๋‹ˆ๋‹ค:

import { relations } from 'drizzle-orm';

export const usersRelations = relations(users, ({ one, many }) => ({
  profile: one(profiles, {
    fields: [users.id],
    references: [profiles.userId],
  }),
  posts: many(posts)
}));

์ด ๋ฐฉ์‹์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„ค๊ณ„์˜ ๋ณธ์งˆ์ ์ธ, ๊ด€๊ณ„์ ์ธ ์ธก๋ฉด์„ ๋ช…ํ™•ํ•˜๊ฒŒ ํ‘œํ˜„ํ•ด์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.

์ฟผ๋ฆฌ ์ž‘์„ฑ์˜ ํŽธ์˜์„ฑ๊ณผ ๋™์ผ ์ด๋ฆ„ ์นผ๋Ÿผ ๋ฌธ์ œ ์ฒ˜๋ฆฌ

๋‘ ORM ๋ชจ๋‘ ์ฟผ๋ฆฌ ์ž‘์„ฑ์„ ์œ„ํ•œ API๋ฅผ ์ œ๊ณตํ•˜์ง€๋งŒ, Drizzle์˜ ์ ‘๊ทผ ๋ฐฉ์‹์ด ๋” ์ง๊ด€์ ์ด๊ณ  ๊ด€๊ณ„ํ˜• ๋ชจ๋ธ์„ ํ™œ์šฉํ•˜๊ธฐ ์‰ฌ์› ์Šต๋‹ˆ๋‹ค:

// Drizzle ORM - db.query ๋ฐฉ์‹์œผ๋กœ ๊ด€๊ณ„ ํ™œ์šฉ
const result = await db.query.posts.findMany({
  where: eq(posts.published, true),
  with: {
    user: true  // ๊ฒŒ์‹œ๋ฌผ ์ž‘์„ฑ์ž ์ •๋ณด๋ฅผ ํ•จ๊ป˜ ์กฐํšŒ
  }
});

// ๊ฒฐ๊ณผ ์ ‘๊ทผ์ด ์ง๊ด€์ ์ด๊ณ  ํƒ€์ž… ์•ˆ์ „ํ•จ
console.log(result[0].title);       // ๊ฒŒ์‹œ๋ฌผ ์ œ๋ชฉ
console.log(result[0].user.name);   // ์ž‘์„ฑ์ž ์ด๋ฆ„ - ๊ฐ์ฒด ๊ตฌ์กฐ๋กœ ๋ช…ํ™•ํ•˜๊ฒŒ ๊ตฌ๋ถ„๋จ
console.log(result[0].user.id);     // ์ž‘์„ฑ์ž ID - ๊ฒŒ์‹œ๋ฌผ ID์™€ ์ด๋ฆ„์ด ๊ฐ™์•„๋„ ๋ฌธ์ œ ์—†์Œ

// Kysely
const result = await db
  .selectFrom('posts')
  .where('posts.published', '=', true)
  .leftJoin('users', 'posts.userId', 'users.id')
  .selectAll();

// ๊ฒฐ๊ณผ ์ ‘๊ทผ ์‹œ ์นผ๋Ÿผ ์ด๋ฆ„ ์ถฉ๋Œ ๋ฌธ์ œ
console.log(result[0].id) // ์˜ค๋ฅ˜: posts.id์™€ users.id ์ค‘ ์–ด๋–ค ๊ฒƒ์ธ์ง€ ๋ชจํ˜ธํ•จ
console.log(result[0].name) // ์˜ค๋ฅ˜: ๋‘˜ ๋‹ค name ์นผ๋Ÿผ์ด ์žˆ๋‹ค๋ฉด ๋ชจํ˜ธํ•จ

Drizzle์˜ ์ ‘๊ทผ ๋ฐฉ์‹์ด ํ…Œ์ด๋ธ”๊ณผ ์ปฌ๋Ÿผ์„ ์ฐธ์กฐํ•  ๋•Œ ํƒ€์ž… ์•ˆ์ „์„ฑ์„ ๋” ๊ฐ•๋ ฅํ•˜๊ฒŒ ๋ณด์žฅํ•˜๊ณ , ๊ด€๊ณ„๋ฅผ ํ™œ์šฉํ•œ ์ฟผ๋ฆฌ ์ž‘์„ฑ์ด ๋” ์ง๊ด€์ ์ด์—ˆ์Šต๋‹ˆ๋‹ค.

ํŠนํžˆ ์—ฌ๋Ÿฌ ํ…Œ์ด๋ธ” ์กฐ์ธ ์‹œ ๋™์ผํ•œ ์ด๋ฆ„์˜ ์นผ๋Ÿผ ์ฒ˜๋ฆฌ ๋ถ€๋ถ„์—์„œ Drizzle ORM์ด ํ›จ์”ฌ ๋” ํŽธ๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ์ œ ๊ฐœ๋ฐœ ๊ฒฝํ—˜์—์„œ ๊ฐ€์žฅ ์ค‘์š”ํ•œ ์ฐจ์ด์  ์ค‘ ํ•˜๋‚˜์˜€์Šต๋‹ˆ๋‹ค.

// Drizzle ORM - ๋™์ผ ์ด๋ฆ„ ์นผ๋Ÿผ ์ฒ˜๋ฆฌ
const result = await db.query.posts.findMany({
  with: {
    user: true  // posts.id์™€ users.id๊ฐ€ ๋ชจ๋‘ ์žˆ์ง€๋งŒ ์ž๋™์œผ๋กœ ๊ตฌ๋ถ„๋จ
  }
});

// ๊ฒฐ๊ณผ์— ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์ ‘๊ทผ ๊ฐ€๋Šฅ
console.log(result[0].id);        // ๊ฒŒ์‹œ๋ฌผ ID
console.log(result[0].user.id);   // ์‚ฌ์šฉ์ž ID - ๋ช…ํ™•ํ•˜๊ฒŒ ๊ตฌ๋ถ„๋จ
console.log(result[0].user.name); // ์‚ฌ์šฉ์ž ์ด๋ฆ„

// Kysely - ๋™์ผ ์ด๋ฆ„ ์นผ๋Ÿผ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•ด ๋ณ„์นญ ํ•„์š”
const result = await db
  .selectFrom('posts')
  .leftJoin('users', 'posts.userId', 'users.id')
  .select([
    'posts.id as postId',       // ๋ณ„์นญ ํ•„์ˆ˜
    'posts.title',
    'posts.content',
    'users.id as userId',       // ๋ณ„์นญ ํ•„์ˆ˜
    'users.name as userName',   // ์นผ๋Ÿผ ์ด๋ฆ„์ด ๊ฐ™์„ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ๋ณ„์นญ ํ•„์ˆ˜
    'users.email as userEmail'  // ์ผ๊ด€์„ฑ์„ ์œ„ํ•ด ๋ชจ๋“  ์‚ฌ์šฉ์ž ๊ด€๋ จ ์นผ๋Ÿผ์— ์ ‘๋‘์–ด ํ•„์š”
  ]);

// ๋ณ„์นญ์„ ํ†ตํ•œ ์ ‘๊ทผ
console.log(result[0].postId);    // ๊ฒŒ์‹œ๋ฌผ ID
console.log(result[0].userId);    // ์‚ฌ์šฉ์ž ID
console.log(result[0].userName);  // ์‚ฌ์šฉ์ž ์ด๋ฆ„

Drizzle ORM์€ ํ…Œ์ด๋ธ”๊ณผ ์นผ๋Ÿผ์„ ๊ฐ์ฒด๋กœ ์ฐธ์กฐํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ๋™์ผํ•œ ์ด๋ฆ„์˜ ์นผ๋Ÿผ์ด ์žˆ์–ด๋„ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๊ณ„์ธต ๊ตฌ์กฐ๋กœ ์ฒ˜๋ฆฌ๋˜๋ฉฐ ํƒ€์ž… ์ถ”๋ก ๋„ ์ •ํ™•ํ•˜๊ฒŒ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. ๋ฐ˜๋ฉด Kysely์—์„œ๋Š” ๋ฌธ์ž์—ด ๊ธฐ๋ฐ˜ ์ ‘๊ทผ ๋ฐฉ์‹ ๋•Œ๋ฌธ์— ๋ณ„์นญ์„ ์ˆ˜๋™์œผ๋กœ ์ง€์ •ํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์•˜๊ณ , ๋ณต์žกํ•œ ์กฐ์ธ์—์„œ ์ด๋Ÿฐ ์ž‘์—…์ด ๋ฒˆ๊ฑฐ๋กœ์›Œ์กŒ์Šต๋‹ˆ๋‹ค. ํŠนํžˆ ์—ฌ๋Ÿฌ ํ…Œ์ด๋ธ”์— ๊ฐ™์€ ์ด๋ฆ„์˜ ์นผ๋Ÿผ์ด ๋งŽ์„์ˆ˜๋ก ๋ชจ๋“  ์นผ๋Ÿผ์— ๋ช…์‹œ์ ์ธ ๋ณ„์นญ์„ ์ง€์ •ํ•ด์•ผ ํ•˜๋Š” ๋ถˆํŽธํ•จ์ด ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

๋˜ํ•œ Drizzle ORM์€ ๊ฒฐ๊ณผ ํƒ€์ž…์„ ์ž๋™์œผ๋กœ ์ •ํ™•ํ•˜๊ฒŒ ์ถ”๋ก ํ•ด์ฃผ์–ด ๋ณ„๋„์˜ ํƒ€์ž… ์ง€์ • ์—†์ด๋„ ์•ˆ์ „ํ•˜๊ฒŒ ๊ฒฐ๊ณผ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

Kysely์˜ ์žฅ์ 

๋ฌผ๋ก  Kysely๋„ ์—ฌ๋Ÿฌ ๊ฐ•์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค:

  1. ๋” ๊ฐ€๋ฒผ์šด ๊ตฌ์กฐ: ํ•„์š”ํ•œ ๊ธฐ๋Šฅ๋งŒ ํฌํ•จํ•  ์ˆ˜ ์žˆ๋Š” ๋ชจ๋“ˆํ™”๋œ ๊ตฌ์กฐ
  2. SQL์— ๋” ๊ฐ€๊นŒ์šด ์ ‘๊ทผ: SQL ๊ตฌ๋ฌธ์— ๋งค์šฐ ์ถฉ์‹คํ•œ API ์„ค๊ณ„
  3. ์œ ์—ฐ์„ฑ: ๋ณต์žกํ•œ ์ฟผ๋ฆฌ์—์„œ ๋•Œ๋กœ ๋” ์œ ์—ฐํ•œ ์ž‘์„ฑ์ด ๊ฐ€๋Šฅ

๋˜ํ•œ ์•ž์„œ ์–ธ๊ธ‰ํ–ˆ๋“ฏ์ด, Kysely์˜ TypeScript ๊ธฐ๋ฐ˜ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜๊ณผ ์–‘๋ฐฉํ–ฅ(up/down) ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ง€์›์€ ํŠน์ • ์ƒํ™ฉ์—์„œ Drizzle ORM๋ณด๋‹ค ์šฐ์œ„์— ์žˆ๋Š” ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค.

SQLAlchemy์™€์˜ ๋น„๊ต ๋ฐ ์•ž์œผ๋กœ์˜ ๊ธฐ๋Œ€

JavaScript/TypeScript ์ƒํƒœ๊ณ„์˜ ORM์„ ์ด์•ผ๊ธฐํ•˜๊ธฐ ์ „์—, ์—ฌ๋Ÿฌ ์–ธ์–ด ์ค‘์—์„œ๋„ Python์˜ SQLAlchemy๋Š” ํŠน๋ณ„ํ•œ ์œ„์น˜๋ฅผ ์ฐจ์ง€ํ•ฉ๋‹ˆ๋‹ค. ๊ฐœ์ธ์ ์œผ๋กœ ์—ฌํƒœ ์‚ฌ์šฉํ•ด๋ณธ ๋‹ค์–‘ํ•œ ์–ธ์–ด์˜ ORM ์ค‘์—์„œ SQLAlchemy๊ฐ€ ๊ฐ€์žฅ ๊ธฐ๋Šฅ์ด ํ’๋ถ€ํ•˜๊ณ  ๊ฐ•๋ ฅํ•˜๋‹ค๊ณ  ๋А๊ผˆ์Šต๋‹ˆ๋‹ค. ๋ณต์žกํ•œ ์ฟผ๋ฆฌ ๊ตฌ์„ฑ, ๊ณ ๊ธ‰ ๊ด€๊ณ„ ๋งคํ•‘, ํŠธ๋žœ์žญ์…˜ ๊ด€๋ฆฌ, ์ด๋ฒคํŠธ ์‹œ์Šคํ…œ ๋“ฑ SQLAlchemy์˜ ๊ธฐ๋Šฅ์€ ์ •๋ง ๋ฐฉ๋Œ€ํ•ฉ๋‹ˆ๋‹ค.

Drizzle ORM์€ JavaScript ์ƒํƒœ๊ณ„์—์„œ ๋งค์šฐ ์ธ์ƒ์ ์ธ ๋ฐœ์ „์„ ์ด๋ฃจ์—ˆ์ง€๋งŒ, ์•„์ง SQLAlchemy์˜ ๊ฒฝ์ง€์—๋Š” ์ด๋ฅด์ง€ ๋ชปํ–ˆ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ํŠนํžˆ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ถ€๋ถ„์—์„œ SQLAlchemy์˜ ์„ฑ์ˆ™๋„์™€ ๊ธฐ๋Šฅ ํ’๋ถ€ํ•จ์ด ๋‹๋ณด์ž…๋‹ˆ๋‹ค:

  • ๋ณต์žกํ•œ ์„œ๋ธŒ์ฟผ๋ฆฌ์™€ ์œˆ๋„์šฐ ํ•จ์ˆ˜ ์ง€์›
  • ๋‹ค์–‘ํ•œ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ์™€ ํ›…
  • ๋‹ค์–‘ํ•œ ์ƒ์† ์ „๋žต
  • ๋ณต์žกํ•œ ํŠธ๋žœ์žญ์…˜ ๊ด€๋ฆฌ์™€ ์„ธ์…˜ ๊ด€๋ฆฌ
  • ๋Œ€๊ทœ๋ชจ ํ”„๋กœ์ ํŠธ์—์„œ ๊ฒ€์ฆ๋œ ์•ˆ์ •์„ฑ
  • Alembic์„ ํ†ตํ•œ ๋น„์„ ํ˜•์  ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ง€์›
  • ๋†€๋ผ์šธ ์ •๋„๋กœ ๋ฐฉ๋Œ€ํ•˜๊ณ  ์ƒ์„ธํ•œ ๋ฌธ์„œํ™”

๊ฒฐ๋ก 

๋‘ ORM ๋ชจ๋‘ ํ›Œ๋ฅญํ•œ ๋„๊ตฌ์ด์ง€๋งŒ, ์ œ ๊ฐœ๋ฐœ ์Šคํƒ€์ผ๊ณผ ํ”„๋กœ์ ํŠธ ์š”๊ตฌ์‚ฌํ•ญ์—๋Š” Drizzle ORM์ด ๋” ์ž˜ ๋งž์•˜์Šต๋‹ˆ๋‹ค. ํŠนํžˆ ์Šคํ‚ค๋งˆ ์ •์˜์˜ ์ง๊ด€์„ฑ, ๊ฐ•๋ ฅํ•œ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๋„๊ตฌ, ๊ทธ๋ฆฌ๊ณ  ์ „๋ฐ˜์ ์ธ ๊ฐœ๋ฐœ์ž ๊ฒฝํ—˜ ์ธก๋ฉด์—์„œ Drizzle ORM์ด ๋” ์ƒ์‚ฐ์ ์ธ ๊ฐœ๋ฐœ์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•ด์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.

๋™์ผ ์ด๋ฆ„ ์นผ๋Ÿผ ์ฒ˜๋ฆฌ์™€ ๊ฐ™์€ ์‹ค์งˆ์ ์ธ ๋ฌธ์ œ์—์„œ Drizzle ORM์˜ ๊ฐ์ฒด ๊ธฐ๋ฐ˜ ์ ‘๊ทผ ๋ฐฉ์‹์ด ๊ฐ€์ ธ๋‹ค์ฃผ๋Š” ํŽธ๋ฆฌํ•จ์€ ์‹ค์ œ ํ”„๋กœ์ ํŠธ์—์„œ ํฐ ์ฐจ์ด๋ฅผ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค.

ORM ์„ ํƒ์€ ๊ฒฐ๊ตญ ํ”„๋กœ์ ํŠธ ํŠน์„ฑ๊ณผ ๊ฐœ์ธ ์„ ํ˜ธ๋„์— ํฌ๊ฒŒ ์ขŒ์šฐ๋ฉ๋‹ˆ๋‹ค. ์ƒˆ๋กœ์šด ํ”„๋กœ์ ํŠธ๋ฅผ ์‹œ์ž‘ํ•œ๋‹ค๋ฉด ๋‘ ๋„๊ตฌ ๋ชจ๋‘ ๊ฐ„๋‹จํžˆ ํ…Œ์ŠคํŠธํ•ด๋ณด๊ณ  ์ž์‹ ์˜ ์›Œํฌํ”Œ๋กœ์šฐ์— ๋” ์ ํ•ฉํ•œ ๊ฒƒ์„ ์„ ํƒํ•˜๋Š” ๊ฒƒ์ด ์ข‹๊ฒ ์ง€๋งŒ, ์ œ ๊ฒฝ์šฐ์—๋Š” Drizzle ORM์ด ๋ช…ํ™•ํ•œ ์Šน์ž์˜€์Šต๋‹ˆ๋‹ค.

์•ž์œผ๋กœ Drizzle ORM์ด ๋”์šฑ ๋ฐœ์ „ํ•˜์—ฌ SQLAlchemy ์ˆ˜์ค€์˜ ํ’๋ถ€ํ•œ ๊ธฐ๋Šฅ๊ณผ ์œ ์—ฐ์„ฑ์„ ์ œ๊ณตํ•˜๊ฒŒ ๋˜๊ธธ ๋ฐ”๋ž๋‹ˆ๋‹ค. JavaScript/TypeScript ์ƒํƒœ๊ณ„์—๋„ ๊ทธ๋Ÿฐ ์ˆ˜์ค€์˜ ๊ฐ•๋ ฅํ•œ ORM์ด ์žˆ์œผ๋ฉด ์ข‹๊ฒ ์Šต๋‹ˆ๋‹ค. ๋‹คํ–‰ํžˆ๋„ Drizzle ORM์€ ๊ณ„์†ํ•ด์„œ ๋ฐœ์ „ํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, ๊ทธ ๋ฐœ์ „ ์†๋„๋ฅผ ๋ณด๋ฉด ๊ธฐ๋Œ€๊ฐ€ ํฝ๋‹ˆ๋‹ค.

์—ฌ๋Ÿฌ๋ถ„์˜ ๊ฒฝํ—˜์€ ์–ด๋–ค๊ฐ€์š”? ๋‹ค๋ฅธ ORM ๋„๊ตฌ๋‚˜ ์–ธ์–ด๋ฅผ ์‚ฌ์šฉํ•ด๋ณด์…จ๋‹ค๋ฉด ์˜๊ฒฌ์„ ๊ณต์œ ํ•ด์ฃผ์„ธ์š”!

Read more โ†’
1

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฐœ๋ฐœ ์ธก๋ฉด์—์„œ ๋ณธ Drizzle ORM ๋Œ€ Kysely ๋น„๊ต

ๆดช ๆฐ‘ๆ†™ (Hong Minhee) @hongminhee@hackers.pub

TypeScript๋กœ ๋ฐฑ์—”๋“œ ์„œ๋ฒ„๋ฅผ ๊ฐœ๋ฐœํ•˜๋ฉด์„œ ์ ์ ˆํ•œ ORM ์„ ํƒ์€ ํ•ญ์ƒ ์ค‘์š”ํ•œ ๊ฒฐ์ • ์ค‘ ํ•˜๋‚˜์ž…๋‹ˆ๋‹ค. ์ตœ๊ทผ ์ œ ํ”„๋กœ์ ํŠธ์—์„œ Drizzle ORM๊ณผ Kysely๋ฅผ ๋ชจ๋‘ ์‚ฌ์šฉํ•ด ๋ณผ ๊ธฐํšŒ๊ฐ€ ์žˆ์—ˆ๋Š”๋ฐ, ๊ฐœ์ธ์ ์œผ๋กœ๋Š” Drizzle ORM์ด ๋” ํŽธ๋ฆฌํ•˜๊ณ  ์ƒ์‚ฐ์„ฑ์ด ๋†’์•˜๋˜ ๊ฒฝํ—˜์„ ๊ณต์œ ํ•˜๊ณ ์ž ํ•ฉ๋‹ˆ๋‹ค.

๋‘ ORM์— ๋Œ€ํ•œ ๊ฐ„๋žตํ•œ ์†Œ๊ฐœ

Drizzle ORM์€ TypeScript์šฉ ORM์œผ๋กœ, ํƒ€์ž… ์•ˆ์ „์„ฑ๊ณผ ์ง๊ด€์ ์ธ API๋ฅผ ๊ฐ•์ ์œผ๋กœ ๋‚ด์„ธ์šฐ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์Šคํ‚ค๋งˆ ์ •์˜๋ถ€ํ„ฐ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜, ์ฟผ๋ฆฌ ๋นŒ๋”๊นŒ์ง€ ํ’€์Šคํƒ ๊ฐœ๋ฐœ ๊ฒฝํ—˜์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

Kysely๋Š” โ€œํƒ€์ž… ์•ˆ์ „ํ•œ SQL ์ฟผ๋ฆฌ ๋นŒ๋”โ€๋กœ ์ž์‹ ์„ ์†Œ๊ฐœํ•˜๋ฉฐ, ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ์˜ ํƒ€์ž… ์‹œ์Šคํ…œ์„ ํ™œ์šฉํ•ด ์ฟผ๋ฆฌ ์ž‘์„ฑ ์‹œ ํƒ€์ž… ์•ˆ์ „์„ฑ์„ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค.

๋‘ ๋„๊ตฌ ๋ชจ๋‘ ํ›Œ๋ฅญํ•˜์ง€๋งŒ, ์ œ ๊ฐœ๋ฐœ ๊ฒฝํ—˜์— ๋น„์ถ”์–ด ๋ณผ ๋•Œ Drizzle ORM์ด ๋ช‡ ๊ฐ€์ง€ ์ธก๋ฉด์—์„œ ๋” ํŽธ๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค.

Drizzle ORM์„ ์„ ํ˜ธํ•˜๊ฒŒ ๋œ ์ด์œ 

์Šคํ‚ค๋งˆ ์ •์˜์˜ ์ง๊ด€์„ฑ

Drizzle ORM์˜ ์Šคํ‚ค๋งˆ ์ •์˜ ๋ฐฉ์‹์€ ๋งค์šฐ ์ง๊ด€์ ์ด๊ณ  ์„ ์–ธ์ ์ž…๋‹ˆ๋‹ค:

import { pgTable, serial, text, integer } from 'drizzle-orm/pg-core';

export const users = pgTable('users', {
  id: serial('id').primaryKey(),
  name: text('name').notNull(),
  email: text('email').unique().notNull(),
  age: integer('age')
});

Drizzle ORM์€ ์ด ์Šคํ‚ค๋งˆ ์ •์˜๋กœ๋ถ€ํ„ฐ ์ž๋™์œผ๋กœ CREATE TABLE SQL์„ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์–ด, ์Šคํ‚ค๋งˆ์™€ ์ฝ”๋“œ๊ฐ€ ํ•ญ์ƒ ๋™๊ธฐํ™”๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

๋ฐ˜๋ฉด Kysely๋Š” ํƒ€์ž… ์ •์˜์— ๋” ์ค‘์ ์„ ๋‘๊ณ  ์žˆ์–ด ์Šคํ‚ค๋งˆ์™€ ํƒ€์ž… ์ •์˜๊ฐ€ ๋ถ„๋ฆฌ๋˜๋Š” ๊ฒฝํ–ฅ์ด ์žˆ์Šต๋‹ˆ๋‹ค:

interface Database {
  users: {
    id: Generated<number>;
    name: string;
    email: string;
    age: number | null;
  };
}

์ด ํƒ€์ž… ์ •์˜๋Š” TypeScript ์ฝ”๋“œ์—์„œ ํƒ€์ž… ์•ˆ์ „์„ฑ์„ ์ œ๊ณตํ•˜์ง€๋งŒ, ์ด ํƒ€์ž… ์ •์˜๋งŒ์œผ๋กœ๋Š” CREATE TABLE SQL์„ ์ƒ์„ฑํ•  ์ˆ˜ ์—†๋‹ค๋Š” ๊ฒƒ์ด ๊ฒฐ์ •์ ์ธ ๋‹จ์ ์ž…๋‹ˆ๋‹ค. ์‹ค์ œ๋กœ ํ…Œ์ด๋ธ”์„ ์ƒ์„ฑํ•˜๋ ค๋ฉด ๋ณ„๋„์˜ SQL ์Šคํฌ๋ฆฝํŠธ๋‚˜ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ํƒ€์ž…๊ณผ ์‹ค์ œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์Šคํ‚ค๋งˆ ๊ฐ„์˜ ๋ถˆ์ผ์น˜ ๊ฐ€๋Šฅ์„ฑ์„ ๋†’์ž…๋‹ˆ๋‹ค.

Drizzle์˜ ์ ‘๊ทผ ๋ฐฉ์‹์ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์Šคํ‚ค๋งˆ์™€ TypeScript ํƒ€์ž…์„ ๋” ๊ธด๋ฐ€ํ•˜๊ฒŒ ์—ฐ๊ฒฐํ•ด์ฃผ์–ด ๊ฐœ๋ฐœ ๊ณผ์ •์—์„œ ํ˜ผ๋ž€์„ ์ค„์—ฌ์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.

๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๊ฒฝํ—˜

Drizzle ORM์˜ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๋„๊ตฌ(drizzle-kit)๋Š” ์ •๋ง ์ธ์ƒ์ ์ด์—ˆ์Šต๋‹ˆ๋‹ค. ์Šคํ‚ค๋งˆ ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ์ž๋™์œผ๋กœ ๊ฐ์ง€ํ•˜๊ณ  SQL ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํŒŒ์ผ์„ ์ƒ์„ฑํ•ด์ฃผ๋Š” ๊ธฐ๋Šฅ์ด ๊ฐœ๋ฐœ ์›Œํฌํ”Œ๋กœ์šฐ๋ฅผ ํฌ๊ฒŒ ๊ฐœ์„ ํ–ˆ์Šต๋‹ˆ๋‹ค:

npx drizzle-kit generate:pg

์ด ๋ช…๋ น์–ด ํ•˜๋‚˜๋กœ ์Šคํ‚ค๋งˆ ๋ณ€๊ฒฝ์‚ฌํ•ญ์— ๋Œ€ํ•œ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํŒŒ์ผ์ด ์ƒ์„ฑ๋˜๋ฉฐ, ์ด๋ฅผ ๊ฒ€ํ† ํ•˜๊ณ  ์ ์šฉํ•˜๋Š” ๊ณผ์ •์ด ๋งค์šฐ ๊ฐ„๋‹จํ–ˆ์Šต๋‹ˆ๋‹ค.

๋ฐ˜๋ฉด Kysely์˜ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์€ ๋ณธ์งˆ์ ์œผ๋กœ ์ˆ˜๋™์ ์ž…๋‹ˆ๋‹ค. ๊ฐœ๋ฐœ์ž๊ฐ€ ์ง์ ‘ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํŒŒ์ผ์„ ์ž‘์„ฑํ•ด์•ผ ํ•˜๋ฉฐ, ์Šคํ‚ค๋งˆ ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ์ž๋™์œผ๋กœ ๊ฐ์ง€ํ•˜๊ฑฐ๋‚˜ SQL์„ ์ƒ์„ฑํ•ด์ฃผ๋Š” ๊ธฐ๋Šฅ์ด ์—†์Šต๋‹ˆ๋‹ค:

// Kysely์˜ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์˜ˆ์‹œ
async function up(db: Kysely<any>): Promise<void> {
  await db.schema
    .createTable('users')
    .addColumn('id', 'serial', (col) => col.primaryKey())
    .addColumn('name', 'text', (col) => col.notNull())
    .addColumn('email', 'text', (col) => col.unique().notNull())
    .addColumn('age', 'integer')
    .execute();
}

async function down(db: Kysely<any>): Promise<void> {
  await db.schema.dropTable('users').execute();
}

์ด๋Ÿฌํ•œ ์ˆ˜๋™ ๋ฐฉ์‹์€ ๋ณต์žกํ•œ ์Šคํ‚ค๋งˆ ๋ณ€๊ฒฝ์—์„œ ์‹ค์ˆ˜ํ•  ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์•„์ง€๊ณ , ํŠนํžˆ ํฐ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ์ž‘์—…๋Ÿ‰์ด ์ƒ๋‹นํžˆ ์ฆ๊ฐ€ํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ Kysely์˜ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์—๋„ ๋‘ ๊ฐ€์ง€ ์ค‘์š”ํ•œ ์žฅ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค:

  1. TypeScript ๊ธฐ๋ฐ˜ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜: Kysely์˜ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์Šคํฌ๋ฆฝํŠธ๋Š” TypeScript๋กœ ์ž‘์„ฑ๋˜๊ธฐ ๋•Œ๋ฌธ์—, ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๋กœ์ง์— ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋กœ์ง์„ ํ†ตํ•ฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, S3์™€ ๊ฐ™์€ ์˜ค๋ธŒ์ ํŠธ ์Šคํ† ๋ฆฌ์ง€์˜ ๋ฐ์ดํ„ฐ๋„ ํ•จ๊ป˜ ๋งˆ์ด๊ทธ๋ ˆ์ดํŠธํ•˜๋Š” ๋ณต์žกํ•œ ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฐ˜๋ฉด Drizzle ORM์€ SQL ๊ธฐ๋ฐ˜ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์ด๋ฏ€๋กœ ์ด๋Ÿฌํ•œ ํ†ตํ•ฉ์ด ๋ถˆ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

  2. ์–‘๋ฐฉํ–ฅ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜: Kysely๋Š” up๊ณผ down ํ•จ์ˆ˜๋ฅผ ๋ชจ๋‘ ์ •์˜ํ•˜์—ฌ ์—…๊ทธ๋ ˆ์ด๋“œ์™€ ๋‹ค์šด๊ทธ๋ ˆ์ด๋“œ๋ฅผ ๋ชจ๋‘ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ํŠนํžˆ ํŒ€ ํ˜‘์—… ํ™˜๊ฒฝ์—์„œ ์ค‘์š”ํ•œ๋ฐ, ๋‹ค๋ฅธ ๊ฐœ๋ฐœ์ž์˜ ๋ณ€๊ฒฝ์‚ฌํ•ญ๊ณผ ์ถฉ๋Œ์ด ๋ฐœ์ƒํ•  ๊ฒฝ์šฐ ๋กค๋ฐฑ์ด ํ•„์š”ํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. Drizzle ORM์€ ํ˜„์žฌ ์—…๊ทธ๋ ˆ์ด๋“œ๋งŒ ์ง€์›ํ•˜๋ฉฐ, ๋‹ค์šด๊ทธ๋ ˆ์ด๋“œ ๊ธฐ๋Šฅ์ด ์—†์–ด ํ˜‘์—… ์‹œ ๋ถˆํŽธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ฐธ๊ณ ๋กœ, Python ์ƒํƒœ๊ณ„์˜ SQLAlchemy ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๋„๊ตฌ์ธ Alembic์€ ํ›จ์”ฌ ๋” ๋ฐœ์ „๋œ ํ˜•ํƒœ์˜ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. Alembic์€ ๋น„์„ ํ˜•์ ์ธ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๊ฒฝ๋กœ(๋ธŒ๋žœ์น˜ํฌ์ธํŠธ ์ƒ์„ฑ ๊ฐ€๋Šฅ)๋ฅผ ์ง€์›ํ•˜์—ฌ ๋ณต์žกํ•œ ํŒ€ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ๋„ ์œ ์—ฐํ•˜๊ฒŒ ๋Œ€์‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด์ƒ์ ์œผ๋กœ๋Š” JavaScript/TypeScript ์ƒํƒœ๊ณ„์˜ ORM๋„ ์ด๋Ÿฌํ•œ ์ˆ˜์ค€์˜ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๋„๊ตฌ๋ฅผ ์ œ๊ณตํ•˜๋Š” ๊ฒƒ์ด ๋ฐ”๋žŒ์งํ•ฉ๋‹ˆ๋‹ค.

๊ด€๊ณ„ ์„ค์ •์˜ ์šฉ์ด์„ฑ

Drizzle ORM์—์„œ ํ…Œ์ด๋ธ” ๊ฐ„ ๊ด€๊ณ„ ์„ค์ •์ด ๋งค์šฐ ์ง๊ด€์ ์ด์—ˆ์Šต๋‹ˆ๋‹ค:

import { relations } from 'drizzle-orm';

export const usersRelations = relations(users, ({ one, many }) => ({
  profile: one(profiles, {
    fields: [users.id],
    references: [profiles.userId],
  }),
  posts: many(posts)
}));

์ด ๋ฐฉ์‹์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„ค๊ณ„์˜ ๋ณธ์งˆ์ ์ธ, ๊ด€๊ณ„์ ์ธ ์ธก๋ฉด์„ ๋ช…ํ™•ํ•˜๊ฒŒ ํ‘œํ˜„ํ•ด์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.

์ฟผ๋ฆฌ ์ž‘์„ฑ์˜ ํŽธ์˜์„ฑ๊ณผ ๋™์ผ ์ด๋ฆ„ ์นผ๋Ÿผ ๋ฌธ์ œ ์ฒ˜๋ฆฌ

๋‘ ORM ๋ชจ๋‘ ์ฟผ๋ฆฌ ์ž‘์„ฑ์„ ์œ„ํ•œ API๋ฅผ ์ œ๊ณตํ•˜์ง€๋งŒ, Drizzle์˜ ์ ‘๊ทผ ๋ฐฉ์‹์ด ๋” ์ง๊ด€์ ์ด๊ณ  ๊ด€๊ณ„ํ˜• ๋ชจ๋ธ์„ ํ™œ์šฉํ•˜๊ธฐ ์‰ฌ์› ์Šต๋‹ˆ๋‹ค:

// Drizzle ORM - db.query ๋ฐฉ์‹์œผ๋กœ ๊ด€๊ณ„ ํ™œ์šฉ
const result = await db.query.posts.findMany({
  where: eq(posts.published, true),
  with: {
    user: true  // ๊ฒŒ์‹œ๋ฌผ ์ž‘์„ฑ์ž ์ •๋ณด๋ฅผ ํ•จ๊ป˜ ์กฐํšŒ
  }
});

// ๊ฒฐ๊ณผ ์ ‘๊ทผ์ด ์ง๊ด€์ ์ด๊ณ  ํƒ€์ž… ์•ˆ์ „ํ•จ
console.log(result[0].title);       // ๊ฒŒ์‹œ๋ฌผ ์ œ๋ชฉ
console.log(result[0].user.name);   // ์ž‘์„ฑ์ž ์ด๋ฆ„ - ๊ฐ์ฒด ๊ตฌ์กฐ๋กœ ๋ช…ํ™•ํ•˜๊ฒŒ ๊ตฌ๋ถ„๋จ
console.log(result[0].user.id);     // ์ž‘์„ฑ์ž ID - ๊ฒŒ์‹œ๋ฌผ ID์™€ ์ด๋ฆ„์ด ๊ฐ™์•„๋„ ๋ฌธ์ œ ์—†์Œ

// Kysely
const result = await db
  .selectFrom('posts')
  .where('posts.published', '=', true)
  .leftJoin('users', 'posts.userId', 'users.id')
  .selectAll();

// ๊ฒฐ๊ณผ ์ ‘๊ทผ ์‹œ ์นผ๋Ÿผ ์ด๋ฆ„ ์ถฉ๋Œ ๋ฌธ์ œ
console.log(result[0].id) // ์˜ค๋ฅ˜: posts.id์™€ users.id ์ค‘ ์–ด๋–ค ๊ฒƒ์ธ์ง€ ๋ชจํ˜ธํ•จ
console.log(result[0].name) // ์˜ค๋ฅ˜: ๋‘˜ ๋‹ค name ์นผ๋Ÿผ์ด ์žˆ๋‹ค๋ฉด ๋ชจํ˜ธํ•จ

Drizzle์˜ ์ ‘๊ทผ ๋ฐฉ์‹์ด ํ…Œ์ด๋ธ”๊ณผ ์ปฌ๋Ÿผ์„ ์ฐธ์กฐํ•  ๋•Œ ํƒ€์ž… ์•ˆ์ „์„ฑ์„ ๋” ๊ฐ•๋ ฅํ•˜๊ฒŒ ๋ณด์žฅํ•˜๊ณ , ๊ด€๊ณ„๋ฅผ ํ™œ์šฉํ•œ ์ฟผ๋ฆฌ ์ž‘์„ฑ์ด ๋” ์ง๊ด€์ ์ด์—ˆ์Šต๋‹ˆ๋‹ค.

ํŠนํžˆ ์—ฌ๋Ÿฌ ํ…Œ์ด๋ธ” ์กฐ์ธ ์‹œ ๋™์ผํ•œ ์ด๋ฆ„์˜ ์นผ๋Ÿผ ์ฒ˜๋ฆฌ ๋ถ€๋ถ„์—์„œ Drizzle ORM์ด ํ›จ์”ฌ ๋” ํŽธ๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ์ œ ๊ฐœ๋ฐœ ๊ฒฝํ—˜์—์„œ ๊ฐ€์žฅ ์ค‘์š”ํ•œ ์ฐจ์ด์  ์ค‘ ํ•˜๋‚˜์˜€์Šต๋‹ˆ๋‹ค.

// Drizzle ORM - ๋™์ผ ์ด๋ฆ„ ์นผ๋Ÿผ ์ฒ˜๋ฆฌ
const result = await db.query.posts.findMany({
  with: {
    user: true  // posts.id์™€ users.id๊ฐ€ ๋ชจ๋‘ ์žˆ์ง€๋งŒ ์ž๋™์œผ๋กœ ๊ตฌ๋ถ„๋จ
  }
});

// ๊ฒฐ๊ณผ์— ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์ ‘๊ทผ ๊ฐ€๋Šฅ
console.log(result[0].id);        // ๊ฒŒ์‹œ๋ฌผ ID
console.log(result[0].user.id);   // ์‚ฌ์šฉ์ž ID - ๋ช…ํ™•ํ•˜๊ฒŒ ๊ตฌ๋ถ„๋จ
console.log(result[0].user.name); // ์‚ฌ์šฉ์ž ์ด๋ฆ„

// Kysely - ๋™์ผ ์ด๋ฆ„ ์นผ๋Ÿผ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•ด ๋ณ„์นญ ํ•„์š”
const result = await db
  .selectFrom('posts')
  .leftJoin('users', 'posts.userId', 'users.id')
  .select([
    'posts.id as postId',       // ๋ณ„์นญ ํ•„์ˆ˜
    'posts.title',
    'posts.content',
    'users.id as userId',       // ๋ณ„์นญ ํ•„์ˆ˜
    'users.name as userName',   // ์นผ๋Ÿผ ์ด๋ฆ„์ด ๊ฐ™์„ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ๋ณ„์นญ ํ•„์ˆ˜
    'users.email as userEmail'  // ์ผ๊ด€์„ฑ์„ ์œ„ํ•ด ๋ชจ๋“  ์‚ฌ์šฉ์ž ๊ด€๋ จ ์นผ๋Ÿผ์— ์ ‘๋‘์–ด ํ•„์š”
  ]);

// ๋ณ„์นญ์„ ํ†ตํ•œ ์ ‘๊ทผ
console.log(result[0].postId);    // ๊ฒŒ์‹œ๋ฌผ ID
console.log(result[0].userId);    // ์‚ฌ์šฉ์ž ID
console.log(result[0].userName);  // ์‚ฌ์šฉ์ž ์ด๋ฆ„

Drizzle ORM์€ ํ…Œ์ด๋ธ”๊ณผ ์นผ๋Ÿผ์„ ๊ฐ์ฒด๋กœ ์ฐธ์กฐํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ๋™์ผํ•œ ์ด๋ฆ„์˜ ์นผ๋Ÿผ์ด ์žˆ์–ด๋„ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๊ณ„์ธต ๊ตฌ์กฐ๋กœ ์ฒ˜๋ฆฌ๋˜๋ฉฐ ํƒ€์ž… ์ถ”๋ก ๋„ ์ •ํ™•ํ•˜๊ฒŒ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. ๋ฐ˜๋ฉด Kysely์—์„œ๋Š” ๋ฌธ์ž์—ด ๊ธฐ๋ฐ˜ ์ ‘๊ทผ ๋ฐฉ์‹ ๋•Œ๋ฌธ์— ๋ณ„์นญ์„ ์ˆ˜๋™์œผ๋กœ ์ง€์ •ํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์•˜๊ณ , ๋ณต์žกํ•œ ์กฐ์ธ์—์„œ ์ด๋Ÿฐ ์ž‘์—…์ด ๋ฒˆ๊ฑฐ๋กœ์›Œ์กŒ์Šต๋‹ˆ๋‹ค. ํŠนํžˆ ์—ฌ๋Ÿฌ ํ…Œ์ด๋ธ”์— ๊ฐ™์€ ์ด๋ฆ„์˜ ์นผ๋Ÿผ์ด ๋งŽ์„์ˆ˜๋ก ๋ชจ๋“  ์นผ๋Ÿผ์— ๋ช…์‹œ์ ์ธ ๋ณ„์นญ์„ ์ง€์ •ํ•ด์•ผ ํ•˜๋Š” ๋ถˆํŽธํ•จ์ด ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

๋˜ํ•œ Drizzle ORM์€ ๊ฒฐ๊ณผ ํƒ€์ž…์„ ์ž๋™์œผ๋กœ ์ •ํ™•ํ•˜๊ฒŒ ์ถ”๋ก ํ•ด์ฃผ์–ด ๋ณ„๋„์˜ ํƒ€์ž… ์ง€์ • ์—†์ด๋„ ์•ˆ์ „ํ•˜๊ฒŒ ๊ฒฐ๊ณผ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

Kysely์˜ ์žฅ์ 

๋ฌผ๋ก  Kysely๋„ ์—ฌ๋Ÿฌ ๊ฐ•์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค:

  1. ๋” ๊ฐ€๋ฒผ์šด ๊ตฌ์กฐ: ํ•„์š”ํ•œ ๊ธฐ๋Šฅ๋งŒ ํฌํ•จํ•  ์ˆ˜ ์žˆ๋Š” ๋ชจ๋“ˆํ™”๋œ ๊ตฌ์กฐ
  2. SQL์— ๋” ๊ฐ€๊นŒ์šด ์ ‘๊ทผ: SQL ๊ตฌ๋ฌธ์— ๋งค์šฐ ์ถฉ์‹คํ•œ API ์„ค๊ณ„
  3. ์œ ์—ฐ์„ฑ: ๋ณต์žกํ•œ ์ฟผ๋ฆฌ์—์„œ ๋•Œ๋กœ ๋” ์œ ์—ฐํ•œ ์ž‘์„ฑ์ด ๊ฐ€๋Šฅ

๋˜ํ•œ ์•ž์„œ ์–ธ๊ธ‰ํ–ˆ๋“ฏ์ด, Kysely์˜ TypeScript ๊ธฐ๋ฐ˜ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜๊ณผ ์–‘๋ฐฉํ–ฅ(up/down) ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ง€์›์€ ํŠน์ • ์ƒํ™ฉ์—์„œ Drizzle ORM๋ณด๋‹ค ์šฐ์œ„์— ์žˆ๋Š” ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค.

SQLAlchemy์™€์˜ ๋น„๊ต ๋ฐ ์•ž์œผ๋กœ์˜ ๊ธฐ๋Œ€

JavaScript/TypeScript ์ƒํƒœ๊ณ„์˜ ORM์„ ์ด์•ผ๊ธฐํ•˜๊ธฐ ์ „์—, ์—ฌ๋Ÿฌ ์–ธ์–ด ์ค‘์—์„œ๋„ Python์˜ SQLAlchemy๋Š” ํŠน๋ณ„ํ•œ ์œ„์น˜๋ฅผ ์ฐจ์ง€ํ•ฉ๋‹ˆ๋‹ค. ๊ฐœ์ธ์ ์œผ๋กœ ์—ฌํƒœ ์‚ฌ์šฉํ•ด๋ณธ ๋‹ค์–‘ํ•œ ์–ธ์–ด์˜ ORM ์ค‘์—์„œ SQLAlchemy๊ฐ€ ๊ฐ€์žฅ ๊ธฐ๋Šฅ์ด ํ’๋ถ€ํ•˜๊ณ  ๊ฐ•๋ ฅํ•˜๋‹ค๊ณ  ๋А๊ผˆ์Šต๋‹ˆ๋‹ค. ๋ณต์žกํ•œ ์ฟผ๋ฆฌ ๊ตฌ์„ฑ, ๊ณ ๊ธ‰ ๊ด€๊ณ„ ๋งคํ•‘, ํŠธ๋žœ์žญ์…˜ ๊ด€๋ฆฌ, ์ด๋ฒคํŠธ ์‹œ์Šคํ…œ ๋“ฑ SQLAlchemy์˜ ๊ธฐ๋Šฅ์€ ์ •๋ง ๋ฐฉ๋Œ€ํ•ฉ๋‹ˆ๋‹ค.

Drizzle ORM์€ JavaScript ์ƒํƒœ๊ณ„์—์„œ ๋งค์šฐ ์ธ์ƒ์ ์ธ ๋ฐœ์ „์„ ์ด๋ฃจ์—ˆ์ง€๋งŒ, ์•„์ง SQLAlchemy์˜ ๊ฒฝ์ง€์—๋Š” ์ด๋ฅด์ง€ ๋ชปํ–ˆ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ํŠนํžˆ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ถ€๋ถ„์—์„œ SQLAlchemy์˜ ์„ฑ์ˆ™๋„์™€ ๊ธฐ๋Šฅ ํ’๋ถ€ํ•จ์ด ๋‹๋ณด์ž…๋‹ˆ๋‹ค:

  • ๋ณต์žกํ•œ ์„œ๋ธŒ์ฟผ๋ฆฌ์™€ ์œˆ๋„์šฐ ํ•จ์ˆ˜ ์ง€์›
  • ๋‹ค์–‘ํ•œ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ์™€ ํ›…
  • ๋‹ค์–‘ํ•œ ์ƒ์† ์ „๋žต
  • ๋ณต์žกํ•œ ํŠธ๋žœ์žญ์…˜ ๊ด€๋ฆฌ์™€ ์„ธ์…˜ ๊ด€๋ฆฌ
  • ๋Œ€๊ทœ๋ชจ ํ”„๋กœ์ ํŠธ์—์„œ ๊ฒ€์ฆ๋œ ์•ˆ์ •์„ฑ
  • Alembic์„ ํ†ตํ•œ ๋น„์„ ํ˜•์  ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ง€์›
  • ๋†€๋ผ์šธ ์ •๋„๋กœ ๋ฐฉ๋Œ€ํ•˜๊ณ  ์ƒ์„ธํ•œ ๋ฌธ์„œํ™”

๊ฒฐ๋ก 

๋‘ ORM ๋ชจ๋‘ ํ›Œ๋ฅญํ•œ ๋„๊ตฌ์ด์ง€๋งŒ, ์ œ ๊ฐœ๋ฐœ ์Šคํƒ€์ผ๊ณผ ํ”„๋กœ์ ํŠธ ์š”๊ตฌ์‚ฌํ•ญ์—๋Š” Drizzle ORM์ด ๋” ์ž˜ ๋งž์•˜์Šต๋‹ˆ๋‹ค. ํŠนํžˆ ์Šคํ‚ค๋งˆ ์ •์˜์˜ ์ง๊ด€์„ฑ, ๊ฐ•๋ ฅํ•œ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๋„๊ตฌ, ๊ทธ๋ฆฌ๊ณ  ์ „๋ฐ˜์ ์ธ ๊ฐœ๋ฐœ์ž ๊ฒฝํ—˜ ์ธก๋ฉด์—์„œ Drizzle ORM์ด ๋” ์ƒ์‚ฐ์ ์ธ ๊ฐœ๋ฐœ์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•ด์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.

๋™์ผ ์ด๋ฆ„ ์นผ๋Ÿผ ์ฒ˜๋ฆฌ์™€ ๊ฐ™์€ ์‹ค์งˆ์ ์ธ ๋ฌธ์ œ์—์„œ Drizzle ORM์˜ ๊ฐ์ฒด ๊ธฐ๋ฐ˜ ์ ‘๊ทผ ๋ฐฉ์‹์ด ๊ฐ€์ ธ๋‹ค์ฃผ๋Š” ํŽธ๋ฆฌํ•จ์€ ์‹ค์ œ ํ”„๋กœ์ ํŠธ์—์„œ ํฐ ์ฐจ์ด๋ฅผ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค.

ORM ์„ ํƒ์€ ๊ฒฐ๊ตญ ํ”„๋กœ์ ํŠธ ํŠน์„ฑ๊ณผ ๊ฐœ์ธ ์„ ํ˜ธ๋„์— ํฌ๊ฒŒ ์ขŒ์šฐ๋ฉ๋‹ˆ๋‹ค. ์ƒˆ๋กœ์šด ํ”„๋กœ์ ํŠธ๋ฅผ ์‹œ์ž‘ํ•œ๋‹ค๋ฉด ๋‘ ๋„๊ตฌ ๋ชจ๋‘ ๊ฐ„๋‹จํžˆ ํ…Œ์ŠคํŠธํ•ด๋ณด๊ณ  ์ž์‹ ์˜ ์›Œํฌํ”Œ๋กœ์šฐ์— ๋” ์ ํ•ฉํ•œ ๊ฒƒ์„ ์„ ํƒํ•˜๋Š” ๊ฒƒ์ด ์ข‹๊ฒ ์ง€๋งŒ, ์ œ ๊ฒฝ์šฐ์—๋Š” Drizzle ORM์ด ๋ช…ํ™•ํ•œ ์Šน์ž์˜€์Šต๋‹ˆ๋‹ค.

์•ž์œผ๋กœ Drizzle ORM์ด ๋”์šฑ ๋ฐœ์ „ํ•˜์—ฌ SQLAlchemy ์ˆ˜์ค€์˜ ํ’๋ถ€ํ•œ ๊ธฐ๋Šฅ๊ณผ ์œ ์—ฐ์„ฑ์„ ์ œ๊ณตํ•˜๊ฒŒ ๋˜๊ธธ ๋ฐ”๋ž๋‹ˆ๋‹ค. JavaScript/TypeScript ์ƒํƒœ๊ณ„์—๋„ ๊ทธ๋Ÿฐ ์ˆ˜์ค€์˜ ๊ฐ•๋ ฅํ•œ ORM์ด ์žˆ์œผ๋ฉด ์ข‹๊ฒ ์Šต๋‹ˆ๋‹ค. ๋‹คํ–‰ํžˆ๋„ Drizzle ORM์€ ๊ณ„์†ํ•ด์„œ ๋ฐœ์ „ํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, ๊ทธ ๋ฐœ์ „ ์†๋„๋ฅผ ๋ณด๋ฉด ๊ธฐ๋Œ€๊ฐ€ ํฝ๋‹ˆ๋‹ค.

์—ฌ๋Ÿฌ๋ถ„์˜ ๊ฒฝํ—˜์€ ์–ด๋–ค๊ฐ€์š”? ๋‹ค๋ฅธ ORM ๋„๊ตฌ๋‚˜ ์–ธ์–ด๋ฅผ ์‚ฌ์šฉํ•ด๋ณด์…จ๋‹ค๋ฉด ์˜๊ฒฌ์„ ๊ณต์œ ํ•ด์ฃผ์„ธ์š”!

Read more โ†’
1

Show GN: TrenDev - ์˜คํ”ˆ์†Œ์Šค ํŠธ๋ Œ๋“œ ๋ชจ์•„๋ณด๊ธฐ
------------------------------
AI ์˜คํ”ˆ์†Œ์Šค repo๋“ค์˜ ์ตœ์‹  ํŠธ๋ Œ๋“œ์™€ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค์–‘ํ•œ ํ”Œ๋žซํผ(GitHub, Kaggle, Hugging Face ๋“ฑ)์—์„œ ํ†ตํ•ฉํ•˜์—ฌ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.

- ๊ฐœ๋ฐœ ๋™๊ธฐ:
๊ธฐ์กด์˜ ๊ฐœ๋ณ„ ํ”Œ๋žซํผ์—์„œ๋Š” ๋ถ„์‚ฐ๋˜์–ด ์žˆ๋Š” AI ์˜คํ”ˆ์†Œ์Šค ๊ด€๋ จ ๋ฐ์ดํ„ฐ๋ฅผ ํ•œ๋ˆˆ์— ํ™•์ธํ•˜๊ธฐ ์–ด๋ ต๋‹ค๋Š” ์ ์—์„œ, ์—ฌ๋Ÿฌ ์†Œ์Šค์˜ ๋ฐ์ดํ„ฐ๋ฅผ ํ•œ๋ฐ ๋ชจ์•„ ๋น„๊ตํ•˜๊ณ  ๋ถ„์„ํ•  ์ˆ˜ ์žˆ๋Š” ํ†ตํ•ฉ โ€ฆ
------------------------------
https://news.hada.io/topic?id=19784&utm_source=googlechat&utm_medium=bot&utm_campaign=1834

0
0

ใพใ‚ใใ‚‚ใใ‚‚ใ„ใพใใ‚Œใฉใ“ใ‚ใงใฏใชใ„๏ผˆๆฐดๆ›œใซใพใ ไปŠ้–‹็™บไธญใฎใ‚ขใƒ—ใƒช2ใคใƒชใƒชใƒผใ‚น๏ผ‰ใฎใงใใ‚ใใ‚็งใฏใ„ใฃใŸใ‚“โ€‹:ai_kieru:โ€‹

RE:
https://misskey.io/notes/a5aywwwialol0md8

0

ใฎใˆใ‚‹ใ•ใ‚“ใ“ใ‚“ใซใกใฏ๏ผ
ๆ˜จๆ—ฅfedibirdใซๆฅใŸใฐใฃใ‹ใ‚Šใฎ๏พ‰๏พ‰ใจ็”ณใ—ใพใ™ใ€
Androidใ‚ขใƒ—ใƒชyuitoใ‚’ไฝฟใ†ใฎใงใ™ใŒใ€ๆŠ•็จฟใ—ใŸใ‚ใจใงไฟฎๆญฃใ‚’ใ—ใ‚ˆใ†ใจใ™ใ‚‹ใจใ“ใ‚“ใชใƒกใƒƒใ‚ปใƒผใ‚ธใ‹ๅ‡บใฆใใŸใฎใงใ€fedibirdใฎไป•ๆง˜ใชใฎใ‹ๆฐ—ใซใชใฃใฆใ„ใพใ™...๏ผไป–ใฎใ‚ตใƒผใƒใƒผใงใฏใ‚ˆใไฝœๅ‹•ใ—ใŸใฎใง...!

0
0
0
0
0
0
0

@shugoShugo Maeda ใชใ‚‹ใปใฉใชใ‚‹ใปใฉใ€‚Encoding::UTF_16BEใ‚„Encoding::UTF_16LEใงencodeใ—ใŸๅ ดๅˆใซใฏsizeใฏ3ใซใชใ‚‹ใ‚ˆใ†ใงใ™ใ€‚UTF-16ใงใ‚ตใƒญใ‚ฒใƒผใƒˆใƒšใ‚ขใซใชใ‚‹ๆ–‡ๅญ—ใ‚’็ฐกๅ˜ใซ็Ÿฅใ‚‹ใ“ใจใŒใงใใฆไพฟๅˆฉใ ใฃใŸใฎใงใ™ใŒใ€ไป–ใฎๆ–นๆณ•ใ‚’ๆŽขใ—ใพใ™w

@zundanzunda Ruby 3.1ไปฅๅ‰ใ ใจ"ๅ‰ๆ‘ๅฎถ".encode("UTF-16").sizeใ‚‚4ใซใชใ‚‹ใฎใง๐ ฎทใ‚’2ใจๆ•ฐใˆใฆใ„ใ‚‹ใฎใงใฏใชใใฆBOMใ‚‚ๆ•ฐใˆใฆใ„ใ‚‹ใฎใ‹ใ‚‚ใ—ใ‚Œใพใ›ใ‚“ใ€‚ใ‚ณใƒผใƒ‰ใƒฆใƒ‹ใƒƒใƒˆๆ•ฐใ‚’ๆ•ฐใˆใ‚‹ใฎใฏ.encode("UTF-16LE").bytesize / 2ใงใ‚ˆใ•ใใ†ใชๆฐ—ใŒใ—ใพใ™

0
0
0
0
0

ใŠๆ˜ผไผ‘ใฟใซ่žใ„ใŸใ€‚
ใƒชใƒ•ใ‚กใ‚ฏใ‚ฟใƒชใƒณใ‚ฐใฏ้€ŸๅบฆใŒๅคงไบ‹(ไป–ใ‚ฟใ‚นใ‚ฏใจใฎๅ…ผใญๅˆใ„็š„ใชๆ„ๅ‘ณใง)ใ€ๆ™ฎๆฎตใ‹ใ‚‰่„ณๅ†…ใƒชใƒ•ใ‚กใ‚ฏใ‚ฟใƒชใƒณใ‚ฐใ‚’ๅๅพฉ็ทด็ฟ’ใ™ใ‚‹ไบ‹ใงใƒชใƒ•ใ‚กใ‚ฏใ‚ฟ็ญ‹ใ‚’ใคใ‘ใพใ—ใ‚‡ใ†ใ€AIใŒใƒ—ใƒญใ‚ธใ‚งใ‚ฏใƒˆใฎๅ‰ฒใ‚Œ็ช“ใ‚ณใƒผใƒ‰ใ‚’ใใฎใพใพๆŒใฃใฆใใ‚‹ใฎใงใใ‚Œใ‚’ใƒชใƒ•ใ‚กใ‚ฏใ‚ฟใƒชใƒณใ‚ฐใ™ใ‚‹ๅฟ…่ฆใ€ใชใฉ
https://open.spotify.com/episode/5hq704quKHwAvSCe7CObnW?si=vJUsMzRYTVu3YKTZeDOsJg

0
0

GNโบ: my-yt - yt-dlp ๊ธฐ๋ฐ˜ ๊ฐœ์ธ YouTube ํ”„๋ก ํŠธ์—”๋“œ
------------------------------
- ๊ด‘๊ณ  ์—†์ด ๊น”๋”ํ•œ ๋ฏธ๋‹ˆ๋ฉ€ ์œ ํŠœ๋ธŒ ํ”„๋ก ํŠธ์—”๋“œ
-
yt-dlp๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์œ ํŠœ๋ธŒ์—์„œ ๋น„๋””์˜ค๋ฅผ ๋‹ค์šด๋กœ๋“œํ•˜๊ณ , ๋กœ์ปฌ AI ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•˜์—ฌ ๋น„๋””์˜ค ์ฝ˜ํ…์ธ  ์š”์•ฝ
- ์ฑ„๋„ ๊ด€๋ฆฌ ๋ฐ ๊ตฌ๋… ๊ธฐ๋Šฅ ์ œ๊ณต
- ๋ณด๊ณ  ์‹ถ์ง€ ์•Š์€ ๋น„๋””์˜ค ๋ฌด์‹œ
- ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ๋น„๋””์˜ค ์žฌ์ƒ
- ์˜คํ”„๋ผ์ธ ๋ฏธ๋””์–ด ์žฌ์ƒ
-
<track> ์š”์†Œ์™€ WebVTT โ€ฆ
------------------------------
https://news.hada.io/topic?id=19782&utm_source=googlechat&utm_medium=bot&utm_campaign=1834

0
0
0
0

์ด๊ฒŒ ๋˜ ์žฌ๋ฐŒ๋Š” ์–˜๊ธด๋ฐ, ๋ฏผ๊ฐ„์–ด์›์€ ์œจ๋ฆฌ์šฐ์Šค ์นด์ด์‚ฌ๋ฅด๊ฐ€ ๊ทธ ์ˆ˜์ˆ ๋กœ ํƒœ์–ด๋‚ฌ๊ธฐ ๋•Œ๋ฌธ์— Caesarean section์ด๋ผ ๋ถ€๋ฅธ๋‹ค๊ณ  ํ•œ๋‹ค. ํ—Œ๋ฐ ๊ทธ๊ฒƒ์„ ๋”ฐ๋ฅด๋ฉด "์ œ์™•์ ˆ๊ฐœ"๋Š” ์˜ค์—ญ์ด๋‹ค. ์—ฌ๊ธฐ์„œ Caesar๋Š” ๋กœ๋งˆ ํ™ฉ์ œ๊ฐ€ ์•„๋‹ˆ๋ผ, ์™•๋„ ํ™ฉ์ œ๋„ ์•„๋‹ˆ์—ˆ๋˜ ์œจ๋ฆฌ์šฐ์Šค ๊ฐœ์ธ์„ ๊ฐ€๋ฆฌํ‚ค๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ํ•œํŽธ ์ž๊ถ ์ ˆ๊ฐœ ์ˆ˜์ˆ ์€ ๋‹น์‹œ ์‚ฐ๋ชจ์—๊ฒŒ 100% ์น˜๋ช…์ ์ด์–ด์„œ ์‚ฐ๋ชจ๊ฐ€ ์ฃฝ์—ˆ๊ฑฐ๋‚˜ ๊ณง ์ฃฝ์„ ์ง€๊ฒฝ์ด ์•„๋‹ˆ๋ฉด ์•ˆ ํ–ˆ๊ณ , ์œจ๋ฆฌ์šฐ์Šค ์นด์ด์‚ฌ๋ฅด์˜ ์–ด๋จธ๋‹ˆ ์•„์šฐ๋ ๋ฆฌ์•„๊ฐ€ ์ถœ์‚ฐ ํ›„ ํ•œ์ฐธ ๋™์•ˆ ์ž˜ ์‚ด์•˜๋‹ค๋Š” ์ ์„ ๋ณด๋ฉด ๋ฏผ๊ฐ„์–ด์›์€ ํ‹€๋ฆฐ ๊ฒƒ์œผ๋กœ ๋ณด์ธ๋‹ค...

RE: https://bsky.app/profile/did:plc:5fi7bgxe6svztyolfjkiyctw/post/3lkipqocppk2d

0
0
0

Show GN: ์ปค์Šคํ…€ ์›จ๋”ฉ ์บ๋ฆญํ„ฐ ๋งŒ๋“ค๊ธฐ
------------------------------
์ž‘๋…„์— ์•„๋‚ด๋ž‘ ๊ฒฐํ˜ผํ•˜๋ฉด์„œ ๋งŒ๋“  ์˜คํ”ˆ์†Œ์Šค ๋ชจ๋ฐ”์ผ ์ฒญ์ฒฉ์žฅ ํ”„๋กœ์ ํŠธ๋ฅผ ๊ธฑ๋‰ด์Šค์— ์˜ฌ๋ ธ์—ˆ๋Š”๋ฐ์š”.
์ด๋ฒˆ์—๋Š” ์•„๋‚ด๊ฐ€ ๊ฒฐํ˜ผ ๊ด€๋ จ๋œ ์ƒˆ๋กœ์šด ํ”„๋กœ์ ํŠธ๋ฅผ ์ถœ์‹œํ•ด์„œ 1๋…„ ๋งŒ์— ์˜ฌ๋ ค๋ด…๋‹ˆ๋‹ค.

์ฃผ์š” ๊ธฐ๋Šฅ
- ๊ท€์—ฌ์šด ์‹ ๋ž‘
๐Ÿคต ์‹ ๋ถ€๐Ÿ‘ฐ์˜ ์ปค์Šคํ…€ ์›จ๋”ฉ ์บ๋ฆญํ„ฐ๋ฅผ ์ง์ ‘ ๋งŒ๋“ค์–ด ๋ณผ ์ˆ˜ ์žˆ๋Š” ์„œ๋น„โ€ฆ
------------------------------
https://news.hada.io/topic?id=19804&utm_source=googlechat&utm_medium=bot&utm_campaign=1834

0
0
0

[ํŒ”๋กœ์ž‰ํ›„๊ธฐ]
โ˜…โ˜†โ˜†โ˜†โ˜† ํˆฟ์ด ๊ฑฐ์˜ ์šฐ์šธํ•ดํ•˜๋Š” ๋‚ด์šฉ์ด์—์š”
โ˜…โ˜†โ˜†โ˜†โ˜† ๋ถ€์ŠคํŠธ๋งŒ ํ•˜๊ณ  ์ˆ˜์ œ ํˆฟ์€ ์•ˆ ์จ์š”
โ˜…โ˜†โ˜†โ˜†โ˜† ํ‚ค์šฐ๋Š” ๋„๋งˆ๋ฑ€ ๊ณต์œ ๋„ ์ž˜ ์•ˆ ํ•ด์š”
โ˜…โ˜†โ˜†โ˜†โ˜† ์ •์‹ ๊ฑด๊ฐ•์— ์•ˆ ์ข‹์•„์š”
โ˜…โ˜†โ˜†โ˜†โ˜† ์š”์ฆ˜ ๋ณด๊ธฐ ์‹ซ์–ด์š”
โ˜…โ˜†โ˜†โ˜†โ˜† ๋ฌด์Šจ ๋ง ํ•˜๋Š”์ง€ ์ดํ•ด๊ฐ€ ์•ˆ ๊ฐ€์š”
โ˜…โ˜†โ˜†โ˜†โ˜† ์ด ํˆฟ๋„ ํ›”์ณค์–ด์š”

0
0
0
0
0
0
0
0

GNโบ: 2025๋…„์˜ ์ปค๋ฆฌ์–ด ์กฐ์–ธ
------------------------------
# ํ˜„์žฌ ์‹œ์žฅ์˜ ๋ณ€ํ™”์™€ ์˜ํ–ฅ
- 2010~2020๋…„์— ๊ณ ์œ„์ง์— ์ง„์ž…ํ–ˆ๋˜ ์‚ฌ๋žŒ๋“ค์ด ํ˜„์žฌ ์—ญํ• ์—์„œ ์žฌ๋ฏธ๋ฅผ ๋œ ๋А๋ผ๊ณ  ์žˆ์Œ
- ๊ณผ๊ฑฐ์—๋Š” ๊ณ ์œ„์ง ๊ด€๋ฆฌ์ž๊ฐ€ *์ฑ„์šฉ, ์œ ์ง€, ๋™๊ธฐ ๋ถ€์—ฌ* ๋Šฅ๋ ฅ์œผ๋กœ ํ‰๊ฐ€๋ฐ›์•˜์Œ
- ํ˜„์žฌ๋Š” *์„ธ๋ถ€ ์‚ฌํ•ญ ์ฒ˜๋ฆฌ, ์†๋„ ํ–ฅ์ƒ, ๊ธฐ์ˆ  ์ „ํ™˜ ๋Œ€์‘* ๋Šฅ๋ ฅ์ด ๋” ์ค‘์š”ํ•ด์ง
- ํ˜„์žฌ์˜ ๊ณ ์œ„์ง ๋ฆฌ๋”๋“ค์€ ์ด๋Ÿฌโ€ฆ
------------------------------
https://news.hada.io/topic?id=19785&utm_source=googlechat&utm_medium=bot&utm_campaign=1834

0