Skip to content

파이프라인 시스템

정교한 비즈니스 로직 오케스트레이션을 위한 고급 액션 파이프라인 기능.

개요

액션 파이프라인 시스템은 우선순위 기반 핸들러 실행, 차단 작업, 중단 메커니즘 및 포괄적인 결과 수집을 통해 복잡한 비즈니스 로직 플로우를 관리하기 위한 고급 제어 메커니즘을 제공합니다.

핵심 기능

🏆 우선순위 기반 실행

핸들러는 우선순위 순서대로 실행되어(높은 순서 먼저) 중요한 작업이 선택적 작업보다 먼저 실행되도록 합니다.

typescript
actionRegister.register('authenticate', validateInput, { priority: 100 });   // 1번째
actionRegister.register('authenticate', checkSecurity, { priority: 90 });    // 2번째  
actionRegister.register('authenticate', performAuth, { priority: 80 });      // 3번째
actionRegister.register('authenticate', logAudit, { priority: 30 });         // 4번째

🚧 차단 작업

차단 및 비차단 핸들러로 실행 플로우를 제어합니다.

typescript
// 차단: 완료를 기다림
actionRegister.register('process', criticalHandler, { blocking: true });

// 비차단: 즉시 계속
actionRegister.register('process', analyticsHandler, { blocking: false });

🛑 중단 메커니즘

조건이 충족되지 않을 때 파이프라인 실행을 중지합니다.

typescript
actionRegister.register('validate', (payload, controller) => {
  if (!isValid(payload)) {
    controller.abort('검증 실패');
    return;
  }
  // 파이프라인 계속
});

📊 결과 수집

핸들러 간 결과를 수집하고 조정합니다.

typescript
// 중간 결과 설정
controller.setResult({ step: 'validation', valid: true });

// 이전 결과 접근
const results = controller.getResults();
const validationResult = results.find(r => r.step === 'validation');

파이프라인 기능

기능목적문서
우선순위 시스템실행 순서 제어우선순위 기반 핸들러 실행
차단 작업실행 플로우 제어차단 vs 비차단 핸들러
디스패치 메서드파이프라인 트리거기본 디스패치 vs 결과 수집
중단 메커니즘실행 중지우아한 파이프라인 종료
결과 처리결과 수집핸들러 간 통신

빠른 시작

1. 기본 파이프라인 설정

typescript
import { ActionRegister, type ActionPayloadMap } from '@context-action/core';

interface AppActions extends ActionPayloadMap {
  processUser: { userId: string; action: string };
}

const appRegister = new ActionRegister<AppActions>();

2. 기능이 있는 핸들러 등록

typescript
// 차단이 있는 높은 우선순위 검증
appRegister.register('processUser', (payload, controller) => {
  if (!payload.userId) {
    controller.abort('사용자 ID 필수');
    return;
  }
  
  controller.setResult({ step: 'validation', valid: true });
  return { step: 'user-validation', success: true };
}, { 
  priority: 100,    // 높은 우선순위
  blocking: true,   // 완료를 기다림
  id: 'validator'   // 고유 식별자
});

// 차단이 있는 중간 우선순위 처리
appRegister.register('processUser', async (payload, controller) => {
  const results = controller.getResults();
  const isValid = results.find(r => r.step === 'validation')?.valid;
  
  if (!isValid) {
    controller.abort('유효하지 않은 사용자를 처리할 수 없습니다');
    return;
  }
  
  const processed = await processUserData(payload.userId, payload.action);
  
  controller.setResult({ 
    step: 'processing', 
    userId: payload.userId,
    processedAt: Date.now()
  });
  
  return { step: 'user-processing', result: processed };
}, { 
  priority: 80,
  blocking: true,
  id: 'processor'
});

// 차단 없는 낮은 우선순위 분석
appRegister.register('processUser', (payload, controller) => {
  const results = controller.getResults();
  
  // 백그라운드에서 분석 추적
  analytics.track('user_processed', {
    userId: payload.userId,
    action: payload.action,
    steps: results.map(r => r.step)
  });
  
  return { step: 'analytics', tracked: true };
}, { 
  priority: 30,
  blocking: false,  // 분석을 기다리지 않음
  id: 'analytics'
});

3. 파이프라인 실행

typescript
// 기본 실행
await appRegister.dispatch('processUser', {
  userId: '123',
  action: 'profile-update'
});

// 결과 수집과 함께 실행
const result = await appRegister.dispatchWithResult('processUser', 
  { userId: '123', action: 'profile-update' },
  { result: { collect: true } }
);

if (result.success) {
  console.log('파이프라인 완료:', result.results);
} else if (result.aborted) {
  console.log('파이프라인 중단:', result.abortReason);
}

