본문 바로가기
Javascript/Node.js

Wiring Pattern - 생성자 디자인 패턴

by v8rus 2022. 2. 3.

모듈 와이어링 (Wiring)

모든 어플리케이션은 여러 컴포넌트를 연결한 결과임
연결 방식은 프로젝트의 유지보수 및 성공에 중요함

컴포넌트 A가 주어진 기능을 수행하기 위해 컴포넌트 B가 필요. = A는 B에 종속적이다. = B는 A의 종속성

싱글톤 패턴과 종속성 주입 패턴을 사용하는 서로 다른 두 가지 접근방법을 사용해 종속성 모델링 하는 방법을 봅시다.

 

 

1. 싱글톤 종속성

두 모듈을 서로 연결하는 간단한 방법으로 모듈 시스템을 활용
종속성들을 묶는 방식은 사실상 싱글톤
Ex) DB 연결을 위한 싱글톤 인스턴스, 간단한 블로깅 어플 구현

// db.js
import { dirname, join } from 'path';
import { fileURLToPath } from 'url';
import sqlite3 from 'sqlite3';

const __dirname = dirname(fileURLToPath(import.meta.url));
export const db = new sqlite3.Database(
  join(__dirname, 'data.sqlite')
)

Sqlite는 모든 데이터를 하나의 로컬파일에 보관하는 DB. 여기서는 'data.sqlite'라는 파일 사용
위의 코드는 새로운 인스턴스를 만들고 DB 연결 객체를 싱글톤으로 내보냄

 

// blog.js
import { promisify } from 'util';
import { db } from './db.js';

const dbRun = promisify(db.run.bind(db));
const dbAll = promisify(db.all.bind(db));

export class Blog {
  initialize () {
    const initQuery = 'CREATE TABLE IF NOT EXISTS posts (
      id TEXT PRIMARY KEY,
      title TEXT NOT NULL,
      content TEXT,
      created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP'
    );

    return dbRun(initQuery);
  }

  createPost (id, title, content, createdAt) {
    return dbRun ('INSERT INTO posts VALUES (?,?,?,?)', id, title, content, createdAt);
  }

  getAllPosts () {
    return dbAll('SELECT * FROM posts ORDER BY created_at DESC');
  }
}
// index.js
import { Blog } from './blog.js';

async function main () {
  const blog = new Blog();
  await blog.initialize();
  const posts = await blog.getAllPosts();
  if (posts.length === 0) {
    console.log('No posts available. ~~~');
  }

  for (const post of posts) {
    console.log(post.title);
    console.log('-'.repeat(post.title.length));
    console.log('Published on ${new Date(post.created_at).toISOString()}');
    console.log(post.content);
  }
}

main().catch(console.error);

포스트들을 간단하게 출력하는 코드

싱글톤 패턴을 활용하여 DB 인스턴스 전달
어플에서 상태 저장 종속성 (stateful dependencies)를 관리하는 방식으로 많이 사용함
충분하지 않은 상황이 있긴함....ㅜ
가장 간단하면서 즉각적이며 가독성이 높은 해결책

단점으로 강한 결합
다만, 테스트 중에 DB 모조를 가지고 동작하려면? (다른 방식을 채택해야 할 경우)
  => 싱글톤은 해결책에 장애물이 됨

여기다가 if 문 등을 도입, 환경 조건 또는 설정에 따라 다른 구현을 선택하도록 => DI

 

 

2. 종속성 주입 (DI = Dependency Injection)

싱글톤이 항상 성공을 보장하는것은 아님, 사용이 쉽고 실용적이지만 강한 결합을 만듦
1. 예시에서 DB 모듈과 blog 모듈이 강하게 결합됨
DB.js 모듈 없이 blog 동작 불가 & 필요한 경우 다른  DB 모듈을 사용할 수 없음 
=> 종속성 주입 패턴을 활용하면 긴말한 결합을 간단히 수정

"컴포넌트들의 종속성들이 종종 인젝터라고 하는 외부 요소에 의해 공급되는 패턴"

