Skip to content

Latest commit

Β 

History

History
355 lines (284 loc) Β· 12.7 KB

README-ko.md

File metadata and controls

355 lines (284 loc) Β· 12.7 KB

Drizzle ORMμ—μ„œ Drizzle Kit을 μ‚¬μš©ν•˜μ—¬ MySQL의 닀쀑 μŠ€ν‚€λ§ˆ μ²˜λ¦¬ν•˜κΈ°

졜근 νšŒμ‚¬μ—μ„œ, MSA(λ§ˆμ΄ν¬λ‘œμ„œλΉ„μŠ€ μ•„ν‚€ν…μ²˜)λ₯Ό 염두에 두고 κ΅¬μΆ•λœ μƒˆλ‘œμš΄ API μ„œλ²„μ—μ„œ 기쑴의 μ—¬λŸ¬ μŠ€ν‚€λ§ˆκ°€ μžˆλŠ” λ°μ΄ν„°λ² μ΄μŠ€ λ””μžμΈμ„ μ‚¬μš©ν•΄μ•Ό ν–ˆμŠ΅λ‹ˆλ‹€. 각 λΉ„μ¦ˆλ‹ˆμŠ€ 도메인이 μ—¬λŸ¬ μŠ€ν‚€λ§ˆλ‘œ λΆ„λ¦¬λ˜μ–΄ users와 같은 ν…Œμ΄λΈ”μ΄ μ—¬λŸ¬ μŠ€ν‚€λ§ˆμ— λΆ„μ‚°λ˜μ–΄ μžˆμ—ˆμŠ΅λ‹ˆλ‹€. μ΄λŠ” λ³΅μž‘μ„±μ„ μ΄ˆλž˜ν–ˆκ³ , μ—”μ§€λ‹ˆμ–΄ 인원이 10λͺ… 미만인 μ†Œκ·œλͺ¨ νŒ€μ΄μ—ˆκΈ° λ•Œλ¬Έμ—, users ν…Œμ΄λΈ”μ„ λ™κΈ°ν™”ν•˜λŠ” λŒ€μ‹  μŠ€ν‚€λ§ˆ κ°„μ˜ JOIN 연산을 μ‚¬μš©ν•˜κΈ°λ‘œ ν–ˆμŠ΅λ‹ˆλ‹€. Drizzle ORMμ—μ„œ μ΄λŸ¬ν•œ JOIN 연산을 μˆ˜ν–‰ν•˜λ €λ©΄ μŠ€ν‚€λ§ˆλ₯Ό λͺ…μ‹œν•΄μ•Ό ν–ˆμŠ΅λ‹ˆλ‹€.

기쑴의 μ—¬λŸ¬ μŠ€ν‚€λ§ˆμ™€ μˆ˜λ§Žμ€ ν…Œμ΄λΈ”μ„ ν¬ν•¨ν•˜λŠ” λ°μ΄ν„°λ² μ΄μŠ€ λ””μžμΈμ„ μž¬μ‚¬μš©ν•˜λŠ” μƒν™©μ΄μ—ˆκΈ° λ•Œλ¬Έμ—, Drizzle μŠ€ν‚€λ§ˆ νŒŒμΌμ„ μˆ˜λ™μœΌλ‘œ μž‘μ„±ν•˜λŠ” 번거둭고 였λ₯˜κ°€ λ°œμƒν•  수 μžˆλŠ” μž‘μ—…μ„ ν”Όν•˜κ³  μ‹Άμ—ˆμŠ΅λ‹ˆλ‹€. κ·Έλž˜μ„œ Drizzle Kit Introspect / Pull을 μ‚¬μš©ν•˜κΈ°λ‘œ ν–ˆμŠ΅λ‹ˆλ‹€. Drizzle ORM은 PostgreSQL 및 MySQL dialectμ—μ„œ μŠ€ν‚€λ§ˆλ₯Ό μ„ μ–Έν•˜λŠ” 방법을 μ œκ³΅ν•˜μ§€λ§Œ, Drizzle Kit Introspect / Pull은 아직 drizzle-kit introspect λͺ…λ Ήμ–΄μ—μ„œ μŠ€ν‚€λ§ˆ 이름을 μΆ”κ°€ν•˜λŠ” 것을 μ§€μ›ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

