현대 Meteor 애플리케이션은 데이터 계층에 대한 커스텀 로직 확장이 필수적이다. meteor-collection-hooks 패키지는 Mongo.Collection 의 표준 메서드를 가로채는 데 가장 널리 사용되는 도구이며, Meteor 3.x 로의 전환과 함께 네이티브 비동기 처리 지원이 핵심 요구사항으로 부상했다. 본 가이드는 최신 프레임워크 구조에서 비동기 훅을 효과적으로 통합하고 호환성 이슈를 해결하는 방법을 다룬다.
Meteor 3 환경의 비동기 훅 개요
Meteor 3.x 는 프라미스 기반 API 로 완전히 전환되었다. 기존 동기식 콜백은 대부분 deprecated 되었으며, 훅 내부에서 외부 서비스 검증, 캐시 무효화, 이벤트 브로드캐스팅을 수행하려면 async/await 지원이 반드시 필요하다. 업데이트된 라이브러리는 기존 동기식 코드와의 후방 호환성을 유지하면서 동시에 최신 비동기 패턴을 전면 지원한다.
설치 및 아키텍처 구조
프로젝트 루트에서 패키지를 초기화한다:
meteor add matb33:collection-hooks
라이브러리는 내부적으로 각 데이터 조작 유형을 개별 핸들러(insert.js, update.js 등) 로 분리하며, 엄격한 타입 검증을 위한 TypeScript 선언 파일을 포함하고 있다.
핵심 구현 패턴
다음은 변수명 및 로직을 재구성한 5 가지 표준 시나리오이다.
1. 삽입 전 데이터 정제 및 비동기 검증
const UserProfiles = new Mongo.Collection('user_profiles');
UserProfiles.before.insert(async function (currentUser, record) {
const isValid = await checkExternalAuth(record.externalId);
if (!isValid) throw new Error('External validation failed');
record.registrationDate = new Date();
record.registeredBy = currentUser || 'system';
});
2. 업데이트 시 모디파이어 동적 주입
UserProfiles.before.update(function (currentUser, record, fields, modifier, opts) {
modifier.$set = modifier.$set || {};
modifier.$set.lastSynced = new Date();
// 수정은 반드시 'modifier' 객체에 적용해야 하며, 'record'는 읽기 전용이다.
});
3. 업데이트 후 이벤트 브로드캐스트
UserProfiles.after.update(async function (currentUser, record, fields, modifier, opts) {
await broadcastStatusChange(record._id, record.status);
});
// 이전 상태 접근:
UserProfiles.after.update({ fetchPrevious: true }, async function (currentUser, record) {
const changes = this.previous.status !== record.status;
if (changes) await triggerWebhook(record._id);
});
4. 연쇄 삭제 작업
const Departments = new Mongo.Collection('departments');
Departments.before.remove(function (currentUser, deptRecord) {
Employees.remove({ departmentId: deptRecord._id });
EquipmentLogs.remove({ assignedToDept: deptRecord._id });
});
5. 전역 쿼리 필터링 (소프트 삭제)
Articles.before.find(function (currentUser, selector, opts) {
selector.isArchived = { $ne: true }; // 보관 처리된 문서를 자동으로 필터링
});
Meteor 3.x 호환성 매트릭스
업그레이드 시 훅의 트리거 방식과 실행 구조에서 발생하는 특정 변경 사항을 반드시 점검해야 한다.
| 훅 카테고리 | Meteor 2.x 동작 | Meteor 3.x 동작 |
|---|---|---|
| 쓰기 연산 (insert/update/remove/upsert) | 동기식 전용 | 비동기/await 지원 + 동기식 폴백 |
| before.find | 동기식 | 동기식 전용 (비동기 시 예외 발생) |
| find 커서 트리거 | 동기/비동기 메서드 공용 | 비동기 커서 메서드 전용 |
| findOne 트리거 | findOne | findOneAsync 전용 |
핵심 실행 규칙
- before.find 의 동기 제약:
find()는 동기적으로 커서를 반환해야 하므로, 해당 훅을async로 선언하면 런타임 오류가 발생한다. 반드시 동기식으로 작성한다. - 비동기 커서 의존성: 쿼리 훅은 이제
fetchAsync(),countAsync(),forEachAsync()호출 시에만 트리거된다. 기존 동기식 페칭은 훅 레이어를 우회한다. - findOne 비동기 마이그레이션: 훅을 활성화하려면
findOne()을await findOneAsync()로 교체해야 한다. 내부 구현은Tracker.withComputation을 통해 반응성 컨텍스트를 유지한다. - Upsert 실행 흐름: 전용
after.upsert훅은 존재하지 않는다. 시스템은 항상before.upsert를 실행한 후, 영향を受けた 문서 수에 따라after.insert또는after.update중 하나를 추가로 호출한다.
고급 구성 및 최적화
1. 대량 작업 시 훅 우회
마이그레이션 또는 배치 처리 중 훅 실행을 건너뛰려면 direct 수정자를 사용한다:
Inventory.direct.insertMany(rawData);
Inventory.direct.updateAsync({ sku: 'A1' }, { $inc: { stock: 100 } });
2. 컨텍스트 기반 사용자 데이터 주입
세션 컨텍스트가 없는 서버 측 스크립트 또는 마이크로서비스에서:
import { CollectionHooks } from 'meteor/matb33:collection-hooks';
CollectionHooks.defaultUserId = 'service-account-bot';
3. 동적 훅 라이프사이클 관리
const hookRef = Inventory.before.insert(asyncHookLogic);
// 코드베이스 내 다른 위치에서:
hookRef.remove();
hookRef.replace(differentLogic, { fetchPrevious: false });
실무 가이드라인 및 문제 해결
- 동형(Isomorphic) 실행 리스크: 공유 디렉토리에 정의된 훅은 클라이언트와 서버 양쪽에서 실행된다. 중복 트리거를 방지하려면 훅 등록 코드를
server/디렉토리로 격리한다. - 연산 취소 로직:
before훅 내에서false를 반환하면 데이터베이스 작업이 중단된다. 모든before훅은 중단 처리가 적용되기 전까지 순차적으로 모두 실행된다는 점을 유의한다. - 암시적 쿼리 훅:
update와remove는 변경 적용 전 내부적으로 문서를 조회하므로, 의도치 않게find또는findOne훅을 트리거할 수 있다. 훅 설계 시 멱등성(Idempotency)을 고려한다. - Null 사용자 컨텍스트: 서버에서 직접 시작된 작업은 일반적으로
userId로undefined를 전달한다. 항상 폴백 로직을 구현하거나defaultUserId를 활용한다. - 오류 처리: 비동기 훅 내에서 처리되지 않은 프라미스 거부는 작업을 중단시킨다. 중요한 로직은
try/catch블록으로 감싸고 구조화된 로깅을 구현하여 트랜잭션 롤백을 방지한다.
Meteor 3.x 로의 전환은 프레임워크 전체의 데이터 처리를 비동기 기준으로 표준화한다. 훅 등록을 *Async 커서 메서드와 일치시키고 네이티브 프라미스 지원을 활용하면, 더 안정적이고 확장 가능한 데이터 계층을 구축할 수 있다. 프로덕션 배포 전 패키지 내 통합 테스트 스위트(Test Suite)를 참조하여 호환성을 검증한다.