Programming/Web

[Angular] Service & Dependency Injection (서비스와 의존성 주입)

쌍쌍바나나 2018. 1. 21. 16:47
반응형

서비스(Service)란?

  • 화면을 구성하는 뷰를 생성하고 관리하는 역할
  • 컴포너는트 내의 부가기능(로깅기능, 서버 통신 기능에 사용)
  • 컴포넌트 외의 부가 기능이 변경되면 컴포넌트를 변경해야한다. (독립성x)
  • 코드중복, 재사용성이 낮고, 복잡도는 높아진다.
  • 서비스가 필요한 이유?
    • 컴포넌트 관심사와 애플리케이션 전역 관심사를 분리하기 위해서 서비스 사용
    • 서비스느 재사용이 가능하게 되어 일관된 앱 코드 작성이 가능
  • 뷰를 구성하기 위해 데이터를 서버로 부터 취득 하는 행위를 컴포넌트에게 필요한 기능이지만 그 기능 자체가 컴포넌트의 주된 괌심사는 아니다.
  • 위 데이터를 서버로 부터 취득하는 공통 관심사를 서비스로 분리한다면 구성 요소마다 자신의 관심사에 집중할 수 있음
  • 결과적으로 재사용성은 높아지고 복잡도는 낮출 수 있다.

서비스 생성

  • 서비스는 Dependency Injection이 가능한 클래스로 작성한다. (의존성은 뒤에서 설명)
  • $ ng generate service hero
  • 의존성
    • 서비스를 만들고, 사용할 컴포넌트에서 객체를 생성해서 함수를 호출할 수 있다. 하지만 이 방법을 사용하면 호출할 서비스가 변경되면 컴포넌트를 변경해야 하기 때문에 의존성이 긴밀한 결합(Tight Coupling) 되어 있다고 할 수 있다.
    • Tight Coupling에서 느슨한 결합(Loose Coupling)으로 전환하기 위해서 의존성주입(Dependency Injection)을 지원한다
  • 의존성 주입은 애플리케이션이 직접 인스턴스를 생성하는 것이 아니라 Angular 프레임워크가 생성한 인스턴스를 사용하는 방식이다
  • 의존성 주입을 사용하여 컴포넌트가 직접 인스터스를 생성하지 않고, Angular가 인스턴스 생성의 주체가 되도록 하는게 좋다.

의존성 주입 (Dependency Injection)

  • 의존성 주입(DI)은 구성요소 간의 의존 관계를 코드 내부가 아닌, 외부 설정파일 등을 통해 정의하는 디자인 패턴 중의 하나로 구성 요소 간 결합도를 낮추고 재사용성을 높인다.
  • 결과적으로 인스턴스를 컴포넌트가 직접 생성하지 않는다.
  • Angluar가 인스턴스를 생성하고 컴포넌트에 전달하는 방식
  • 제어권의 역전(Inversion of Control, IOC)
    • 컴포넌트는 필요한 의존관계 객체를 요구하고, 프레임워크는 제어권(Control)을 갖는 주체로 동작 요구된 의존 관계 객체의 인스턴스를 ㅈ기접 생성하여 전달
  • 필요한 의존 관계 객체의 인스턴스를 어떻게 생성하는지 Angular가 모르기 때문에 이 정보를 Angular에게 알려주어야 한다.
    • @Component Annotation의 providers 프로퍼티는 의존성으로 주입될 객체의 인스턴스를 어떻게 생성하는지 Angular에게 설명
  • 컴포넌트의 메소드에서 this에 의해 참조 가능

인젝터 (Injector)

  • 컴포넌트가 생성될 때, Angular는 컴포넌트에 필요한 인스턴스를 인젝터에게 요청
  • 인젝터는 이전에 이미 생성한 인스턴스를 담고 있는 컨테이너를 관리
    • 인스턴스가 컨테이너에 존재하지 않으면 인스턴스 생성하고 컨테이너에 추가

인젝터 트리 (Injector tree)

  • 컴포넌트는 트리 구조로 구성
  • 인젝터 수행 순서
    • 컴포넌트는 각각 하나의 인젝터를 가지고 있기 때문에 컴포넌트의 트리 구조와 동일한 인젝터가 생성
    • 컴포넌트의 주입 요청이 있을 때 인젝터는 현재 컴포넌트에서 등록한 프로바이더에서 주입 대상을 검색
    • 이때 해당 컴포넌트의 프로바이더에서 주입 대상을 찾을 수 없으면 상위 컴포넌트의 프로바이더에서 주입 대상을 검색
    • 상위 컴포넌트의 프로바이더를 검색하여 주입 대상을 찾으면 해당 인젝터는 인스턴스를 생성하며 상위 인젝 가 생성한 인스턴스는 하위 컴포넌트에서 사용가능
  • 결과적으로 이젝터 트리의 최상위 인젝터 즉 루트 인젝터에서 생성한 인스턴스는 전역에서 사용 가능한 전역 서비스로 사용 가능