실제 파이프라인 예제

전자상거래 주문 처리

typescript
interface OrderActions extends ActionPayloadMap {
  placeOrder: { 
    items: OrderItem[]; 
    userId: string; 
    paymentMethod: string;
  };
}

const orderRegister = new ActionRegister<OrderActions>();

// 단계 1: 입력 검증 (우선순위 100, 차단)
orderRegister.register('placeOrder', (payload, controller) => {
  if (!payload.items?.length) {
    controller.abort('주문에는 상품이 포함되어야 합니다');
    return;
  }
  
  if (!payload.userId) {
    controller.abort('사용자 ID 필수');
    return;
  }
  
  controller.setResult({ step: 'validation', itemCount: payload.items.length });
  return { step: 'input-validation', success: true };
}, { priority: 100, blocking: true, id: 'input-validator' });

// 단계 2: 재고 확인 (우선순위 90, 차단)
orderRegister.register('placeOrder', async (payload, controller) => {
  const inventoryChecks = await Promise.all(
    payload.items.map(item => checkInventory(item.productId, item.quantity))
  );
  
  const insufficientItems = inventoryChecks
    .filter(check => !check.available)
    .map(check => check.productId);
  
  if (insufficientItems.length > 0) {
    controller.abort('재고 부족', { insufficientItems });
    return;
  }
  
  controller.setResult({ 
    step: 'inventory', 
    allAvailable: true,
    checkedItems: inventoryChecks.length
  });
  
  return { step: 'inventory-check', success: true };
}, { priority: 90, blocking: true, id: 'inventory-checker' });

// 단계 3: 결제 처리 (우선순위 80, 차단)
orderRegister.register('placeOrder', async (payload, controller) => {
  const total = calculateOrderTotal(payload.items);
  
  const payment = await processPayment({
    userId: payload.userId,
    amount: total,
    method: payload.paymentMethod
  });
  
  if (!payment.success) {
    controller.abort('결제 실패', { reason: payment.error });
    return;
  }
  
  controller.setResult({ 
    step: 'payment', 
    transactionId: payment.transactionId,
    amount: total
  });
  
  return { 
    step: 'payment-processing', 
    success: true, 
    transactionId: payment.transactionId 
  };
}, { priority: 80, blocking: true, id: 'payment-processor' });

// 단계 4: 주문 생성 (우선순위 70, 차단)
orderRegister.register('placeOrder', async (payload, controller) => {
  const results = controller.getResults();
  const paymentResult = results.find(r => r.step === 'payment');
  
  const order = await createOrder({
    userId: payload.userId,
    items: payload.items,
    transactionId: paymentResult.transactionId,
    total: paymentResult.amount
  });
  
  controller.setResult({ 
    step: 'order-creation', 
    orderId: order.id,
    createdAt: order.createdAt
  });
  
  return { 
    step: 'order-created', 
    success: true, 
    orderId: order.id 
  };
}, { priority: 70, blocking: true, id: 'order-creator' });

// 단계 5: 재고 업데이트 (우선순위 60, 차단)
orderRegister.register('placeOrder', async (payload, controller) => {
  await Promise.all(
    payload.items.map(item => 
      updateInventory(item.productId, -item.quantity)
    )
  );
  
  return { step: 'inventory-update', success: true };
}, { priority: 60, blocking: true, id: 'inventory-updater' });

// 단계 6: 이메일 알림 (우선순위 40, 비차단)
orderRegister.register('placeOrder', async (payload, controller) => {
  const results = controller.getResults();
  const orderResult = results.find(r => r.step === 'order-creation');
  
  // 백그라운드에서 확인 이메일 발송
  await sendOrderConfirmation({
    userId: payload.userId,
    orderId: orderResult.orderId,
    items: payload.items
  });
  
  return { step: 'email-notification', sent: true };
}, { priority: 40, blocking: false, id: 'email-notifier' });

// 단계 7: 분석 추적 (우선순위 30, 비차단)  
orderRegister.register('placeOrder', (payload, controller) => {
  const results = controller.getResults();
  const orderResult = results.find(r => r.step === 'order-creation');
  const paymentResult = results.find(r => r.step === 'payment');
  
  // 주문 분석 추적
  analytics.track('order_placed', {
    orderId: orderResult.orderId,
    userId: payload.userId,
    itemCount: payload.items.length,
    orderValue: paymentResult.amount,
    paymentMethod: payload.paymentMethod
  });
  
  return { step: 'analytics', tracked: true };
}, { priority: 30, blocking: false, id: 'analytics-tracker' });

