Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
432 changes: 432 additions & 0 deletions chapter11/kilhyeonjun/README.md

Large diffs are not rendered by default.

56 changes: 56 additions & 0 deletions chapter11/kilhyeonjun/code/01-db-without-queue.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/**
* 01-db-without-queue.js
* 비동기 초기화 컴포넌트의 문제점
*
* 연결이 완료되기 전에 쿼리를 실행하면 에러 발생
*/

import { EventEmitter } from 'events'

// 비동기 초기화가 필요한 DB 모듈
class DB extends EventEmitter {
connected = false

connect() {
console.log('Connecting to database...')
// 연결 지연 시뮬레이션 (500ms)
setTimeout(() => {
this.connected = true
console.log('Database connected!')
this.emit('connected')
}, 500)
}

async query(queryString) {
if (!this.connected) {
throw new Error('Not connected yet')
}
console.log(`Query executed: ${queryString}`)
return { rows: [] }
}
}

export const db = new DB()

// 문제 시연
async function main() {
db.connect()

// 연결 완료 전에 쿼리 실행 시도 - 에러 발생!
try {
await db.query('SELECT * FROM users')
} catch (err) {
console.error('Error:', err.message)
}

// 600ms 후 (연결 완료 후) 쿼리 실행 - 성공
setTimeout(async () => {
try {
await db.query('SELECT * FROM users')
} catch (err) {
console.error('Error:', err.message)
}
}, 600)
}

main()
63 changes: 63 additions & 0 deletions chapter11/kilhyeonjun/code/02-db-local-init.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/**
* 02-db-local-init.js
* 로컬 초기화 확인 방식
*
* API 호출 시마다 초기화 여부를 확인하고 대기
*/

import { EventEmitter, once } from 'events'

class DB extends EventEmitter {
connected = false

connect() {
console.log('Connecting to database...')
setTimeout(() => {
this.connected = true
console.log('Database connected!')
this.emit('connected')
}, 500)
}

async query(queryString) {
if (!this.connected) {
throw new Error('Not connected yet')
}
console.log(`Query executed: ${queryString}`)
return { rows: [] }
}
}

export const db = new DB()

// 로컬 초기화 확인 방식
async function updateLastAccess() {
// 매번 연결 상태 확인
if (!db.connected) {
console.log('Waiting for connection...')
await once(db, 'connected')
}

await db.query(`INSERT (${Date.now()}) INTO "LastAccesses"`)
}

async function main() {
db.connect()

// 연결 전 호출 - 대기 후 실행
updateLastAccess()

// 600ms 후 호출 - 바로 실행
setTimeout(() => {
updateLastAccess()
}, 600)
}

main()

/**
* 단점:
* - 매번 초기화 상태를 확인해야 함
* - 코드 중복 발생
* - 여러 비동기 컴포넌트가 있으면 복잡해짐
*/
68 changes: 68 additions & 0 deletions chapter11/kilhyeonjun/code/03-db-delayed-startup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/**
* 03-db-delayed-startup.js
* 지연 시작 방식
*
* 모든 초기화가 완료된 후 애플리케이션 로직 실행
*/

import { EventEmitter, once } from 'events'

class DB extends EventEmitter {
connected = false

connect() {
console.log('Connecting to database...')
setTimeout(() => {
this.connected = true
console.log('Database connected!')
this.emit('connected')
}, 500)
}

async query(queryString) {
if (!this.connected) {
throw new Error('Not connected yet')
}
console.log(`Query executed: ${queryString}`)
return { rows: [] }
}
}

export const db = new DB()

// 초기화 함수
async function initialize() {
db.connect()
await once(db, 'connected')
console.log('All services initialized!')
}

// 비즈니스 로직 (초기화 상태 확인 불필요)
async function updateLastAccess() {
await db.query(`INSERT (${Date.now()}) INTO "LastAccesses"`)
}

async function main() {
// 먼저 모든 초기화 완료 대기
await initialize()

// 이후 비즈니스 로직 실행
updateLastAccess()

setTimeout(() => {
updateLastAccess()
}, 100)
}

main()

/**
* 장점:
* - 비즈니스 로직에서 초기화 상태 확인 불필요
* - 간단하고 명확함
*
* 단점:
* - 애플리케이션 시작 시간 지연
* - 비동기 컴포넌트의 재초기화는 고려하지 않음
* - 어떤 컴포넌트가 초기화가 필요한지 미리 알아야 함
*/
73 changes: 73 additions & 0 deletions chapter11/kilhyeonjun/code/04-db-preinitialization-queue.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/**
* 04-db-preinitialization-queue.js
* 사전 초기화 큐 방식
*
* 초기화 전 요청을 큐에 저장하고, 초기화 완료 후 일괄 실행
*/