인젝터는 다른 컴포넌트를 초기화, 종속성들을 함께 연결
모든 모듈들의 연결을 중앙 집중화하여 보다 복잡한 전역 컨테이너일수도 있음...
장점 : 향상된 디커플링, 특히 상태 저장 인스턴스에 대한 종속성을 가진 모듈들에 유효
DI 사용시 각 종속성이 모듈에 하드코딩되는 대신 외부에서 주입됨
최소한의 노력으로 다른 컨텍스트에서 재사용가능

인스턴스를 조회하고 서비스에 전달(인젝터 담당)

1. 에서 만든 blog를 리팩토링 해 봅시다.

// blog.js
import { promisify } from 'util';

export class Blog {
  constructor (db) {
    this.db = db;
    this.dbrun = promisify(db.run.bind(db));
    this.dbAll = promisify(db.all.bind(db));
  }

  initialize() {
    const initQuery = 'CREATE TABLE IF NOT EXISTS posts (
      // ...
    )';

    return this.dbRun(initQuery);
  }
  
  createPost( id, title, content, createdAt) {
    return this.dbRun('INSERT INTO posts VALUES(?, ?, ?, ?)', id, title, content, createdAt)
  }

  getAllPosts () {
    return this.dbAll(
      'SELECT * FROM posts ORDER BY created_at DESC'
    )
  }
}

두가지의 차이점이 있쥬?

  1. 더 이상 DB 모듈을 임포트 하지 않음
  2. Blog 클래스 생성자는 db 인자로 취급

새로운 생성자의 인자 db는 Blog 클래스의 사용자 컴포넌트로부터 실행 시 제공되는 종속성
JS는 추상 인터페이스 표현 방법이 없음, 제공될 종속성은 dbrun(), dbAll() 함수를 직접 구현해야 함(덕타이핑)

위의 db.js 재 작성, 싱글톤 패턴 제거, 재사용 및 설정 가능한 구현체 만들어봅시다.

// db.js
import sqlite3 from 'sqlite3';

export function createDb(dbFile) {
  return new sqlite3.Database(dbFile);
};

createDb() 라는 팩토리 함수 제공, 실행시 데이터베이스의 새 인스턴스를 만들 수 있음
필요 시 다른 파일에 데이터를 쓸 수 있는 독립적인 인스턴스를 만들기 위해 생성 시 데이터베이스 파일 경로를 전달

 

이제 인젝터까지 고려해봅시다.

import { dirname, join } from 'path';
import { fileURLToPath } from 'url';
import { Blog } from './blog.js';
import { createDb } from './db.js';

const __dirname = dirname(fileURLToPath(import.meta.url));

async function main () {
  const db = createDb(join())dirname, 'data.sqlite'));    // 팩토리 함수를 사용해 데이터베이스 종속성(db)을 생성
  cosnt blog = new Blog(db);      // Blog 클래스 인스턴스화 할대 데이터베이스 인스턴스를 명시적으로 "주입"
  await blog.initialize();
  const posts = await blog.getAllPosts();
  if (posts.length === 0) {
    console.log('No posts available. ~~~');
  }

  for (const post of posts) {
    console.log(post.title);
    console.log('-'.repeat(post.title.length));
    console.log('Published on ${new Date(post.created_at).toISOString()}');
    console.log(post.content);
  }
}

main().catch(console.error);

blog 모듈은 실제 DB 구현에서 완전히 분리되어 설정이 가능, 구성요소들이 격리된 상태로 테스트 용이하게 됨

종속성 주입 패턴의 디커플링 및 재사용성의 대가가 있음
코딩 시 종속성을 해결 할 수 없기 때문에 시스템을 구성하는 여러 컴포넌간의 관계를 이해하기 더 어려워짐, 대규모 어플에서 두드러짐
blog 인스턴스에서 함수를 호출하기 전에 DB 인스턴스가 생성되어있는지 확인해야 함
종속성의 주입 순서가 바르게 되기 위해 종속성 그래프를 직접 수동으로 작성해야 함

댓글