// 완전한 주문 파이프라인 실행
const result = await orderRegister.dispatchWithResult('placeOrder', {
  items: [{ productId: 'prod-1', quantity: 2 }],
  userId: 'user-123',
  paymentMethod: 'credit-card'
}, { result: { collect: true } });

// 파이프라인 실행 순서: validation → inventory → payment → order → inventory-update
// 이메일과 분석은 백그라운드에서 실행

기본 액션에서 마이그레이션

이전: 단순 액션 핸들러

typescript
// 기본 액션 처리
const handleUserUpdate = async (userData: UserData) => {
  await validateUser(userData);
  await updateDatabase(userData);
  await sendNotification(userData.id);
  await trackAnalytics('user_updated', userData);
};

이후: 파이프라인 기반 액션

typescript
// 파이프라인 기반 액션 처리
interface UserActions extends ActionPayloadMap {
  updateUser: UserData;
}

const userRegister = new ActionRegister<UserActions>();

// 검증 (우선순위 100, 차단)
userRegister.register('updateUser', async (payload, controller) => {
  const validation = await validateUser(payload);
  if (!validation.valid) {
    controller.abort('사용자 검증 실패', validation.errors);
    return;
  }
  return { step: 'validation', success: true };
}, { priority: 100, blocking: true });

// 데이터베이스 업데이트 (우선순위 80, 차단)  
userRegister.register('updateUser', async (payload) => {
  const result = await updateDatabase(payload);
  return { step: 'database', success: true, userId: result.id };
}, { priority: 80, blocking: true });

// 알림 (우선순위 40, 비차단)
userRegister.register('updateUser', async (payload) => {
  await sendNotification(payload.id);
  return { step: 'notification', sent: true };
}, { priority: 40, blocking: false });

// 분석 (우선순위 30, 비차단)
userRegister.register('updateUser', (payload) => {
  trackAnalytics('user_updated', payload);
  return { step: 'analytics', tracked: true };
}, { priority: 30, blocking: false });

// 파이프라인 실행
await userRegister.dispatch('updateUser', userData);

아키텍처 통합

MVVM 레이어 통합

mermaid
graph TD
    A[뷰 레이어] --> B[뷰모델 레이어]
    B --> C[모델 레이어]
    
    B1[우선순위 시스템] --> B
    B2[차단 제어] --> B
    B3[중단 메커니즘] --> B
    B4[결과 수집] --> B
    B5[디스패치 메서드] --> B
    
    B --> C1[스토어 업데이트]
    B --> C2[외부 API]
    B --> C3[부작용]

스토어 패턴과의 통합

파이프라인 기능은 스토어 통합과 원활하게 작동합니다:

typescript
// 파이프라인이 결과를 기반으로 스토어 업데이트
actionRegister.register('updateUserData', async (payload, controller) => {
  // 현재 스토어 상태 가져오기
  const currentUser = userStore.getValue();
  
  // 업데이트 검증
  const validation = validateUserUpdate(currentUser, payload.updates);
  if (!validation.valid) {
    controller.abort('업데이트 검증 실패');
    return;
  }
  
  // 업데이트 적용
  const updatedUser = { ...currentUser, ...payload.updates };
  userStore.setValue(updatedUser);
  
  controller.setResult({ 
    step: 'store-update', 
    previousValue: currentUser,
    newValue: updatedUser
  });
  
  return { step: 'user-update', success: true };
}, { priority: 80, blocking: true });

모범 사례 요약

1. 우선순위 가이드라인

  • 90-100: 중요한 검증, 보안, 입력 확인
  • 70-89: 비즈니스 로직, 데이터 처리, 핵심 작업
  • 50-69: 상태 업데이트, 외부 API 호출
  • 30-49: 알림, 보조 작업
  • 10-29: 분석, 로깅, 정리

2. 차단 가이드라인

  • 차단 사용: 후속 핸들러에 영향을 주는 작업
  • 비차단 사용: 분석, 로깅, 선택적 개선

3. 중단 가이드라인

  • 중단 사용: 비즈니스 규칙 위반, 검증 실패
  • throw 사용: 예상치 못한 시스템 오류

4. 결과 가이드라인

  • 일관된 결과 구조 사용
  • 의미 있는 단계 이름과 타이밍 포함
  • 핸들러 조정을 위한 결과 활용

고급 패턴

특정 파이프라인 패턴 탐색:

예제 앱에서의 실제 예제

Context-Action 예제 앱은 모든 파이프라인 기능의 실제 구현을 제공합니다:

주요 예제

이러한 예제는 일반적인 파이프라인 시나리오를 위한 프로덕션 준비 패턴을 보여줍니다.

관련 문서

Released under the Apache-2.0 License.