ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Design Pattern] Singleton Pattern, Nest.js
    Backend/Design Pattern 2024. 7. 18. 18:32
    728x90

    Singleton Pattern이란? 

    싱글톤 패턴은 특정 클래스의 인스턴스가 하나만 생성되고, 이를 통해 모든 클라이언트가 동일한 인스턴스를 공유하도록 보장하는 디자인 패턴입니다. 이 패턴은 전역 변수처럼 어디서든 접근할 수 있는 객체를 제공하며, 이를 통해 리소스를 절약하고 데이터 일관성을 유지할 수 있습니다.

     

    어떤 문제가 있을까?

    1. 구현 코드가 많이 들어갑니다.

    인스턴스 생성 여부를 제어하고 멀티쓰레드 환경에서 동기화 처리 등을 해야 하므로 구현이 복잡해집니다.

     

    2. DIP(Dependency Inversion Principle, 의존성 역전 원칙) 위반

    의존성 역전 원칙은 고수준 모듈과 저수준 모듈이 추상화된 인터페이스에 의존해야 한다고 하지만, 싱글톤 패턴은 클라이언트 코드가 구체 클래스에 직접 의존하게 만듭니다.

     

    3. OCP(Open-Closed Principle, 개방-폐쇄 원칙) 위반

    개방-폐쇄 원칙은 클래스가 확장에 열려 있고 수정에 닫혀 있어야 한다고 하지만, 싱글톤 패턴은 클라이언트 코드가 싱글톤 클래스를 직접 참조하므로 새로운 기능을 추가하거나 변경할 때 클라이언트 코드를 수정해야 합니다.

     

    4. SRP(Single Responsibility Principle, 단일 책임 원칙) 위반

    단일 책임 원칙은 하나의 클래스는 하나의 책임만 가져야 한다는 원칙입니다. 즉, 클래스는 변경의 이유가 하나만 있어야 합니다.

    싱글톤 패턴은 한 번에 두 가지 문제를 해결하려고 합니다:

    1) 클래스의 인스턴스가 하나만 존재하도록 제한합니다.

    2) 해당 인스턴스에 대한 전역 접근을 제공합니다.

    이로 인해 클래스가 인스턴스 관리와 비즈니스 로직이라는 두 가지 책임을 가지게 되어 단일 책임 원칙을 위반하게 됩니다.

     

    5. 테스트하기 어렵습니다.

    싱글톤 인스턴스를 여러 테스트에서 공유하므로 테스트 간에 상태가 공유되어 예상치 못한 문제가 발생할 수 있습니다.

     

    6. 내부 속성을 변경하거나 초기화하기 어렵습니다.

    싱글톤 인스턴스는 애플리케이션 시작 시점에 초기화되므로 이후 상태를 변경하거나 초기화하기 어렵습니다.

     

    7. private 생성자로 자식 클래스를 만들기 어렵습니다.

    싱글톤 클래스의 생성자가 private이므로 이를 상속하여 자식 클래스를 만들기 어렵습니다.

     

    8. 유연성이 떨어집니다.

    싱글톤 패턴은 객체 생성 방식을 고정하므로 다른 객체 생성 패턴보다 유연성이 떨어집니다.

     

    9. 따라서 안티패턴으로 불리기도 합니다.

    전역 상태를 관리하고, 테스트 및 유지보수를 어렵게 만든다는 이유로 싱글톤 패턴을 안티패턴으로 간주하기도 합니다.

     

    문제점이 많은데 왜 싱글톤 패턴을 사용하는가?

    1. 리소스 절약: 데이터베이스 연결, 파일 시스템 접근 등 비용이 큰 리소스를 하나의 인스턴스로 공유할 수 있습니다.

    2. 글로벌 접근: 애플리케이션 내에서 전역적으로 접근 가능한 객체를 제공하여, 상태를 공유하거나 설정 값을 관리하기 용이합니다.

    3. 일관성 보장: 동일한 인스턴스를 사용하므로 데이터 일관성을 유지할 수 있습니다.

     

    Singleton Pattern Example

    class Singleton {
      private static instance: Singleton;
    
      private constructor() {}
    
      public static getInstance(): Singleton {
        if (!Singleton.instance) {
          Singleton.instance = new Singleton();
        }
        return Singleton.instance;
      }
    
      // 추가 메서드들...
    }
    
    const singleton1 = Singleton.getInstance();
    const singleton2 = Singleton.getInstance();
    
    // 인스턴스가 1개인지 확인
    if (singleton1 === singleton2) {
      console.log('Singleton works, both variables contain the same instance.');
    } else {
      console.log('Singleton failed, variables contain different instances.');
    }

     

    Nest.js에서의 사용

    Nest.js에서는 개발자가 인스턴스를 직접 생성하지 않고, 모듈 주입 과정을 통해 싱글톤 인스턴스를 자동으로 생성합니다. 싱글톤 인스턴스는 Moudle과 Provider Scope로 생성되며, Nest.js는 이를 통해 전역적으로 관리합니다.

    Nest.js는 싱글톤 패턴의 문제점을 인터페이스와 의존성 주입(DI)을 통해 해결합니다. DI 컨테이너를 활용하여 싱글톤 객체의 상태 관리, 테스트 용이성, 모듈 간 유연성을 보장하며, 이를 통해 클라이언트 코드의 구체 클래스 의존성을 줄이고, 유지보수와 확장을 용이하게 합니다.

    의존성 주입(DI: Dependency Injection) : 하나의 객체가 다른 객체의 의존성을 제공하는 기술을 말합니다. 코드의 결합도를 느슨하게 하도록하고, 객체의 생성과 사용을 분리하여 코드의 가독성과 재사용성을 높입니다.

     

    nestjs 싱글톤 패턴의 특징

    • 공유된 자원을 사용하기 위해, 하나의 인스턴스를 여러 곳에 사용했을 경우 동시성 이슈와 데드락 같은 문제를 방지할 수 있습니다.
    • 인스턴스를 필요할 때 마다 생성하고 필요없을 때, 제거 하면서 까지 메모리 관리를 신경 쓰지 않아도 됩니다.
    • 하나의 인스턴스를 global 하게 사용할 수도 집약적으로 사용할 수 있습니다. global 하게 사용한다면 A클래스에서 B클래스에서 사용하는 인스턴스에 접근을 방지 하기 위해 private 을 선언해주는 방법도 있습니다.

    싱글톤을 구현하기위해 @Injectable(), @Module()데코레이터를 선언하면, 해당 인스턴스를 nest 내장 IOC container 가 관리합니다.

    데코레이터가 달린 클래스는 타입스크립트가 컴파일시 메타데이터로 어떤 서비스에 의존하고 있는지 명시를 해준다. 그러면 nestjs 가 어떤 의존관계가 있는지 알 수 있습니다.

    즉, nest 내부의 IOC container 가 @Injectable(), @Module() 데코레이터 클래스를 싱글톤으로 생성하여 DI의 대상으로 관리합니다.

    IOC(inversion of control) : 인스턴스의 생성 및 할당과 해제를 개발자가 하는 것이 아니고, 프레임워크가 이를 맡아주는 것을 말합니다.

     

    싱글톤 패턴 사용 시에 어떤 점을 주의해야 할까?

    1. 무상태(stateless)로 설계

    싱글턴 객체는 여러 클라이언트가 공유하기 때문에 상태를 유지하지 않아야 합니다. 특정 클라이언트에 의존적인 필드나 값을 변경할 수 있는 필드가 있으면 안됩니다. 가급적 읽기만 가능한 형태로 설계해야 합니다.

     

    2. 비동기 환경에서의 주의사항

    NestJS는 비동기 요청을 처리하는 Node.js 환경에서 동작하므로, 싱글톤 객체가 비동기 요청 간에 상태를 공유할 수 있습니다. 비동기 요청 간의 상태 공유 문제를 방지하기 위해 서비스는 상태를 저장하지 않도록 설계해야 합니다.

     

    3. 테스트 환경에서의 주의사항

    싱글톤 인스턴스는 테스트 간에 공유될 수 있으므로, 테스트 환경에서는 DI 컨테이너를 통해 독립적인 인스턴스를 생성하여 사용해야 합니다. 이를 통해 테스트 간의 상태 공유 문제를 방지할 수 있습니다.

     

    4. 모듈 간 의존성 관리

    싱글톤 서비스가 여러 모듈에 걸쳐 사용될 때, 특정 모듈의 상태 변화가 다른 모듈에 영향을 미치지 않도록 주의해야 합니다. 모듈 간 의존성을 명확히 정의하고, 필요한 경우 모듈별로 독립적인 서비스를 사용할 수 있도록 설계해야 합니다.

     

    5. 요청 범위(Request Scope) 활용

    상태를 유지해야 하는 경우, 싱글톤 대신 요청 범위(Request 스코프)로 서비스를 정의하여 각 요청마다 새로운 인스턴스를 생성하도록 합니다. 이를 통해 각 요청 간의 상태 공유 문제를 방지할 수 있습니다.

     

    6. 외부 저장소 활용

    상태를 유지해야 하는 데이터를 데이터베이스나 분산 캐시와 같은 외부 저장소에 저장하여 여러 인스턴스 간의 일관성을 유지합니다. 이를 통해 상태 관리의 복잡성을 줄이고, 애플리케이션의 안정성을 높일 수 있습니다.

     

     

    728x90
Designed by Tistory.