Fastify 플러그인 아키텍처 이해
Fastify는 Express와 달리 플러그인 기반의 캡슐화(encapsulation)를 핵심 설계 원칙으로 삼고 있습니다. 각 플러그인은 독립적인 컨텍스트를 가지며, 데코레이터와 훅은 해당 스코프 내에서만 유효합니다.
기본 플러그인 구조
// plugins/db.js
const fp = require('fastify-plugin');
async function dbPlugin(fastify, opts) {
const pool = new Pool({
host: opts.host || 'localhost',
port: opts.port || 5432,
database: opts.database,
});
// 데코레이터로 fastify 인스턴스에 db 추가
fastify.decorate('db', pool);
// 서버 종료 시 커넥션 정리
fastify.addHook('onClose', async () => {
await pool.end();
});
}
// fp()로 감싸면 캡슐화를 깨고 부모 스코프에 공유
module.exports = fp(dbPlugin, {
name: 'db-plugin',
dependencies: [],
});
라우트 플러그인 모듈화
// routes/users/index.js
async function userRoutes(fastify, opts) {
// 이 스코프 전용 훅
fastify.addHook('preHandler', async (req, reply) => {
// 인증 체크
if (!req.headers.authorization) {
reply.code(401).send({ error: 'Unauthorized' });
}
});
fastify.get('/', {
schema: {
querystring: {
type: 'object',
properties: {
page: { type: 'integer', default: 1 },
limit: { type: 'integer', default: 20 },
},
},
response: {
200: {
type: 'object',
properties: {
users: { type: 'array' },
total: { type: 'integer' },
},
},
},
},
handler: async (req, reply) => {
const { page, limit } = req.query;
const offset = (page - 1) * limit;
const result = await fastify.db.query(
'SELECT id, name, email FROM users LIMIT $1 OFFSET $2',
[limit, offset]
);
return { users: result.rows, total: result.rowCount };
},
});
fastify.post('/', {
schema: {
body: {
type: 'object',
required: ['name', 'email'],
properties: {
name: { type: 'string', minLength: 2 },
email: { type: 'string', format: 'email' },
},
},
},
handler: async (req, reply) => {
const { name, email } = req.body;
const result = await fastify.db.query(
'INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *',
[name, email]
);
return reply.code(201).send(result.rows[0]);
},
});
}
module.exports = userRoutes;
앱 등록 구조
// app.js
const fastify = require('fastify')({ logger: true });
const autoLoad = require('@fastify/autoload');
const path = require('path');
// 플러그인 자동 로드
fastify.register(autoLoad, {
dir: path.join(__dirname, 'plugins'),
options: { database: 'myapp' },
});
// 라우트 자동 로드 (접두사 자동 매핑)
fastify.register(autoLoad, {
dir: path.join(__dirname, 'routes'),
options: {},
routeParams: true, // 폴더명을 파라미터로 매핑
});
fastify.listen({ port: 3000 }, (err) => {
if (err) { fastify.log.error(err); process.exit(1); }
});
플러그인 vs Express 미들웨어 비교
| 특성 | Fastify 플러그인 | Express 미들웨어 |
|---|---|---|
| 캡슐화 | 스코프 기반 격리 | 전역 공유 |
| 의존성 관리 | 자동 순서 해결 | 수동 순서 지정 |
| 스키마 검증 | 내장 JSON Schema | 별도 라이브러리 필요 |
| 성능 | 직렬화 최적화 | 별도 최적화 없음 |
fastify-plugin(fp)으로 감싸면 캡슐화를 해제하여 공유 가능@fastify/autoload로 디렉터리 기반 자동 등록- 스키마 기반 직렬화로 JSON.stringify 대비 2-3배 빠름
- 데코레이터 패턴으로 의존성 주입(DI) 구현
Fastify의 플러그인 시스템은 대규모 API 서버에서 코드 분리와 테스트 용이성을 크게 향상시킵니다. 적절한 캡슐화 전략을 통해 마이크로서비스 수준의 모듈 격리를 모노리스 내에서 구현할 수 있습니다.
댓글 0