의존성 주입 周期의 이해: 단일 인스턴스, 범위 및 이전 상황에서의 상세 예시

개발자들은 의존성 주입을 사용할 때 서비스 인스턴스의 생명주기를 알고 있을 수 있지만, 많은 사람들이 그것이 어떻게 작동하는지 완전히 이해하지 못한다. 이러한 개념을 명확하게 하는 수많은 글을 온라인에서 찾을 수 있지만, 그들은 대개 이미 알고 있을 수도 있는 정의들을 반복하는 것일 뿐이다. 상세한 예를 들어 설명을 간단하게 해 보겠습니다.

의존성 주입을 구현할 때, 개발자들은 인스턴스의 생명주기를 결정하는 세 가지 옵션을 가지고 있다:

  1. 싱글톤
  2. 스코프드
  3. 트랜지엘

대부분의 개발자는 이러한 용어를 알고 있지만, 서비스의 수명에 어느 옵션을 선택해야 하는지에 대해 고민하는 사람들도 많다.

정의

설명을 시작하려면 이렇다:

  • 싱글톤라이프타임 서비스 인스턴스는 서비스 컨테이너에서 응용 프로그램당 한 번만 생성된다. 단일 인스턴스가 모든 후속 요청을 처리한다. 싱글톤 서비스는 응용 프로그램의 끝에서 제거된다 (즉, 응용 프로그램 재시작시).
  • 트랜지엘라이프타임 서비스 인스턴스는 서비스 컨테이너에서 요청당 생성된다. 트랜지엘 서비스는 요청의 끝에서 제거된다.
  • 스코프드라이프타임 서비스 인스턴스는 클라이언트 요청당 한 번만 생성된다. 트랜지엘 서비스는 요청의 끝에서 제거된다.

何时使用

  • 싱글톤 – 응용 프로그램의 전체 周期 동안 서비스의 단일 인스턴스를 사용하고자 하는 경우
  • 트랜잭ional – 클라이언트 요청 내에서 서비스의 개별 인스턴스를 사용하고자 하는 경우
  • 스코oped – 각 요청마다 서비스의 단일 인스턴스를 사용하고자 하는 경우

클라이언트 요청이 무엇인지? 간단하게 말하면, 사용자의 단추 클릭으로 응용 프로그램으로 API/REST 호출을 보내고 응답을 얻는 것입니다.

걱정 안 하고 예제로 이해해 봐요.

예제

먼저, 인터페이스 / 서비스와 클래스를 생성해 봐요:

C#

 

	// 아래 3개의 서비스를 선언하고자 합니다
		Public interface ISingleton
		Public interface ITransient 
		Public interface IScoped 

이제 위에 생성한 각 서비스 인터페이스 / 서비스에 대한 実装을 쓰 let’s 이해를 도울 수 있도록 callMeSingleton, callMeTransient, callMeScoped 변수를 更新하도록 해봐요.

  • 싱글톤 클래스 実装:
C#

 

class SingletonImplementation: ISingleton
{
	var callMeSingleton = ""

	// 다른 実装
	public SetSingleton(string value)
	{
		callMeSingleton = value;
	}
	// 다른 実装
}

  • 트랜잭ional 클래스 実装:
C#

 

class TransientImplementation: ITransient 
{
	var callMeTransient = ""
	
	// 다른 実装
	public SetTransient(string value)
	{
		callMeTransient = value;
	}
	// 다른 実装
}

  • 스코oped 클래스 実装:
C#

 

class ScopedImplementation: IScoped 
{
	var callMeScoped = ""
			
	// 다른 실행
	public SetScoped(string value)
	{
		callMeScoped = value;
	}
	// 다른 실행		
}

let’s 각 서비스 인스턴스의 周期을 결정하기 위해 (ConfigureServices) DI (依存성 주입)로 등록해봐요.

C#

 