프로바이더 (Provider)

  • 인젝터가 인스턴스를 생성할 때 인스턴스를 어떻게 생성하는지 인스턴스 생생 정보를 Angular에 알려주어야 한다.
  • 위 인스턴스 생성 정보는 providers 프로퍼티에 등록한다.
  • providers 프로퍼티 모듈의 @NgModule 또는 컴포넌트의 @Component 어노테이션에 등록한다.
  • @NgModule 어노테이션에 드옥한 서비스는 모듈 전체(루트 모듈의 경우, 애플리케이션 전역)에 반영
  • @Component 어노테이션에 등록한 서비스는 해당 컴포넌트와 자식 컴포넌트에 반영
  • 모듈 레벨에 등록한 서비스는 하나의 인스턴스를 공유하지만 컴포넌트 레벨로 등록한 서비스는 컴포넌트가 생성될 때마다 새로운 인스턴스를 취득

  • 프로바이더 3가지 종류

    • 클래스 프로바이더 (Class Provider)
    • 클래스 인스턴스를 의존성 주입하기 위한 설정을 등록 (위에서 Tight Coupling에서 Loose Coupling을 하기 위해 객체 생성 방식을 변경할때 우리는 class provider를 사용)
    • 간단하게 provide는 그대로 두고, useClass에 AnotherGreetingService로 변경하면 가능
      • 확인하기 위해서는 console.log(greetingService instanceof AnotherGreetingService); // true
    • @Component 레벨에서 프로바이더를 등록하면, @NgModule에 동일하기 AnotherGreetingService를 등록하면 중복하여 객체가 생성
    • @NgMoudle에 그대로 두고, @Component 레벨에서 키를 useClass를 useExistingd로 변경하면, 인스턴스를 싱글터으로 생성하도록 프로바이더를 수정할 수 있다.
      • 파라미터에 생성자를 두개 두고 생성하면 확인 가능
      • 확인하기 위해서 console.log(greetingService === anotherGreetingservice);
    • 값 프로바이더 (Value Provider)
    • 고정 값을 의존성 주입하기 위한 설정을 등록
    • 예) URL, PORT
    • @Component에 providers에 dict의 형태로 삽입하면 사용 가능
      • 예) providers; [{provide: AppConfig, useValue: MYAPPCONFIG}]
    • AppConfig는 인스턴스 값 프로바이더 useValue property의 MYAPPCONFIG의 값으로 초기화 되었다.
    • 주입될 의존성은 클래스가 아닌 문자열, 함수, 객체일 수 도 있음
    • @Inject 데코레이터에는 주입할 대상의 토큰을 설정
      • 클래스 이외의 토큰의 경우, 명시적으로 @Inject 데코레이터를 선언해야 한다.
      • @Inject('myConfig') public myConfig: string
    • 팩토리 프로바이더 (Factory Provider)
    • 의존성을 생성할 때 어떠한 로직을 거쳐야 한다면 팩토리 함수를 사용
      • 조건을 인자로 받아 의존성을 생성
      • 여러 의존성 중에 어떤 것을 생성할 지 결정해야 하는 경우
      • dev모드일때 mock데이터를 실제 서비스일때는 실제 데이터를 전달하기 위해 사용
    • 인젝션 토큰 (Injection Token)
    • 공통 상수로 사용하는 경우를 제외하고 토큰으로 클래스를 사용
    • 인젝션 토큰은 클래스가 아닌 의존성(non-class dependency)
      • 객체, 문자열, 함수 등을 위한 토큰을 주입받기 위해 사용
    • 인젝션 토큰 (Injection Token) 사용 사례
      • 타입스크립트는 자바스크립트로 트랜스파일링될 때, 자바스크립트는 인터페이스를 지원하지 않으므로, 인터페이스가 사라지게 된다.
      • Angular가 런타임에 찾을 수 있는 인터페이스 타입 정보가 없기 때문에 인터페이스를 토큰으로 등록하면 에러가 발생
      • 위에서 AppConfig을 class가 아닌 interface로 정의를 내리는 경우
      • MYAPPCONFIG를 제거 할 수 있음
    • 선택적 의존성 주입 (Optional Dependency)
    • 프로바이더 등록을 하지 않으면 의존성 주입은 실패하고 애플리케이션은 중단된다.
    • @Optional decorator를 사용하면 의존성 주입이 필수가 아닌 선택 사항임을 Angular에게 전달
    • 의존성이 없어도 에러로 인해 애플리케이션이 중단되지 않는다
    • 서비스 중재자 패턴 (Service Mediator Pattern)
    • 컴포넌트는 독리적인 존재지만, 다른 컴포넌트와 결합도를 낮게 유지하면서 상태 정보를 교환
    • @Input, @Output 데코레이터를 이용하면 컴포넌트 간에 상태를 공유할 수 있다
      • 원거리 컴포넌트 간의 상태 공유를 위해서 상태 공유가 필요없는 컴포넌트를 경
      • 일관된 자료 구조가 존재하지 않기 때문에 개별적인 프로퍼티만 교환 할 수 밖에 없는 한계
    • 위 문제로 인해서 컴포넌트 간 데이터 중재자로 서비스를 사용하면 일정한 형식의 자료 구조를 사용하면서 컴포넌트 간 상태 공유 가능
  • [참고]

    • http://poiemaweb.com/angular-service
반응형