μ°Έκ³ : 이 λ¬Έμ„œμ—μ„œλŠ” schemaλΌλŠ” μš©μ–΄λ₯Ό PostgreSQL λ˜λŠ” Oracleκ³Ό 같은 λ°μ΄ν„°λ² μ΄μŠ€μ—μ„œ ν…Œμ΄λΈ” 및 기타 λ°μ΄ν„°λ² μ΄μŠ€ 객체의 논리적 그룹을 μ„€λͺ…ν•˜λŠ” μš©μ–΄λ‘œ μ‚¬μš©ν•©λ‹ˆλ‹€. MySQLμ—μ„œλŠ” schema λŒ€μ‹  databaseλΌλŠ” μš©μ–΄λ₯Ό 자주 μ‚¬μš©ν•©λ‹ˆλ‹€. MySQLμ—μ„œ 이 두 μš©μ–΄λŠ” κΈ°λŠ₯적으둜 λ™μΌν•˜λ―€λ‘œ, μ—¬κΈ°μ—μ„œ schema라고 μ–ΈκΈ‰ν•˜λŠ” 것은 MySQLμ—μ„œ λ§ν•˜λŠ” database와 λ™μΌν•©λ‹ˆλ‹€.

μ—¬λŸ¬ μŠ€ν‚€λ§ˆλ₯Ό μ‚¬μš©ν•˜λŠ” μƒ˜ν”Œ λ°μ΄ν„°λ² μ΄μŠ€ 생성

두 개의 μŠ€ν‚€λ§ˆμ™€ κ°„λ‹¨ν•œ ν…Œμ΄λΈ” 및 데이터λ₯Ό 생성해 λ³΄κ² μŠ΅λ‹ˆλ‹€.

-- Create the bananastand database
CREATE DATABASE IF NOT EXISTS bananastand;

-- Switch to bananastand database
USE bananastand;

-- Create bananas table
CREATE TABLE IF NOT EXISTS bananas (
    id INT AUTO_INCREMENT PRIMARY KEY,
    type VARCHAR(255) NOT NULL,
    ripeness_level ENUM('Unripe', 'Ripe', 'Overripe') NOT NULL,
    price DECIMAL(5, 2) NOT NULL,
    date_received DATE NOT NULL
);

-- Insert some sample data into bananas table
INSERT INTO bananas (type, ripeness_level, price, date_received) VALUES
('Cavendish', 'Ripe', 1.20, '2024-10-01'),
('Plantain', 'Unripe', 0.90, '2024-10-02'),
('Red Banana', 'Overripe', 1.50, '2024-10-03');


-- Create the pizzaplanet database
CREATE DATABASE IF NOT EXISTS pizzaplanet;

-- Switch to pizzaplanet database
USE pizzaplanet;

-- Create pizzas table
CREATE TABLE IF NOT EXISTS pizzas (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    toppings TEXT NOT NULL,
    size ENUM('Small', 'Medium', 'Large', 'Extra Large') NOT NULL,
    price DECIMAL(6, 2) NOT NULL
);

-- Create special_ingredients table linking pizzaplanet with bananastand
CREATE TABLE IF NOT EXISTS special_ingredients (
    id INT AUTO_INCREMENT PRIMARY KEY,
    pizza_id INT NOT NULL,
    banana_id INT NOT NULL,
    amount_needed INT NOT NULL,
    FOREIGN KEY (pizza_id) REFERENCES pizzas(id),
    FOREIGN KEY (banana_id) REFERENCES bananastand.bananas(id) ON DELETE CASCADE
);

-- Insert some sample data into pizzas table
INSERT INTO pizzas (name, toppings, size, price) VALUES
('Banana Deluxe', 'Banana, Cheese, Tomato Sauce', 'Large', 15.99),
('Tropical Banana Special', 'Banana, Pineapple, Ham, Cheese', 'Medium', 13.50);

-- Insert some sample data into special_ingredients table
INSERT INTO special_ingredients (pizza_id, banana_id, amount_needed) VALUES
(1, 1, 2),  -- Banana Deluxe uses 2 Cavendish bananas
(2, 2, 3);  -- Tropical Banana Special uses 3 Plantain bananas

λ°μ΄ν„°λ² μ΄μŠ€λ₯Ό μ‹€ν–‰ν•˜λ €λ©΄ Docker Composeλ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€.