services.AddSingleton<ISingleton, SingletonImplementation>();
services.AddTransient<ITransient , TransientImplementation>();
services.AddScoped<IScoped , ScopedImplementation>();

ClassA이나 ClassBClassC 중任何의 서비스를 이해하기 위해서는 각 서비스의 生命周期(생명周期)을 이해하는 것이 중요합니다.

  • ClassA:
C#

 

public class ClassA
{
	private ISingleton _singleton;
	생성자에서 다른 세 가지 서비스를 인스턴스화하는 것을 허용하는 것이 중요합니다.
	public ClassA(ISingleton singleton,
                  ITransient _transient,
                  IScoped _scoped)
	{
		_singleton = singleton;
	}
		
	public void UpdateSingletonFromClassA()
	{
		_singleton.SetSingleton("I am from ClassA");
	} 
	
  	public void UpdateTransientFromClassA()
    {
		_transient.SetTransient("I am from ClassA");
	} 
	
  	public void UpdateScopedFromClassA()
	{
		_scoped.SetScoped("I am from ClassA");
	}

	기타 구현 
}

  • ClassB:
C#

 

public class ClassB
{
	private ISingleton _singleton;
	생성자에서 다른 세 가지 서비스를 인스턴스화하는 것을 허용하는 것이 중요합니다.
	public ClassB(ISingleton singleton,
                  ITransient _transient,
                  IScoped _scoped)
	{
		_singleton = singleton;
	}
		
	public void UpdateSingletonFromClassB()
	{
		_singleton.SetSingleton("I am from ClassB");
	} 
	
  	public void UpdateTransientFromClassB()
    {
		_transient.SetTransient("I am from ClassB");
	} 
	
  	public void UpdateScopedFromClassB()
	{
		_scoped.SetScoped("I am from ClassB");
	}

	기타 구현 
}

  • ClassC:
C#

 

public class ClassC
{
	private ISingleton _singleton;
	생성자에서 다른 세 가지 서비스를 인스턴스화하는 것을 허용하는 것이 중요합니다.
	public ClassC(ISingleton singleton,
                  ITransient _transient,
                  IScoped _scoped)
	{
		_singleton = singleton;
	}
		
	public void UpdateSingletonFromClassC()
	{
		_singleton.SetSingleton("I am from ClassC");
	} 
	
  	public void UpdateTransientFromClassC()
    {
		_transient.SetTransient("I am from ClassC");
	} 
	
  	public void UpdateScopedFromClassC()
	{
		_scoped.SetScoped("I am from ClassC");
	}

	기타 구현 
}

분석

위에서 제시한 구현을 통해 각 生命周期의 결과와 행동을 analyze하겠습니다.

Singleton

모든 クラ스 (ClassA, ClassB, ClassC)는 应用程序의 전체 生命周期 동안 SingletonImplementation 클래스의 동일한 인스턴스를 사용합니다. 这意味着 SingletonImplementation 클래스의 속성, 필드, 및 operands는 호출 클래스에서 사용되는 모든 인스턴스 사이에 共有됩니다. 속성 또는 필드의 갱신은 이전 변경에 의해 재정의되ます.

예를 들어, 이전 코드에서, ClassA, ClassB, ClassC 모두 SingletonImplementation 서비스를 단일 인스턴스로 사용하고 SetSingleton를 호출하여 callMeSingleton 변수를 갱신합니다. 이 경우, 이 속성에 액세스하려는 모든 요청은 callMeSingleton 변수의 단일 값을 가지게 됩니다. 마지막으로 접근하여 갱신하는 클azz은 callMeSingleton 값을 盖정합니다.

  • ClassATransientImplementation 서비스를 사용할 때 다른 클azz과 같은 인스턴스를 가지게 됩니다.
  • ClassBTransientImplementation 서비스를 사용할 때 다른 클azz과 같은 인스턴스를 가지게 되ます.
  • ClassCTransientImplementation 서비스를 사용할 때 다른 클azz과 같은 인스턴스를 가지게 됩니다.

