일단 싱글톤 객체가 뭘까? 싱글톤 클래스 인스턴스? 싱글톤 패턴?
나는 MVC 패턴밖에 모른단 말야.. 그래서 일단 두 패턴을 비교해보도록 하자....
새 프로젝트 서버 구조에 대해서 얘기하는데, 팀장님이 말씀하시길 MVC 패턴은 이제 좀 낡은 방식이라고 하셨다. 그동안 프로젝트 할 때는 MVC 패턴만 쭉 써왔는데..! 객체 지향과 관련이 있다는데, 내가 더 찾아보고 정리하려고 한다.
디자인 패턴은 개발 규칙이라고 보면 된다!
일단 나름 잘 알고있는(있다고 생각했던) MVC 패턴은 애플리케이션을 세 가지 역할로 구분한 개발 방법이다. 컨트롤러, 모델, 뷰 이 세 가지의 기본 골격을 갖고 있다.
즉 컨트롤러는 이벤트 핸들러로 사용자의 요청(이벤트)에 맞는 데이터를 모델에 요청하고, 뷰에 전달하는 역할
모델은 데이터베이스의 테이블로서, 뷰와 컨트롤러에 대해선 모르고 데이터에 직접적으로 관련된 로직만 가지고 있다.
뷰는 모델을 적합한 형태로 랜더링한 html 페이지를 말한다.
각자 역할에 맞게 구분되어 있기 때문에 유지보수가 편리하다.
엄.. 그렇구나
어떤 분은 프린터 1대를 여러 사람이 함께 사용하는 경우를 예시로 들었다.
프린터를 사용하려는 사람들이 프린터를 각자 생성해서 사용하는 것은 불가능하다. 마법사도 아니고
어쨌든 여러 사람이 1대의 프린터를 공유해서 사용하는 것과 마찬가지로, 우리 프로그램 내에서 단 1개만 존재해야하는 객체가 있다면, 이 단 하나의 객체를 프로그램 내의 여러 부분에서 호출해서 사용해야 한다.
만약 프로그램 내에서 발생하는 이벤트들을 스케쥴링하고 처리하는 객체가 있다고 할 때, 이 이벤트들은 모두 하나의 같은 스케쥴링 큐에 들어가서 처리되어야 한다. 이벤트 발생 때마다 스케쥴링 큐를 따로따로 생성하면 되겠는가?
그래서 싱글톤 패턴은, 객체가 프로그램 내부에서 단 한개만 생성됨을 보장하고, 멀티스레드에서 이 객체를 공유하면서 동시에 접근하는 경우에 발생하는 동시성 문제도 해결해준다고 한다.
--
싱글톤 패턴은 자바에서 많이 사용한다고 한다!
싱글톤 패턴은 말하자면 어떤 클래스가 최초 한 번만 메모리를 할당하고(static), 그 메모리에 객체를 만들어 사용하는 디자인 패턴이다.
생성자의 호출이 반복적으로 이뤄져도, 실제로 생성되는 객체는 최초 생성된 객체를 반환해주는 것이란 거다.
이런 식으로 한번의 new로 객체 생성 후 재사용이 가능하기 때문에 메모리 낭비를 방지할 수 있다. 싱글톤으로 생성된 객체는 전역 인스턴스이기 때문에 다른 클래스의 인스턴스들, 즉 다른 객체와 공유가 용이하다.
따라서 DBCP(DataBase Connection Pool)처럼 공통된 객체를 여러개 생성해서 사용해야하는 상황에서 많이 사용한다고 한다. 이번에 우리 프로젝트에서도 DB 풀을 클래스로 만들어서 커스텀 메소드들을 적용시키기로 했다!
안드로이드 앱 같은 경우 각 액티비티나 클래스별로 주요 클래스들을 일일이 전달하기가 번거로와서 싱글톤 클래스를 만들어서 어디서나 접근하도록 쉽게 설계하는 것이라고 한다.
계속 최초의 객체를 사용하기 때문에, 두 번째 이용부터는 객체 로딩 시간이 현저히 줄어들어 성능이 좋아진다고 함!
문제점도 있다. 싱글톤으로 만든 객체의 역할이 복잡해지거나 너무 많은 데이터를 공유시킬 경우에 이 싱글톤 객체를 사용하는 다른 객체들간의 결합도가 높아져서 객체 지향 설계 원칙에 어긋난다! SOLID 법칙 중 계방 폐쇄(Open Closed Principle) 원칙! 따라서 수정이 어려워지고 테스트하기도 어려워진다.
또 싱글톤 객체를 수정할 경우, 이 객체를 사용하는 곳에서 사이드 이팩트가 발생할 확률이 높아서 멀티스레드 환경에서 동기화 처리 문제 등이 생긴다고 한다. 동기화 처리를 안하게 된다면.. 인스턴스가 두개가 생성된다든지 한댄다..
일단 DB 연결할 때~
보통은 pool을 만들어서 사용하게 되는데, 커넥션 객체를 만들어 쓰는것은(createConnection) 단일 연결 방식이기 때문에 요청이 있을때마다 connect와 destroy로 연결과 제거를 불필요하게 반복해줘야 한다.
그래서! 정해진 갯수의 연결을 미리 생성해서 저장하는 풀을 만든다.(createPool) 풀로 만들어진 연결은 수동으로 제거할 필요가 없고 쉽게 재사용도 가능하다! 또 연결 제한에 걸린 경우에는 연결이 가능할 때까지 기다려주기도 하니.. 풀로 만드는게 훨씬 생산적이다.
암튼 서론이 길었다
const mysql = require('mysql2')
const poolPromise = mysql.createPool({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_DATABASE,
})
암튼간에 이 pool객체는, 여러개가 있어도 될까? 당연히 안되겠죠? 하나의 pool 객체에서 연결을 뽑아 쓸건디.. 그래서 여기서 튀어나오는게 싱글톤이다. 이 pool을 하나만 두고 싶기 때문이다!
class DB {
getPool = () => {
return new Promise((resolve, reject) => {
resolve(poolPromise)
})
}
// getConnection.. query.. 등등 커스텀 가능
}
이건 대충 쓴거고 여튼 이런식으로 클래스를 사용해서 재사용이 가능하도록 한다! 안에 try..catch문도 쓰고, 에러도 핸들링하고 등등등.. 좀 더 제대로 된 예시를 들고 오고 싶은데 그러려면 좀 더 공부가 필요하겠고.. 사실..귀찮다. 하하