import { EventEmitter } from 'events'

class DB extends EventEmitter {
connected = false
commandsQueue = []

connect() {
console.log('Connecting to database...')
setTimeout(() => {
this.connected = true
console.log('Database connected!')
this.emit('connected')

// 큐에 쌓인 명령들 실행
this.commandsQueue.forEach(command => command())
this.commandsQueue = []
}, 500)
}

async query(queryString) {
if (!this.connected) {
console.log(`Request queued: ${queryString}`)

// 명령을 큐에 저장하고 Promise 반환
return new Promise((resolve, reject) => {
const command = () => {
this.query(queryString)
.then(resolve, reject)
}
this.commandsQueue.push(command)
})
}

console.log(`Query executed: ${queryString}`)
return { rows: [] }
}
}

export const db = new DB()

async function main() {
db.connect()

// 연결 전 호출 - 큐에 저장됨
const promise1 = db.query('SELECT * FROM users')
const promise2 = db.query('SELECT * FROM orders')

// 연결 완료 후 자동 실행
const results = await Promise.all([promise1, promise2])
console.log('All queries completed!')

// 이후 호출 - 바로 실행
setTimeout(async () => {
await db.query('SELECT * FROM products')
}, 600)
}

main()

/**
* 장점:
* - 사용자 코드에서 초기화 상태 확인 불필요
* - 투명한 사용 가능 (초기화 상태를 몰라도 됨)
*
* 이 방식이 Mongoose, pg 등에서 사용됨
*/
108 changes: 108 additions & 0 deletions chapter11/kilhyeonjun/code/05-db-state-pattern.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/**
* 05-db-state-pattern.js
* 상태 패턴을 활용한 사전 초기화 큐
*
* QueuingState와 InitializedState로 분리하여 더 깔끔하게 구현
*/

import { EventEmitter } from 'events'

// 초기화가 필요한 함수 목록
const METHODS_REQUIRING_CONNECTION = ['query']

// 상태 비활성화를 위한 Symbol (이름 충돌 방지)
const deactivate = Symbol('deactivate')

// 대기 상태: 초기화 전
class QueuingState {
constructor(db) {
this.db = db
this.commandsQueue = []

// 연결이 필요한 함수들을 동적으로 생성
METHODS_REQUIRING_CONNECTION.forEach(methodName => {
this[methodName] = function (...args) {
console.log('Command queued:', methodName, args[0])
return new Promise((resolve, reject) => {
const command = () => {
db[methodName](...args)
.then(resolve, reject)
}
this.commandsQueue.push(command)
})
}
})
}

// 상태 비활성화 시 큐 실행
[deactivate]() {
console.log('Flushing queued commands...')
this.commandsQueue.forEach(command => command())
this.commandsQueue = []
}
}

// 초기화 완료 상태: 비즈니스 로직만 구현
class InitializedState {
async query(queryString) {
console.log(`Query executed: ${queryString}`)
return { rows: [] }
}
}

// DB 컨텍스트 클래스
class DB extends EventEmitter {
constructor() {
super()
this.state = new QueuingState(this)
}

// 현재 상태로 위임
async query(queryString) {
return this.state.query(queryString)
}

connect() {
console.log('Connecting to database...')
setTimeout(() => {
console.log('Database connected!')
this.emit('connected')

// 상태 전환
const oldState = this.state
this.state = new InitializedState()

// 이전 상태 비활성화 (큐 실행)
if (oldState[deactivate]) {
oldState[deactivate]()
}
}, 500)
}
}

export const db = new DB()

async function main() {
db.connect()

// 연결 전 호출 - QueuingState가 처리
const promise1 = db.query('SELECT * FROM users')
const promise2 = db.query('SELECT * FROM orders')

const results = await Promise.all([promise1, promise2])
console.log('All queries completed!')

// 연결 후 호출 - InitializedState가 처리
setTimeout(async () => {
await db.query('SELECT * FROM products')
}, 600)
}

main()

/**
* 장점:
* - 상태별 로직 분리로 코드 가독성 향상
* - InitializedState는 순수 비즈니스 로직만 포함
* - 확장이 용이 (새로운 상태 추가 가능)
*/
Loading