ClassA, ClassB, ClassCSingletonImplementation 클azz의 동일한 인스턴스를 갱신하면 callMeSingleton 값을 盖정합니다. 따라서 단일 인스턴스 서비스 구현에 속성을 설정하거나 갱신할 때 조심하십시오.

단일 인스턴스 서비스는 应用程序 末尾에 제거됩니다(즉, 应用程序 重启 시).

Transient

모든 클래스(ClassA, ClassB, ClassC)는 TransientImplementation 클래스의 개별 인스턴스를 사용합니다. 这意味着 하나의 클래스가 TransientImplementation 클래스의 속성, 필드, 或者 操作을 호출하면, 그 인스턴스의 独占적인 값들만 갱신 或者 재정의합니다. TransientImplementation 의 다른 인스턴스들과 속성 或者 필드의 갱신은 공유되지 않습니다.

이해해봐요:

  • ClassATransientImplementation 의 서비스의 自己的 인스턴스를 가지게 될 것입니다.
  • ClassBTransientImplementation 의 서비스의 自己的 인스턴스를 가지게 될 것입니다.
  • ClassCTransientImplementation 의 서비스의 自己的 인스턴스를 가지게 될 것입니다.

比如说 あなたが ClassD 를 가지고 있으며 ClassA, ClassB, ClassC 인스턴스에서 트랜지언트 서비스를 호출하고 있을 때, 각 클래스 인스턴스가 다른/별개의 인스턴스로 처리되며 각 클래스는 自己的 callMeTransient 의 값을 가질 것입니다. 下記의 인라인 코멘트를 ClassD に대해 읽어보세요.

C#

 

public ClassD
{
	// 다른 구현
		
    // 아래 各行의 코드는 클래스 A의 인스턴스에 대해 callMeTransient 의 값을 "I am from ClassA"로 갱신하며, Class B나 C에서 来的 任何时候의 호출이나 값이 변경되지 않음
    // 아래 各行의 코드는 Class B의 인스턴스에 대해 callMeTransient 의 값을 "I am from ClassB"로 갱신하며, Class A의 인스턴스 값을 덮어쓰지 않고, Class C로 来的 任何时候의 호출이나 값이 변경되지 않음
	ClassA.UpdateTransientFromClassA(); 		
       
    // 아래 各行의 코드는 Class C의 인스턴스에 대해 callMeTransient 의 값을 "I am from ClassC"로 갱신하며, Class A, B의 인스턴스 값을 덮어쓰지 않고, 다른 任何时候의 Class에서의 호출이나 값이 변경되지 않음
    // Transient services are disposed at the end of each request. Use Transient when you want a state less behavior within the request.
	ClassB.UpdateTransientFromClassB(); 
    
    // Scoped
    // All the classes (ClassA, ClassB, and ClassC) will be using single instances of ScopedImplementation class for each request. This means that calls for properties/fields/operations on ScopedImplementation class will happen on single instance with in the scope of request. Any updates of properties/fields will be shared among other classes.   
    ClassC.UpdateTransientFromClassC(); 

	// 다른 구현
}

Transient services are disposed at the end of each request. Use Transient when you want a state less behavior within the request.

Scoped

All the classes (ClassA, ClassB, and ClassC) will be using single instances of ScopedImplementation class for each request. This means that calls for properties/fields/operations on ScopedImplementation class will happen on single instance with in the scope of request. Any updates of properties/fields will be shared among other classes.   

Let’s understand:

  • ClassA – It will have its instance of service of TransientImplementation.
  • ClassBClassA와 같은 인스턴스의 TransientImplementation 서비스를 가지고 있을 것입니다.
  • ClassCClassAClassB가 같은 인스턴스의 TransientImplementation 서비스를 가지고 있을 것입니다.

