사실 이번 챌린지에서 프레임워크를 구현할 것이라고는 생각도 못했다. 아마 나말고도 많은 사람들이 비슷한 생각을 하는 걸로 알고있다..
처음에는 과제 조건 자체도 이해가 잘 안되어서 (라우팅 모듈 구현) 팀원들과 스터디 미팅을 하면서 겨우 이해하고 구현을 어떻게든 시작하게 되었다. 오랜만에 디자인 패턴 들을 검색하고, 과제의 주 목적중 하나인 ‘Nest.js 와 같은 프레임워크의 이해’ 라는 목적에 걸맞게 Nest.js Reference 를 찾아가서 소스를 둘러보고, 책을 뒤져가며 내가 구현할 프레임워크를 구상했다.
라우팅 모듈을 구현하면서 마주한 문제는 API spec 을 적어놓은 json 파일을 가져다가 별도의 라우팅 설정 없이 라우팅이 되도록 하는 작업이었다. 어떻게 구현할까 하다가 fastify 에서 jsonSchema 방식을 사용할 수 있다는 점을 참고하여, API spec 에서 key 값에 해당하는 부분을 Controller 역할을 하는 함수명과 매핑시키면 알아서 라우팅이 되지 않을까 하는 아이디어를 떠올려서 바로 작업에 착수했고, 구현에 성공하였다.
// Routing Module - controller 함수를 받아 json schema key 들과 매핑합니다.
class RouterModule implements ModuleDefaultClass {
private schema: { [x: string]: RouterApiSpec } = {};
private routeFunctions: { [x: string]: (api: RouterApiSpec) => any } = {};
constructor({ path, routeFunctions }: RouterProps) {
const apiSchemaJson = require(path);
const apiSchema = Object.freeze(apiSchemaJson);
this.schema = apiSchema;
this.routeFunctions = routeFunctions
}
/**
* @description json schema router module - API 스펙을 파싱하여 경로를 라우팅합니다.
*/
init(app: express.Express) {
const _schema = Object.entries(this.schema);
_schema.forEach(([key, api]) => {
if (typeof this.routeFunctions[key] === 'function') {
app[api.method](api.url, this.routeFunctions[key](api));
}
})
...
이제 구현은 되었고, 이 모듈을 이제 제어를 할 Root Class 를 만들어 연결하도록 해야 했다. 그래서 Framework 라는 클래스를 만들었고, 이 클래스를 생성하면, express 서버를 만들고 라우팅 등록을 수행하도록 했다. 생성자에서 라우팅 모듈을 전달받아 라우팅 등록을 수행하도록 모듈을 실행하는 로직을 만들었다.
class Framework {
private static _instance: Instance;
private readonly _app: express.Express;
private readonly _server: Server;
private readonly _modules: Modules;
constructor(params: FrameworkProps) {
// 싱글톤 인스턴스 생성
if (!Framework._instance) {
Framework._instance = this.init();
}
// express app 초기화
this._app = expressApp.instance;
// 서버 초기화
this._server = new Server({});
this._modules = params.appProps.modules;
// 모듈 초기화
Object.values(this._modules).forEach((module) => module.init(this._app));
}
static get instance() {
return Framework._instance;
}
init() {
return {
modules: this._modules,
app: this._app,
}
}
run() {
this._server.init(this._app);
}
}
사실 모듈을 연결하는 과정이 순탄치 않았다. 모듈을 만들었다는 것은, DI (Dependency injection) 디자인 패턴을 적용하여 연결하겠다는 것인데, JS 에서 클래스가 사용되는 순간, Framework 클래스가 Singleton instance 로 만들어지는데 이 과정을 기다리지 않기 때문에 ‘모듈과 모듈’ 사이의 연결이 불가능 하다는 점이었다.