Notice
Recent Posts
Recent Comments
Link
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

개발을 하기는 합니다만

[Angular][04] 서비스와 의존성 주입 본문

Angular/Angular Official Guide

[Angular][04] 서비스와 의존성 주입

jaeyoung-lee 2020. 3. 15. 17:21

본 글은 앵귤러 공식 홈페이지(http://angular.io)의 DOC 항목을 한국어로 (공부 목적으로) 요약(?)정리(?)한 글임을 밝힙니다.


서비스(Servicies)와 의존성 주입(DI: Dependency Injection)

서비스는 앱이 필요로하는 상수, 함수, 기능을 포괄하는 단위입니다. 서비스는 대게  간결하고 기능적으로 잘 정의된 클래스로, 특정 작업을 매우 잘 수행하여야합니다.

 

앵귤러는 모듈화와 재사용성을 위해 컴포넌트와 클래스를 구분합니다. 컴포넌트의 뷰 관련 기능과 다른 프로세싱 과정을 분리하면, 컴포넌트 클래스를 간결하고 효율적으로 만들 수 있습니다.

 

이상적으로, 컴포넌트에는 사용자의 행동과 동작에 관한 기능만 두는 것이 좋습니다. 컴포넌트는 템플릿이 제공하는 뷰와 모델을 포함하는 애플리케이션 로직을 중재하는 역할(즉, 데이터 바인딩을 위한 프로퍼티와 메소드를 제공하는 것)만 하는 것이 좋습니다.

 

컴포넌트는 서버로부터 데이터를 받아오거나, 사용자 인풋을 검증하거나, 콘솔에 직접적으로 로그를 입력하는 등의 작업을 서비스에 위임할 수 있습니다. 이러한 작업을 "주입가능한(injectable)"한 서비스 클래스 내에 정의함으로써, 이러한 작업을 어떠한 컴포넌트에서든 사용할 수 있게 됩니다. 또한, 동일한 서비스를 다양한 환경(예를 들어 개발/배포 환경)에 맞게 다른 제공자(providers)를 통해 주입하면 앱을 더욱 유연하게 만들 수 있습니다.

 

앵귤러가 이러한 방법을 강제하는 것은 아닙니다. 앵귤러는 개발자가 이러한 방법을 따름으로써 앱의 로직을 서비스에 편리하게 팩토라이징하고 의존성 주입을 통하여 다양한 컴포넌트가 사용가능하게 하도록 권장할 뿐입이다.

 

서비스 예제

다음의 예제는 브라우저의 콘솔에 로그를 입력하는 서비스 클래스입니다.

//src/app/logger.service.ts (class)

export class Logger {
  log(msg: any)   { console.log(msg); }
  error(msg: any) { console.error(msg); }
  warn(msg: any)  { console.warn(msg); }
}

서비스를 다른 서비스들에게 의존적으로 만들 수도 있습니다. 가령, HeroService는 Logger 서비스에 의존하는 동시에 heroes 배열을 받아오기 위해 BackendService에도 의존할 수 있습니다. 그리고 BackendService는 서버로부터 비동기적으로 heroes를 받아오기 위하여 HttpClient에 의존할 수 있습니다.

 

추가적으로 이 관계를 다음과 같이 정리할 수 있습니다.

  • HeroService는 Logger와 BackendService를 의존성으로 사용합니다.
  • BackendService는 HttpService를 의존성으로 사용합니다.
//src/app/hero.service.ts (class)

export class HeroService {
  private heroes: Hero[] = [];

  constructor(
    private backend: BackendService,
    private logger: Logger) { }

  getHeroes() {
    this.backend.getAll(Hero).then( (heroes: Hero[]) => {
      this.logger.log(`Fetched ${heroes.length} heroes.`);
      this.heroes.push(...heroes); // fill cache
    });
    return this.heroes;
  }
}

 

의존성 주입(Dependency Injection, DI)

컴포넌트와 서비스

의존성 주입은 앵귤러 프레임워크를 서비스나 기타 필요한 정보, 기능과 묶어, 어느 컴포넌트에서나 사용 가능하게 해줍니다. 컴포넌트는 서비스를 소비(활용)한다고 할 수 있는데, 이는 컴포넌트가 특정 서비스 클래스에 접근할 수 있도록 함으로써 서비스를 주입(inject)한다고도 할 수 있습니다.

 

앵귤러에서 클래스를 서비스로 정의하기 위해서 @Injectable 데코레이터를 활용하여 해당 서비스 클래스를 다른 컴포넌트에 의존성으로써 주입할 수 있다는 것을 메타데이터로써 제공하여 앵귤러에게 알려줍니다. @Injectable()은 컴포넌트 이외에도 다른 서비스, 파이프 혹은 NgModule 클래스에도 사용되어 해당 클래스가 의존성을 가지고 있음을 알려줄 수 있습니다.

 

  • "인젝터(injector)"가 핵심 기능입니다. 앵귤러는 부트스트랩과정에서 애플리케이션 전역에서 동장하는 인젝터를 생성하며, (그럴 경우는 많이 없겠지만) 필요할 경우 추가적인 인젝터를 생성합니다. 그렇기에 인젝터를 직접 생성할 필요는 없습니다.
  • 인젝터는 "의존성(dependency)"을 생성하고, 의존성 인스턴스가 재사용될 것을 대비하여 컨테이너에 담아 관리합니다.
  • "프로바이더(provider)"는 인젝터에게 어떻게 의존성을 생성하는지 알려주는 객체입니다.

앱에서 필요한 의존성의 경우, 앱의 인젝터에 해당 프로바이더를 등록하여, 인젝터가 해당 프로바이더를 사용하여 해당 의존성의 인스턴스를 생성할 수 있도록 해야합니다. 서비스의 경우, 서비스 클래스가 자체적으로 프로바이터의 역할을 합니다.

 

의존성이 꼭 서비스일 필요는 없습니다. 함수나 특정한 값도 의존성이 될 수 있습니다.
[노트]
서비스 이외로 API url, 키, 다양한 토큰들이 의존성으로 많이 등록됩니다.
개발, 테스트, 배포 환경에서 다른 값을 가지는 경우가 많아, 유연한 관리가 필요하기 때문입니다.

 

앵귤러가 새로운 컴포넌트 클래스의 객체를 생성할 때, 앵귤러는 생성자의 인자 타입(constructor parameter type)을 확인하며 해당 컴포넌트가 필요로하는 서비스나 기타 의존성을 찾아냅니다. 예를 들어, 아래 코드에서 HeroListComponent의 생성자는 HeroService가 의존성으로 주입되어야 한다는 것을 알려줍니다.

//src/app/hero-list.component.ts (생성자)

constructor(private service: HeroService) { }

 

앵귤러가 특정 컴포넌트가 특정 서비스에 의존한다는 것을 알아내면, 먼저 인젝터가 해당 서비스의 인스턴스를 이미 가지고 있는지 확인합니다. 만약 요구되는 서비스 인스턴스가 없다면, 인젝터는 등록된 프로바이더를 통해 서비스 인스턴스를 생성하며, 앵귤러에 해당 서비스를 보내주기 전에 인젝터에 해당 서비스를 먼저 추가합니다.

 

요구되는 서비스가 모두 준비되었다면, 앵귤러는 해당 컴포넌트의 생성자를 호출하고 생정자의 인자로 등록된 의존성을 전달합니다.

 

HeroService 의존성의 주입과정은 다음과 같습니다.

인젝터의 다양한 서비스 중 생성자 인자에 명시된 HeroService만 컴포넌트에 전달됩니다.

 

서비스 프로바이더 등록

 

서비스를 사용하기 위해서는 어딘가에 적어도 한 개의 프로바이더를 등록해야합니다. 프로바이더는 서비스 자체의 메타데이터의 일부(@Injectable() 데코레이터 사용)일 수도 있습니다, 이러한 방식은 해당 서비스를 앱 전역에서 사용가능하게 합니다. 또한 프로바이더를 특정 모듈이나 컴포넌트에 등록(@NgModule이나 @Component 메타데이터 내부에)할 수도 있습니다. 

 

@Injectable() 데코레이터 사용

Angular CLI가 제공하는 ng generate service 명령은 프로바이더 메타데이터를 @Injectable() 데코레이터에 포함시킴으로써 프로바이더를 최상위 루트 인젝터에 등록합니다. 튜토리얼의 경우 HeroService 프로바이더를 이러한 방식으로 등록했습니다.

@Injectable({
 providedIn: 'root',
})

서비스를 최상위 루트 레벨에 등록하면, 앵귤러는 하나의 공유되는 HeroService 인스턴스를 만들고 이를 요구하는 모든 클래스에 HeroService에 주입할 수 있습니다. 프로바이더를 @Injectable() 메타데이터에 등록하는 방식은, 컴파일 단계에서 앱이 해당 서비스를 사용하지 않을 경우 해당 서비스를 삭제하여, 앱을 최적화 할 수 있습니다.

 

특정 NgModule에 등록

특정 NgModule에 프로바이더를 등록하면, 해당 프로바이더가 제공하는 서비스 클래스 인스턴스는 해당 NgModule 속 컴포넌트에서 사용할 수 있습니다. NgModule 레벨에서 등록을 하기 위해서는 @NgModule() 데코레이터의 providers 프로퍼티를 활용해야 합니다.

@NgModule({
  providers: [
  BackendService,
  Logger
 ],
 ...
})

 

특정 컴포넌트에 등록

컴포넌트 레벨에 프로바이더를 등록하면, 해당 컴포넌트의 인스턴스가 생성될 때마다 의존성 객체를 새로 생성하게 됩니다. 컴포넌트 레벨에서는 @Component() 데코레이터의 providers 프로퍼티를 활용해야 합니다.

//src/app/hero-list.component.ts (컴포넌트 프로바이더)

@Component({
  selector:    'app-hero-list',
  templateUrl: './hero-list.component.html',
  providers:  [ HeroService ]
})

 

Comments