ObjectOS
구축

플로우 및 자동화

선언형 비즈니스 로직 — AI에게 설명하거나 TypeScript로 작성하면, 런타임은 어느 쪽이든 동일한 아티팩트를 실행합니다.

플로우 및 자동화

플로우는 서버를 작성하지 않고도 비즈니스 로직을 표현하는 방법입니다. 모든 플로우는 런타임이 실행하는 선언형 메타데이터로, 객체나 뷰와 동일합니다. 즉, 플로우는 os diff, 감사 로그, Console의 플로우 빌더, 그리고 AI Builder에 한꺼번에 나타납니다.

대부분의 고객은 AI에게 요청하여 플로우를 만듭니다.

"우선순위가 높은 티켓이 'new' 상태로 30분간 머물러 있으면, Slack으로 매니저에게 알려줘."

AI가 아래 플로우를 생성합니다. 이 페이지에서는 읽고 편집할 수 있도록 그 구조를 설명합니다.

스택에서 해당 기능을 활성화하세요.

export default defineStack({
  // ...
  requires: ['automation'],
});

세 가지 플로우 유형

유형트리거사용 사례
Autolaunched레코드 변경 (insert/update/delete)"사용자 등록 시 환영 이메일 보내기"
ScheduledCron 표현식 또는 간격"매일 밤 오래된 작업 표시하기"
Manual사용자가 Console에서 버튼을 클릭하거나 API 호출"송장 승인" 액션

Autolaunched: 레코드 변경에 반응하기

// src/flows/welcome_email.ts
import { defineFlow } from '@objectstack/spec';

export const welcomeEmail = defineFlow({
  name: 'welcome_email',
  type: 'autolaunched',
  trigger: {
    object: 'sys_user',
    when: 'after_insert',
  },
  steps: [
    {
      type: 'action',
      action: 'send_email',
      inputs: {
        to:      '{!trigger.record.email}',
        subject: 'Welcome to {!org.name}',
        body:    'Hi {!trigger.record.name}, welcome aboard.',
      },
    },
  ],
});

변수 보간: {!trigger.record.<field>}, {!org.<field>}, {!user.<field>}, {!step.<step-name>.output}. condition: 블록에서는 CEL 표현식을 사용하세요.

트리거 타이밍:

when실행 시점
before_insert쓰기 트랜잭션 내부, INSERT 이전
after_insert커밋 이후
before_update쓰기 트랜잭션 내부, UPDATE 이전
after_update커밋 이후
before_delete쓰기 트랜잭션 내부, DELETE 이전
after_delete커밋 이후

before_* 플로우는 작성 중인 레코드를 변경할 수 있습니다(필드 계산, 데이터 정규화). after_* 플로우는 비동기로 실행되며 느린 외부 서비스를 호출할 수 있습니다.

Scheduled: 정해진 시각에 실행하기

export const nightlyCleanup = defineFlow({
  name: 'nightly_cleanup',
  type: 'scheduled',
  schedule: { cron: '0 2 * * *', timezone: 'America/New_York' },
  steps: [
    {
      type: 'query',
      query: { object: 'task', filter: 'status:open AND due_lt:now()' },
      output: 'stale',
    },
    {
      type: 'foreach',
      items: '{!step.stale}',
      do: [
        { type: 'update', record: '{!item.id}', fields: { status: 'overdue' } },
      ],
    },
  ],
});

@objectstack/service-job 기능이 이를 지원합니다 — Runtime Capabilities를 참조하세요.

Manual: 액션 및 승인

export const approveInvoice = defineFlow({
  name: 'approve_invoice',
  type: 'manual',
  inputs: {
    invoice_id: { type: 'lookup', reference: 'invoice', required: true },
    note:       { type: 'textarea' },
  },
  steps: [
    {
      type: 'update',
      record: '{!inputs.invoice_id}',
      fields: { status: 'approved', approved_by: '{!user.id}' },
    },
  ],
});

Console에서 Invoice 뷰의 버튼으로 노출하거나, REST를 통해 호출하세요.

curl -X POST https://app.example.com/api/v1/actions/invoice/approve_invoice \
  -H 'Authorization: Bearer <token>' \
  -d '{"inputs": {"invoice_id": "inv_123", "note": "OK"}}'

스텝 유형

스텝용도
queryObjectQL을 통해 레코드 읽기
create / update / delete객체에 쓰기
action내장 또는 플러그인 등록 액션 호출 (이메일, 웹훅, AI 호출 등)
conditionCEL 표현식으로 분기
foreach컬렉션 순회
parallel하위 스텝을 동시에 실행
wait일정 시간 / 특정 시각까지 / 조건 충족까지 일시 정지
subflow다른 플로우 호출
approval사용자가 승인할 때까지 차단 (@objectstack/plugin-approvals 필요)

조건 및 분기

{
  type: 'condition',
  when: 'trigger.record.amount > 10000',
  then: [
    { type: 'action', action: 'send_slack', inputs: { /* ... */ } },
  ],
  else: [
    { type: 'update', record: '{!trigger.record.id}', fields: { status: 'auto_approved' } },
  ],
}

오류 처리

각 스텝은 다음을 받습니다:

{
  type: 'action',
  action: 'send_email',
  inputs: { /* ... */ },
  retry: { attempts: 3, backoffMs: 1000, multiplier: 2 },
  onError: 'continue' | 'fail' | 'rollback',
}

Autolaunched before_* 플로우의 경우, onError: 'fail'(기본값)은 원본 쓰기 트랜잭션을 중단시킵니다. after_* 플로우의 경우, 원본 쓰기는 이미 커밋된 상태이며, 실패한 플로우 실행은 작업 재시도 큐로 들어갑니다.

수식 및 표현식 (CEL)

조건, 동적 필드 값, 필터 표현식은 모두 CEL(Common Expression Language) — 안전한 표현식 평가를 위한 Google의 언어 — 을 받습니다:

'amount > 10000 && account.tier == "enterprise"'
'duration(now() - created_at) > duration("30d")'
'has(record.notes) && record.notes != ""'

CEL은 샌드박스로 격리되어 있고(부수 효과 없음, I/O 없음), 서버 측에서 평가되며, 플로우 빌더에서 감사가 가능합니다.

비주얼 빌더

Console에는 선언형 메타데이터와 양방향으로 변환되는 비주얼 플로우 빌더가 함께 제공됩니다 — 비엔지니어도 플로우를 편집할 수 있으며, 직접 손으로 작성하는 TypeScript와 동일한 구조로 다시 직렬화됩니다.

플로우 테스트

os test --scenario "welcome email fires on signup"

한계 및 모범 사례

  • before 훅은 작게 유지하세요. 쓰기 트랜잭션을 차단합니다.
  • 장시간 실행되는 스텝 대신 wait를 사용하세요. 슬립하는 플로우는 워커를 차단하지만, wait until은 워커를 풀로 반환합니다.
  • 독립적인 스텝에는 parallel을 사용하세요. 기본값은 순차 실행입니다.
  • 멱등성이 중요합니다. 재시도는 동일한 스텝을 두 번 실행할 수 있으므로, 외부 부수 효과는 중복을 제거해야 합니다(플로우 실행 id를 키로 사용하세요).
  • 감사에 민감한 액션. 권한을 변경하거나 레코드를 삭제하는 플로우는 스스로 sys_audit_log에 로그를 남겨야 합니다.

다음으로 볼 내용

On this page