// docker-compose.yml
services:
  db:
    image: mysql
    environment:
      MYSQL_ROOT_PASSWORD: rootpassword
    ports:
      - "3306:3306"
    volumes:
      - ./init.sql:/docker-entrypoint-initdb.d/1.sql

이제 λ°μ΄ν„°λ² μ΄μŠ€λ₯Ό μ‹€ν–‰ν•΄ λ³΄κ² μŠ΅λ‹ˆλ‹€.

3306 ν¬νŠΈμ—μ„œ λ‹€λ₯Έ μ„œλ²„κ°€ μ‹€ν–‰ 쀑이지 μ•Šμ€μ§€ ν™•μΈν•˜μ‹­μ‹œμ˜€.

docker compose up -d

λ°μ΄ν„°λ² μ΄μŠ€κ°€ 이제 μ‚¬μš©ν•  μ€€λΉ„κ°€ λ˜μ—ˆμŠ΅λ‹ˆλ‹€. mysql λͺ…λ Ήμ–΄λ₯Ό μ‚¬μš©ν•΄ 확인해 λ³΄μ‹­μ‹œμ˜€.

% mysql -h 127.0.0.1 -u root -prootpassword -e "\
SELECT pizzas.name AS pizza_name, \
bananas.type AS banana_type, \
bananas.ripeness_level as banana_ripeness, \
special_ingredients.amount_needed \
FROM pizzaplanet.pizzas \
JOIN pizzaplanet.special_ingredients ON pizzas.id = special_ingredients.pizza_id \
JOIN bananastand.bananas ON special_ingredients.banana_id = bananas.id;"
mysql: [Warning] Using a password on the command line interface can be insecure.
+-------------------------+-------------+-----------------+---------------+
| pizza_name              | banana_type | banana_ripeness | amount_needed |
+-------------------------+-------------+-----------------+---------------+
| Banana Deluxe           | Cavendish   | Ripe            |             2 |
| Tropical Banana Special | Plantain    | Unripe          |             3 |
+-------------------------+-------------+-----------------+---------------+

Drizzle Introspection ꡬ성

각 μŠ€ν‚€λ§ˆμ— λŒ€ν•΄ 두 개의 Drizzle ꡬ성을 λ§Œλ“­λ‹ˆλ‹€.

// drizzle.config.bananastand.ts
import { defineConfig } from "drizzle-kit"

export default defineConfig({
	out: "./src/drizzle/bananastand",
	dialect: "mysql",
	dbCredentials: {
		url: "mysql://root:rootpassword@localhost:3306/bananastand"
	}
})
// drizzle.config.pizzaplanet.ts
import { defineConfig } from "drizzle-kit"

export default defineConfig({
	out: "./src/drizzle/pizzaplanet",
	dialect: "mysql",
	dbCredentials: {
		url: "mysql://root:rootpassword@localhost:3306/pizzaplanet"
	}
})

drizzle-kit introspectλ₯Ό μ‹€ν–‰ν•˜κΈ° μœ„ν•œ package.json을 μƒμ„±ν•©λ‹ˆλ‹€.

{
  "name": "drizzle-introspect-schema",
  "scripts": {
    "drizzle:pull:bananastand": "drizzle-kit introspect --config drizzle.config.bananastand.ts",
    "drizzle:pull:pizzaplanet": "drizzle-kit introspect --config drizzle.config.pizzaplanet.ts",
    "drizzle:pull": "yarn drizzle:pull:bananastand && yarn drizzle:pull:pizzaplanet"
  },
  "dependencies": {
    "drizzle-orm": "^0.33.0",
    "mysql2": "^3.11.3"
  },
  "devDependencies": {
    "drizzle-kit": "^0.24.2",
    "tsx": "^4.19.1"
  }
}

yarn drizzle:pull을 μ‹€ν–‰ν•˜μ—¬ μŠ€ν‚€λ§ˆλ₯Ό κ°€μ Έμ˜¨ ν›„, μƒμ„±λœ 파일 쀑 ν•˜λ‚˜λ₯Ό 확인해 λ΄…λ‹ˆλ‹€.

// src/drizzle/bananastand/schema.ts
import { mysqlTable, mysqlSchema, AnyMySqlColumn, primaryKey, int, varchar, mysqlEnum, decimal, date } from "drizzle-orm/mysql-core"
import { sql } from "drizzle-orm"

