메시지 브로커를 인터페이스로 정의하면서 Redis 구현체를 생성했다. 이와 관련하여 내가 필요했던 기능은 생성자 주입과 싱글톤 객체였다.
Java와 다르게 Go는 생성자 개념이 없어 New라는 키워드를 붙인 임의의 생성자 함수를 직접 작성해야 한다. 또한, 스프링 프레임워크처럼 빈을 관리하는 컨테이너 개념도 없기 때문에 생성자 주입이나 싱글톤 기능도 직접 구현해야 한다.
설계에 따르면, 메시지 브로커의 구현체는 생성자 주입을 통해 다른 구현체(kafka 등)로 쉽게 변경될 수 있어야 하며, 시스템이 처음 실행될 때 딱 한 번 생성되어야 한다.
func 메시지_브로커_생성하기() 메시지_브로커_인터페이스 {
return new_메시지_브로커_구현체()
}
sync.Once
객체를 사용하여 싱글톤을 구현했다GetInstance
함수를 구현했다once.Do()
함수 내부에서 instance 변수를 초기화하면, 그 다음 getInstance
함수 호출 시에는 once.Do()
를 실행하지 않고 초기화된 instance 변수를 반환한다func GetInstance(ip, port string) 메시지_브로커_인터페이스 {
var once sync.Once
var instance 메시지_브로커_인터페이스
once.Do(func() {
instance = 메시지_브로커_생성하기()
})
return instance
}
3. 팩토리 메서드 내부에 getInstance
정의하기
- 객체를 생성하는 팩토리 메서드에 getInstance
를 정의하면 싱글톤으로 정의된 객체가 반환된다
GetInstance
내부에 팩토리 메서드 정의하기GetInstance
로 반환하는 것이 더 자연스럽다고 판단되어 기존 설계를 변경했다GetInstance
모두 객체를 생성한다는 점에서 같은 기능을 한다고 볼 수 있기 때문에 그에 따라 두 함수 중 하나만 가져가도 된다getInstance
는 싱글톤 객체를 생성하는 역할을 수행하므로 두 역할은 분리되어야 한다GetInstance
에서 선언된 인터페이스 타입의 instance 변수에 할당될 수 있다func new_메시지_브로커_구현체() 메시지_브로커_구현체_포인터 {
return &메시지_브로커_구현체
}
그러나, 여전히 팩토리 메서드와 getInstance
가 동일한 기능을 하고 있다는 점에서 위와 같은 설계를 가져갈 경우에 불필요한 레이어를 늘어난다는 생각이 든다. 따라서, 팀원과 협의 후에 역할 분리와 최소한의 레이어 중 어떤 게 더 효율적인지를 결정할 예정이다.
일반적으로 go의 생성자 함수는 필드의 값을 초기화하기 위해 포인터를 사용한다. 구조체 타입을 반환할 경우, 생성자 함수를 호출할 때 필드명을 함께 넘겨야 해서 다소 번거롭다. 이때, 필드명을 넘기지 않으면 컴파일 에러가 발생한다.
포인터 타입으로 반환
p := NewPerson("John", 30)
구조체 타입으로 반환
p := NewPerson(name: "John", age: 30)