메타(meta)
메타란뒤
,넘어서
,~와 함께
,접하여
,스스로
라는 뜻을 가진접두사
이다.
다른 개념으로부터의 추상화
를 가리키며 후자를완성하거나 추가
하는 데 쓰인다.
즉,~에 대해서
로 해석 되며 데이터에 대한 데이터가 될 수 있다.
단어 앞에 메타
라는 접두사가 붙으면 해당 단어를 추상화하여 해당 단어에 대한 데이터를 설명해준다.
에 대한
데이터에 대한
데이터프로그래밍
이란 특정 작업을 수행하기 위해 실행가능한 프로그램을 설계하고 구축하는 프로세스를 의미한다.
<- 우리가 원하는 일을 수행할 수 있도록, 컴퓨터가 이해 가능한 명령이나 코드를 작업하는 것이다.
🤔 프로그래밍 앞에 접두사 메타
가 붙으면 어떻게 될까?
프로그램이 만들어져 있는 다른 프로그램을 조작할 수 있도록 설계된 프로그래밍 기술
이 된다.
즉, 메타 프로그래밍이란 자기 자신 혹은 다른 컴퓨터 프로그램을 데이터로 취급
하여 프로그램을 작성 수정하는 것을 말한다.
프로그램을 넓게 어플리케이션 좁게는 함수로 볼 수 있지 않을까?
단순히 어플리케이션은 프로그램과 데이터로 구성되는 데, 코드 영역에 있는 프로그램이 데이터 영역의 변수 값을 제어하며 어플리케이션이 동작된다.
즉, 프로그램이 데이터를 제어 혹은프로그래밍
하는 셈이다.
출처: 김정환님 블로그
메타 프로그래밍에 이용되는 언어를 메타 언어
라고 하고, 메타 프로그래밍의 대상이 되는 언어를 대상 언어
라고 한다.
여기서 스스로 메타 언어가 되는 것
을 반영
혹은 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
🤔 여기서 func
의 name
변수는 어디에 있을까?
constructor
의 프로퍼티 name으로 func가 들어가 있는 것을 볼 수 있다.
console
도 함께 봐보면 콘솔의 모든 메서드의 이름이 지정되어 있는 것을 볼 수 있다.
객체 속성에 대해 설명해주는 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
메타 프로그래밍이 동작하는 방식으로는 세 가지 방식이 있다.
- 런타임이 제공하는 API를 활용하여 런타임 시에 메타 프로그래밍을 하는 방법
- 문자열이나 다른 형식으로 이루어진 프로그래밍 명령을
동적으로
수행하는 방식- 범용 프로그램 변환
💡 여기서는 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 제안서를 한번 봐도 좋을 것 같다 😁
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.ts를 한 번 보자
defineMetadata
를 보자defineMetadata
에 주목해야될 점은 target
과 property
이다.
아래 코드 사진을 보면 확인하는 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-metadata
는 Metadata
를 전역에 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
를 활용하고 있는 라이브러리들을 디깅해보는 시간을 가져야겠다!!