Reflect-Metadata

김동현·2024년 1월 30일
1

Nestjs

목록 보기
3/6
post-thumbnail

메타프로그래밍(Metaprogramming)

메타(meta)
메타란 , 넘어서, ~와 함께, 접하여, 스스로 라는 뜻을 가진 접두사이다.
다른 개념으로부터의 추상화를 가리키며 후자를 완성하거나 추가하는 데 쓰인다.
즉, ~에 대해서로 해석 되며 데이터에 대한 데이터가 될 수 있다.

단어 앞에 메타라는 접두사가 붙으면 해당 단어를 추상화하여 해당 단어에 대한 데이터를 설명해준다.

  1. 메타인지 <- 인지에 대한 데이터
  2. 메타감정 <- 감정에 대한 데이터
  3. 메타프로그래밍 <- 프로그래밍에 대한 데이터
  4. 메타몽?

✅ 메타 + 프로그래밍

프로그래밍이란 특정 작업을 수행하기 위해 실행가능한 프로그램을 설계하고 구축하는 프로세스를 의미한다.
<- 우리가 원하는 일을 수행할 수 있도록, 컴퓨터가 이해 가능한 명령이나 코드를 작업하는 것이다.

🤔 프로그래밍 앞에 접두사 메타가 붙으면 어떻게 될까?
프로그램이 만들어져 있는 다른 프로그램을 조작할 수 있도록 설계된 프로그래밍 기술이 된다.
즉, 메타 프로그래밍이란 자기 자신 혹은 다른 컴퓨터 프로그램을 데이터로 취급하여 프로그램을 작성 수정하는 것을 말한다.
프로그램을 넓게 어플리케이션 좁게는 함수로 볼 수 있지 않을까?

단순히 어플리케이션은 프로그램과 데이터로 구성되는 데, 코드 영역에 있는 프로그램이 데이터 영역의 변수 값을 제어하며 어플리케이션이 동작된다.
즉, 프로그램이 데이터를 제어 혹은 프로그래밍하는 셈이다.
출처: 김정환님 블로그

Reflection

메타 프로그래밍에 이용되는 언어를 메타 언어라고 하고, 메타 프로그래밍의 대상이 되는 언어를 대상 언어라고 한다.
여기서 스스로 메타 언어가 되는 것반영 혹은 Reflection 이라고 하는 데 이러한 프로그래밍의 종류를 메타 프로그래밍 이라고 한다.

메타 프로그래밍의 종류는 세가지가 있다.
1. 타입 자기 성찰(Type Introspection)
2. 반영(Reflection)
3. 자기-수정 코드(Self-Modifying Code)
💡 여기서는 관련 있는 반영에 대해 살펴보자


리플렉션은 스스로 메타 언어가 되어 자기 자신을 프로그래밍 하는 것을 말한다고 했다.

function func () {}
console.log(func.name); // func

func.name = "hello";
console.log(func.name); // func

🤔 여기서 funcname 변수는 어디에 있을까?

  • 디버깅 모드를 찍어보면 함수의 프로토타입에서 생성 시 constructor의 프로퍼티 name으로 func가 들어가 있는 것을 볼 수 있다.

    console도 함께 봐보면 콘솔의 모든 메서드의 이름이 지정되어 있는 것을 볼 수 있다.

Object.getOwnPropertyDescriptor()

객체 속성에 대해 설명해주는 Object.getOwnPropertyDescriptor() 메서드를 사용해서 name 프로퍼티에 접근해보자

{
  value: 'func',
  writable: false,     // 수정 가능 여부
  enumerable: false, 
  configurable: true
}

객체 속성 내부에 직접 접근하여 새로운 속성을 정의하거나 수정하는 Object.defineProperty() 메소드를 사용하여 writable을 수정가능하도록 변경해 준 뒤 func.name을 수정하면 func.name이 바뀌는 것을 볼 수 있다.

Object.defineProperty(func, "name", {
    writable: true,
})

func.name = "what"
console.log(func.name) // what

Reflect API

메타 프로그래밍이 동작하는 방식으로는 세 가지 방식이 있다.

  1. 런타임이 제공하는 API를 활용하여 런타임 시에 메타 프로그래밍을 하는 방법
  2. 문자열이나 다른 형식으로 이루어진 프로그래밍 명령을 동적으로 수행하는 방식
  3. 범용 프로그램 변환
    💡 여기서는 Javascript가 제공해주는 첫번째 방식 Reflect API를 살펴보도록 하자