let’s say あなたには ClassD가 있습니다. 이 경우 ClassA, ClassB, ClassC 인스턴스로부터 범위 서비스를 호출하고 있습니다. 각 クラス은 ScopedImplementation クラ스의 단일 인스턴스를 가질 것입니다. ClassD 아래의 인라인 コメン트를 읽으십시오.

C#

 

public class ClassD
{
  // 다른 구현
 
  // 아래 代码は callMeScoped 의 값을 "I am from ClassA" 로 ClassA 인스턴스에 대해 更新します
  // 그러나 이는 스코프 생명周期이기 때문에 单个 인스턴스 ScopedImplementation을 持っています
  // 그러다가 ClassB 또는 ClassCから 다음 호출이 오면 그 값을 재정의할 수 있습니다
	ClassA.UpdateScopedFromClassA();  
  
  // 아래 代码は single instance ScopedImplementation의 callMeScoped 값을 "I am from ClassB" 로 更新します 
  // 그리고 이는 ClassA 인스턴스의 callMeScoped 값도 재정의합니다. 
	ClassB.UpdateScopedFromClassB();
  
  // 이제 Class A가 ScopedImplementation에 대해 어떤 操作을 수행하면
  // 그들은 classB로 재정의된 最新의 속성/字段 값을 사용합니다.
	
  // 아래 代码は callMeScoped 값을 "I am from ClassC" 로 更新합니다
  // 그리고 이는 ClassA와 ClassB 인스턴스의 callMeScoped 값도 재정의합니다.
	ClassC.UpdateScopedFromClassC(); 
  // 이제 Class B나 Class A가 ScopedImplementation에 대해 어떤 操作을 수행하면

    // 다른 구현
}

// 다른 구현

Scoped services are disposed at the end of each request. Use Scoped when you want a stateless behavior between individual requests.

Trivia TimeThe lifecycle of a service can be overridden by a parent service where it gets initialized. Confused? Let me explain:

다음은 위 클래스와 같은 예제를 가져와 SingletonImplementation (싱글톤으로 구성됨)에서 ITransientIScoped 서비스를 초기화하는 것입니다. 이렇게 하면 이 서비스들의 생명주기를 싱글톤 생명주기로 변경하고 부모 서비스로 지정합니다. 이 경우 응용 프로그램에는 (예제에서 사용했던 3개의 서비스만 고려할 때)Transient 또는 Scoped 서비스가 없게 됩니다.

다음 코드의 줄을 읽어보십시오:

C#

 

public class SingletonImplementation: ISingleton
{
	// 서비스 초기화를 위한 생성자.
	private readonly ITransient _transient 
	private readonly IScoped _scoped 
		
	SingletonImplementation(ITransient transient, IScoped scoped)
	{
		_transient = transient;  
        // 이제 _transient는 Transient로 등록되었든가 상관없이 싱글톤 서비스로 동작합니다.
		_scoped = scoped;
        // 이제 scoped는 Scoped로 등록되었든가 상관없이 싱글톤 서비스로 동작합니다.
	}
    var callMeSingleton = ""
		
	// 기타 구현
}

요약

이 글이 주제를 이해하는 데 도움이 되었기를 바랍니다. 위의 문맥을 설정하고 직접 시도해 보시고, 그러면 다시 혼돈하실 일이 없을 것입니다. 싱글톤은 가장 쉽게 이해할 수 있습니다. 인스턴스를 한 번 만들면 응용 프로그램 전체의 응용 프로그램의 생명주기 동안 공유됩니다. 싱글톤과 유사하게, Scoped 인스턴스는 응용 프로그램 내의 요청 생명주기 동안 같은 동작을 보여줍니다. Transient는 완전히 상태 없는 것으로, 각 요청과 각 클래스 인스턴스는 서비스의 본인의 인스턴스를 보유합니다.

Source:
https://dzone.com/articles/understanding-the-dependency-injection-lifecycle