Skip to content

Context-Action 액션 파이프라인 시스템 가이드

Context-Action 프레임워크의 핵심인 액션 파이프라인 시스템에 대한 포괄적인 가이드입니다.

개요

액션 파이프라인 시스템은 Context-Action의 ViewModel 레이어의 핵심으로, 우선순위 기반 핸들러 실행과 정교한 파이프라인 제어를 통한 중앙집중식 액션 처리를 제공합니다.

핵심 구성 요소

ActionRegister

ActionRegister 클래스는 액션 파이프라인 시스템의 핵심입니다:

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

interface MyActions extends ActionPayloadMap {
  authenticate: { username: string; password: string };
  processData: { data: any; options?: Record<string, any> };
  uploadFile: { filename: string; content: string };
}

const actionRegister = new ActionRegister<MyActions>({
  name: 'MyAppActions',
  registry: {
    debug: false,
    defaultExecutionMode: 'sequential'
  }
});

핸들러 등록

우선순위 기반 실행으로 핸들러를 등록합니다:

typescript
// 높은 우선순위 핸들러가 먼저 실행 (priority 100 > 50 > 10)
actionRegister.register('authenticate', validateCredentials, { priority: 100 });
actionRegister.register('authenticate', checkRateLimit, { priority: 90 });
actionRegister.register('authenticate', performAuth, { priority: 80 });
actionRegister.register('authenticate', logAudit, { priority: 70 });

파이프라인 컨트롤러

각 핸들러는 고급 파이프라인 관리를 위한 PipelineController를 받습니다:

typescript
actionRegister.register('authenticate', async (payload, controller) => {
  // 1. 입력 검증
  if (!payload.username) {
    controller.abort('사용자명이 필요합니다');
    return;
  }
  
  // 2. 후속 핸들러를 위한 페이로드 수정
  controller.modifyPayload(current => ({
    ...current,
    timestamp: Date.now(),
    validated: true
  }));
  
  // 3. 중간 결과 설정
  controller.setResult({ step: '검증', success: true });
  
  // 4. 최종 결과 반환
  return { validated: true, user: payload.username };
});

우선순위 기반 실행

실행 순서

핸들러는 내림차순 우선순위 순서로 실행됩니다 (가장 높은 것부터):

typescript
const executionOrder: string[] = [];

actionRegister.register('processData', () => {
  executionOrder.push('낮음');    // Priority: 10
}, { priority: 10 });

actionRegister.register('processData', () => {
  executionOrder.push('높음');   // Priority: 100  
}, { priority: 100 });

actionRegister.register('processData', () => {
  executionOrder.push('중간'); // Priority: 50
}, { priority: 50 });

await actionRegister.dispatch('processData', { data: 'test' });
// executionOrder: ['높음', '중간', '낮음']

핸들러 설정

typescript
actionRegister.register('uploadFile', handler, {
  id: 'file-processor',           // 고유 식별자
  priority: 50,                   // 실행 우선순위
  once: false,                    // 여러 번 실행
  blocking: true,                 // 완료 대기
  condition: (payload) => payload.filename.endsWith('.pdf'), // 조건부 실행
  metadata: {                     // 사용자 정의 메타데이터
    description: 'PDF 파일 처리기',
    version: '1.0.0'
  }
});

파이프라인 제어 메서드

controller.abort()

선택적 이유와 함께 파이프라인 실행 중단:

typescript
actionRegister.register('authenticate', (payload, controller) => {
  if (!isValidUser(payload.username)) {
    controller.abort('유효하지 않은 사용자 자격 증명');
    return;
  }
  // 후속 핸들러는 실행되지 않음
});

controller.modifyPayload()

후속 핸들러를 위한 페이로드 변환:

typescript
actionRegister.register('processData', (payload, controller) => {
  controller.modifyPayload(current => ({
    ...current,
    processed: true,
    timestamp: Date.now(),
    version: '2.0'
  }));
}, { priority: 100 });

actionRegister.register('processData', (payload) => {
  // payload에 이제 processed, timestamp, version이 포함됨
  console.log(payload.processed); // true
}, { priority: 50 });

controller.setResult() 및 getResults()

핸들러 간 중간 결과 관리:

typescript
actionRegister.register('uploadFile', (payload, controller) => {
  // 중간 결과 설정
  controller.setResult({ step: '검증', fileSize: 1024 });
  
  return { step: '업로드', fileId: 'file-123' };
}, { priority: 100 });

actionRegister.register('uploadFile', (payload, controller) => {
  // 이전 결과 접근
  const previousResults = controller.getResults();
  console.log(previousResults); 
  // [{ step: '검증', fileSize: 1024 }, { step: '업로드', fileId: 'file-123' }]
}, { priority: 50 });

