μ΅κ·Ό νμ¬μμ, 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 ꡬμ±μ λ§λλλ€.
// 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μμ μ€ν€λ§ μ΄λ¦μ μ¬μ©νμ¬ λ°μ΄ν°λ² μ΄μ€μ μ±κ³΅μ μΌλ‘ 쿼리λ₯Ό μννμ΅λλ€!
μ΄ λ¬Έμμμ μμ λ μ½λλ μ¬κΈ°μμ νμΈν μ μμ΅λλ€.