자바스크립트는 리플렉션과 비슷한 이름의 Reflect API를 제공해 준다.
이는 ES6부터 제공해주는 데 Object API의 관련 기능을 모두 제공해준다.

Reflect.defineProperty(func, "name", { writable: true })
func.name = "what"
console.log(func.name) // 'what'

console.log(Reflect.getOwnPropertyDescriptor(func, "name"))

📌 Object와 Reflect는 이미 정의된 속성을 다루기 위한 API를 제공해준다.

Reflect-Metadata

이미 정의된 프로퍼티 뿐 아니라 추가적인 메타 데이터를 추가하기 위해 제안된 개념이 Reflect-Metadata이다.
Reflect-Metadata 제안서를 한번 봐도 좋을 것 같다 😁

npm Reflect-Metadata처럼 패키지에서 다운 받은 후 파일의 최상단에 import reflect-metadata를 추가해주면 해당 아이디어를 사용할 수 있게 된다.
패키지의 github의 reflect.ts 코드를 보면 아래와 같이 namespace로 선언되어 있는 것을 볼 수 있다.
내장되어 있는 Reflect API와 같은 이름을 namespace로 지정해 줌으로써 defineMetadata를 사용할 수 있게 되는 것이다

예를 들어, 아래 코드를 실행 해보면 Reflect API에 추가된 프로퍼티와 메소드를 사용할 수 있게 된다.
reflect-metadata도 비슷하게 활용하는 것 같다.

namespace Reflect {
    export const hello = "hello";
    export function add(num1: number, num2: number): number {
        console.log(num1);
        console.log(num2);
        return num1 + num2;
    }
}
console.log(Reflect.add(1, 2)) // 1, 2, 3
console.log(Reflect.hello) // hello

reflect-metadata 구현부

Reflect-metadata의 Reflect.ts를 한 번 보자

  1. 메타 데이터 저장소와 공급자를 생성한 후 반환한다.
  1. 메타 데이터를 등록하는 함수 defineMetadata를 보자

defineMetadata에 주목해야될 점은 targetproperty이다.
아래 코드 사진을 보면 확인하는 depth가 target과 property로2단계로 이루어져 있는 것을 확인 할 수 있다.

즉, 아래와 같은 자료 구조 형식을 띈다.

const metadata = {
	target: {
      property: {
        key: value
      }
    }
}

💡 예시

// defineMetadata(metadataKey, metadataValue, target, property)
Reflect.defineMetadata('MetadataKey', 'MetadataValue', TestClass, 'TestMethod');

// getMetadata(metadataKey, target, propertyKey)
const metadataValue = Reflect.getMetadata('MetadataKey', TestClass, 'TestMethod');
console.log(metadataValue);
const Metadata = Map{
  // target
  TestCalss: Map{
    // propertyKey
    propertyKey: Map{
      metadataKey: metadataValue,
    }
  }

정리

Reflect-metadataMetadata를 전역에 Map 자료구조를 활용하여 관리한다.

선언된 Map은 두 Depth를 가지게 되는 데 해당 Key 값은 Target과 PropertyKey로 관리된다.
자료 구조 형식은 아래와 같다.

const metadata = {
  	// object
	target: {
      	// 프로퍼티 여러개 지정 가능
    	property: {
          	// 하나의 프로퍼티에 여러가지 메타 데이터를 가질 수 있음.
        	metadataKey: metadataValue
            ...
        }
    }
}

Target은 오브젝트 타입이고, PropertyKey는 문자열 | Symbol | undefined 타입이다.

보통 클래스와 함수에 이미 정의된 프로퍼티를 제외한 추가적인 메타 데이터를 지정하기 위해 reflect-metadata를 활용 할 수 있다!

😎 앞으로

우아한 형제들 팀이 만든 nestjs-library-crud라이브러리는 typeORM의 reflect-metadata와 Nestjs의 인터셉터를 활용하여 CRUD와 스웨거를 자동으로 생성해준다.
처음에 보고 넋을 놓고 코드를 바라본 기억이 난다.😯

위 라이브러리와 @injectable 를 이해하기 위해 시작했는데 reflect-metadata의 내용이 워낙 방대해서 너무 길어졌다..
getMetadata 도 추가적으로 정리를 한 번 한 뒤 reflect-metadata를 활용하고 있는 라이브러리들을 디깅해보는 시간을 가져야겠다!!

참고사항

profile
달려보자

0개의 댓글