실행 모드

순차 모드 (기본값)

핸들러가 차례대로 실행:

typescript
actionRegister.setActionExecutionMode('processData', 'sequential');

// 핸들러 1 완료 → 핸들러 2 시작 → 핸들러 3 시작

병렬 모드

모든 핸들러가 동시에 실행:

typescript
actionRegister.setActionExecutionMode('processData', 'parallel');

// 핸들러 1, 2, 3이 모두 동시에 시작

경쟁 모드

첫 번째 완료 핸들러가 승리:

typescript
actionRegister.setActionExecutionMode('processData', 'race');

// 처음 반환하는 핸들러가 나머지를 중단

결과 수집

기본 디스패치

typescript
const result = await actionRegister.dispatch('authenticate', {
  username: 'john',
  password: 'secret123'
});

결과 수집을 통한 디스패치

typescript
const result = await actionRegister.dispatchWithResult('uploadFile', 
  { filename: 'document.pdf', content: 'pdf 내용' },
  { result: { collect: true } }
);

console.log(result);
// {
//   success: true,
//   aborted: false,
//   terminated: false,
//   results: [
//     { step: '검증', success: true },
//     { step: '업로드', fileId: 'file-123' },
//     { step: '알림', sent: true }
//   ],
//   execution: {
//     handlersExecuted: 3,
//     startTime: 1640995200000,
//     endTime: 1640995200500,
//     duration: 500
//   }
// }

에러 처리

개별 핸들러가 실패해도 파이프라인은 계속 실행됩니다:

typescript
actionRegister.register('processData', () => {
  throw new Error('핸들러 1 실패');
}, { priority: 100 });

actionRegister.register('processData', () => {
  return { success: true, step: '복구' };
}, { priority: 50 });

const result = await actionRegister.dispatchWithResult('processData', 
  { data: 'test' },
  { result: { collect: true } }
);

// result.success: true (파이프라인 성공)
// result.results: [{ success: true, step: '복구' }] (성공한 결과만)

실제 예제: 인증 플로우

typescript
interface AuthActions extends ActionPayloadMap {
  authenticate: { username: string; password: string };
}

const authRegister = new ActionRegister<AuthActions>();

// 1. 입력 검증 (Priority: 100)
authRegister.register('authenticate', (payload, controller) => {
  if (!payload.username || !payload.password) {
    controller.abort('자격 증명 누락');
    return;
  }
  return { step: '검증', valid: true };
}, { priority: 100, id: 'validator' });

// 2. 속도 제한 (Priority: 90)
authRegister.register('authenticate', (payload) => {
  // 속도 제한 확인
  return { step: '속도제한', allowed: true };
}, { priority: 90, id: 'rate-limiter' });

// 3. 인증 (Priority: 80)
authRegister.register('authenticate', async (payload) => {
  const user = await authenticateUser(payload.username, payload.password);
  return { 
    step: '인증', 
    user: { id: user.id, username: user.username },
    token: generateJWT(user)
  };
}, { priority: 80, id: 'authenticator' });

// 4. 감사 로깅 (Priority: 70)
authRegister.register('authenticate', (payload) => {
  logAuthAttempt(payload.username, true);
  return { step: '감사', logged: true, timestamp: Date.now() };
}, { priority: 70, id: 'auditor' });

// 완전한 인증 파이프라인 실행
const result = await authRegister.dispatchWithResult('authenticate', {
  username: 'john',
  password: 'secret123'
}, { result: { collect: true } });

// 결과에 모든 단계 포함: 검증 → 속도제한 → 인증 → 감사

React와의 통합

액션 파이프라인은 액션 컨텍스트 패턴을 통해 React와 원활하게 통합됩니다:

typescript
const { Provider, useActionDispatch, useActionHandler } = createActionContext<AuthActions>('Auth');

function AuthComponent() {
  const dispatch = useActionDispatch();
  
  // 컴포넌트에서 핸들러 등록
  useActionHandler('authenticate', async (payload) => {
    // 인증 처리
  });
  
  const handleLogin = async () => {
    await dispatch('authenticate', { username: 'john', password: 'secret' });
  };
  
  return <button onClick={handleLogin}>로그인</button>;
}

모범 사례

  1. 우선순위 설계: 논리적 실행 순서에 따라 우선순위 할당
  2. 에러 처리: 적절한 에러 처리와 복구 메커니즘 구현
  3. 결과 수집: 복잡한 워크플로에 대해 결과 수집 사용
  4. 성능 최적화: 적절한 실행 모드 선택
  5. 타입 안전성: TypeScript와 함께 사용하여 타입 안전성 확보

Released under the Apache-2.0 License.