export const bananas = mysqlTable("bananas", {
	id: int("id").autoincrement().notNull(),
	type: varchar("type", { length: 255 }).notNull(),
	ripenessLevel: mysqlEnum("ripeness_level", ['Unripe','Ripe','Overripe']).notNull(),
	price: decimal("price", { precision: 5, scale: 2 }).notNull(),
	// you can use { mode: 'date' }, if you want to have Date as type for this column
	dateReceived: date("date_received", { mode: 'string' }).notNull(),
},
(table) => {
	return {
		bananasId: primaryKey({ columns: [table.id], name: "bananas_id"}),
	}
});

μœ„ μ½”λ“œλŠ” mysqlTable을 μ‚¬μš©ν•˜μ—¬ ν…Œμ΄λΈ”μ„ μ •μ˜ν•©λ‹ˆλ‹€. μŠ€ν‚€λ§ˆλ₯Ό λͺ…μ‹œν•˜λ €λ©΄ μˆ˜λ™μœΌλ‘œ mysqlSchemaλ₯Ό μΆ”κ°€ν•΄μ•Ό ν•©λ‹ˆλ‹€.

// src/drizzle/bananastand/schema.ts
export const mySchema = mysqlSchema("bananastand")

export const bananas = mySchema.table("bananas", {
    // ...
})

μŠ€ν‚€λ§ˆ λ³€ν™˜

λ§Žμ€ μŠ€ν‚€λ§ˆκ°€ μžˆμ„ 경우 μˆ˜λ™μœΌλ‘œ μž‘μ—…ν•˜κΈ° νž˜λ“€κΈ° λ•Œλ¬Έμ— 이λ₯Ό μžλ™ν™”ν•  슀크립트λ₯Ό λ§Œλ“€μ–΄ λ΄…λ‹ˆλ‹€.

import fs from "fs"
import path from "path"

interface Replacement {
	search: RegExp
	replace: string
}

const replacements: Replacement[] = [
]

function replace(content: string, replacements: Replacement[]) {
	for (const { search, replace } of replacements) {
		content = content.replace(search, replace)
	}
	return content
}

// main
try {
	const schema = process.argv[2]
    
    const filePath = path.join(__dirname, `./src/drizzle/${schema}/schema.ts`)
	let content = fs.readFileSync(filePath, "utf8")
    
	if (schema) {
		const schemaDeclaration = `export const mySchema = mysqlSchema("${schema}");\n`
		replacements.push({
			search: /mysqlTable\("([^"]*)",/g,
			replace: 'mySchema.table("$1",'
		})
		const lastImportIndex = content.lastIndexOf("import")
		const lastImportEndIndex = content.indexOf("\n", lastImportIndex) + 1
		content = content.slice(0, lastImportEndIndex) + "\n" + schemaDeclaration + content.slice(lastImportEndIndex)
	}

	content = replace(content, replacements)
	fs.writeFileSync(filePath, content, "utf8")
	console.info(`Import and replacements completed successfully in ${filePath}`)
} catch (error) {
	console.error("An error occurred:", error)
}

μƒμ„±λœ μŠ€ν‚€λ§ˆλ₯Ό λ³€ν™˜ν•˜κΈ° μœ„ν•΄ ν•„μš”ν•œ 슀크립트λ₯Ό package.json에 μΆ”κ°€ν•©λ‹ˆλ‹€.

{
  // ...
  "scripts": {
    "drizzle:transform:bananastand": "npx tsx drizzle.transform.ts bananastand",
    "drizzle:transform:pizzaplanet": "npx tsx drizzle.transform.ts pizzaplanet",
    "drizzle:pull:bananastand": "drizzle-kit introspect --config drizzle.config.bananastand.ts && yarn drizzle:transform:bananastand",
    "drizzle:pull:pizzaplanet": "drizzle-kit introspect --config drizzle.config.pizzaplanet.ts && yarn drizzle:transform:pizzaplanet",
    "drizzle:pull": "yarn drizzle:pull:bananastand && yarn drizzle:pull:pizzaplanet"
  },
  // ...
}

이제 yarn drizzle:pull을 μ‹€ν–‰ν•˜κ³  μƒμ„±λœ νŒŒμΌμ„ λ‹€μ‹œ ν™•μΈν•©λ‹ˆλ‹€.

// src/drizzle/bananastand/schema.ts
import { mysqlTable, mysqlSchema, AnyMySqlColumn, primaryKey, int, varchar, mysqlEnum, decimal, date } from "drizzle-orm/mysql-core"
import { sql } from "drizzle-orm"

export const mySchema = mysqlSchema("bananastand");

export const bananas = mySchema.table("bananas", {
	// ...
});
import { mysqlTable, mysqlSchema, AnyMySqlColumn, primaryKey, int, varchar, text, mysqlEnum, decimal, index, foreignKey } from "drizzle-orm/mysql-core"
import { sql } from "drizzle-orm"

export const mySchema = mysqlSchema("pizzaplanet");

export const pizzas = mySchema.table("pizzas", {
	// ...
});

export const specialIngredients = mySchema.table("special_ingredients", {
	// ...
});

이제 mysqlSchema와 mySchema.table을 μ‚¬μš©ν•˜λŠ” κ²ƒμœΌλ‘œ μŠ€ν‚€λ§ˆ λ³€ν™˜μ„ μ„±κ³΅μ μœΌλ‘œ μ™„λ£Œν–ˆμŠ΅λ‹ˆλ‹€!

μŠ€ν‚€λ§ˆ μ‚¬μš©ν•˜κΈ°

이제 Drizzle ORM을 μ‚¬μš©ν•˜μ—¬ μŠ€ν‚€λ§ˆλ₯Ό 기반으둜 λ°μ΄ν„°λ² μ΄μŠ€μ—μ„œ 쿼리λ₯Ό μ‹€ν–‰ν•΄ λ΄…μ‹œλ‹€.

// src/main.ts
import { eq } from "drizzle-orm"
import { drizzle } from "drizzle-orm/mysql2"
import mysql from "mysql2/promise"

import { pizzas, specialIngredients } from "./drizzle/pizzaplanet/schema"
import { bananas } from "./drizzle/bananastand/schema"

const conn = await mysql.createConnection({
    host: "localhost",
    user: "root",
    password: "rootpassword",
    port: 3306,
})
const db = drizzle(conn)

const items = await db.select({
    pizzaName: pizzas.name,
    bananaType: bananas.type,
    bananaRipeness: bananas.ripenessLevel,
    amountNeeded: specialIngredients.amountNeeded,
}).from(pizzas)
    .innerJoin(specialIngredients, eq(pizzas.id, specialIngredients.pizzaId))
    .innerJoin(bananas, eq(specialIngredients.bananaId, bananas.id))

console.log(items)

await conn.end()

이제 main.tsλ₯Ό npx tsx src/main.ts둜 μ‹€ν–‰ν•˜λ©΄ λ‹€μŒκ³Ό 같은 κ²°κ³Όκ°€ 좜λ ₯λ©λ‹ˆλ‹€.

% npx tsx src/main.ts
Query: select `pizzas`.`name`, `bananas`.`type`, `bananas`.`ripeness_level`, `special_ingredients`.`amount_needed` from `pizzaplanet`.`pizzas` inner join `pizzaplanet`.`special_ingredients` on `pizzas`.`id` = `special_ingredients`.`pizza_id` inner join `bananastand`.`bananas` on `special_ingredients`.`banana_id` = `bananas`.`id`
[
  {
    pizzaName: 'Banana Deluxe',
    bananaType: 'Cavendish',
    bananaRipeness: 'Ripe',
    amountNeeded: 2
  },
  {
    pizzaName: 'Tropical Banana Special',
    bananaType: 'Plantain',
    bananaRipeness: 'Unripe',
    amountNeeded: 3
  }
]

쿼리 λ‘œκ·Έμ—μ„œ ν…Œμ΄λΈ” 이름에 μŠ€ν‚€λ§ˆ 이름이 ν¬ν•¨λœ 것을 확인할 수 μžˆμŠ΅λ‹ˆλ‹€, 예: `bananastand`.`bananas`

VoilΓ ! 이제 Drizzle ORMμ—μ„œ μŠ€ν‚€λ§ˆ 이름을 μ‚¬μš©ν•˜μ—¬ λ°μ΄ν„°λ² μ΄μŠ€μ— μ„±κ³΅μ μœΌλ‘œ 쿼리λ₯Ό μˆ˜ν–‰ν–ˆμŠ΅λ‹ˆλ‹€!

이 λ¬Έμ„œμ—μ„œ μž‘μ—…λœ μ½”λ“œλŠ” μ—¬κΈ°μ—μ„œ 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.