빠른 시작¶
이 페이지는 windforce를 로컬에 띄우고 첫 잡을 5분 안에 돌리는 가장 짧은 경로를 보여 준다. docker compose로 스택을 기동하고, 워크스페이스를 부트스트랩하고, 간단한 hello 액션을 sync한 다음, curl로 잡을 실행하고 결과·로그를 받아 온다.
처음 한 번만 하면 되는 설치(스택 기동)와, 그 위에서 반복하는 작업(액션 작성 → sync → 실행)을 차례로 다룬다.
사전 준비¶
- Docker / Docker Compose — 스택 전체(Postgres·server·worker)를 컨테이너로 띄운다.
- 액션 소스를 담을 git 저장소 — 아래의
windforce.json과main.ts를 커밋해 둔 repo 하나(로컬·원격 어느 쪽이든, server·worker 컨테이너가 clone할 수 있으면 된다). curl정도의 HTTP 클라이언트.
호스트 5432 포트가 비어 있어야 한다 — 따로 떠 있는 Postgres가 있으면 먼저 내린다.
1. 스택 기동¶
레포 루트에서 한 줄로 전체 스택을 올린다. Postgres, DB 뷰어(Adminer), 1회성 마이그레이션·super-admin 시드, server(API·sync·reaper), worker가 함께 뜬다.
- API —
http://localhost:8080 - 콘솔(웹 UI) — 같은
http://localhost:8080(브라우저로 접속해 로그인). 첫 설치 super-admin은 compose 파일의SUPERADMIN_EMAIL/SUPERADMIN_PASSWORD(root@demo.test/correct-password)로 들어간다. - Adminer(DB 뷰어) —
http://localhost:8081(serverpostgres, user·password 모두windforce).
이 빠른 시작은 curl 중심으로 진행한다. 콘솔로 같은 일(앱·액션 보기, 잡 결과 확인)을 할 수도 있다 — 콘솔 사용법은 사용자 가이드를 참고한다.
2. 워크스페이스 부트스트랩¶
server 컨테이너에 부트스트랩 CLI가 내장돼 있다. 워크스페이스를 만들고(첫 admin도 함께 생성), 토큰을 발급하고, 액션 소스 repo를 git source로 등록한 뒤 sync한다.
# 워크스페이스 + 첫 admin 생성
docker compose exec server /app/windforce bootstrap-workspace demo "Demo" \
--admin-email dev@demo.test --admin-password correct-password --admin-username dev
# API 호출용 토큰 발급 (기본 scope "*")
docker compose exec server /app/windforce create-token demo dev@demo.test --admin
# 액션 소스가 있는 repo를 git source로 등록 (이름 "repo", 브랜치 main)
docker compose exec server /app/windforce add-gitsource demo repo <git-url> main
# clone → manifest 읽기 → 소스 materialize → 카탈로그 upsert
docker compose exec server /app/windforce sync demo 1
bootstrap-workspace <key> "<이름>"— 워크스페이스demo를 만들고 그 안에 첫 admin 계정을 둔다.create-token— 출력된 토큰 문자열을 다음 단계의curl에서Authorization: Bearer <token>으로 쓴다.add-gitsource demo repo <git-url> main— sync가 사용할 repo·브랜치를 등록한다.<git-url>을 액션 소스 repo로 바꾼다. 등록된 첫 git source의 id는1이다.sync demo 1— git source1을 clone해windforce.json(manifest)과 companion 스키마를 읽고, 소스를 오브젝트 캐시에 materialize한 뒤 app·action 카탈로그에 upsert한다. sync는 manifest와 스키마 파일만 읽고 코드(TS 등)는 파싱하지 않는다.
3. hello 액션 작성¶
sync 대상 repo에 아래 두 파일을 둔다. windforce.json이 app(Application Project)의 entrypoint·기본값을 선언하고, main.ts가 ctx를 받는 main 함수를 export한다.
windforce.json (app당 1개, repo의 app 루트에 위치):
{
"app": "greet", // app_key — 실행 주소의 {app}
"entrypoint": "main.ts", // app당 1개. 모든 action이 공유
"timeout": 60,
"tag": "default", // 잡을 default 워커 클래스로 라우팅
"actions": {
"hello": {} // action_key — 실행 주소의 {action}
}
}
main.ts (entrypoint = 조립):
import type { WindforceContext } from "windforce-client"
export async function main(ctx: WindforceContext) {
const input = (ctx.input ?? {}) as { name?: string }
ctx.logger.info(`hello ${input.name ?? "world"}`) // 로그 채널
return { message: `hi ${input.name ?? "world"}` } // JSON 직렬화 가능한 결과
}
핵심 계약 몇 가지:
- 입력은
ctx.input으로만 온다 — env나 입력 파일을 직접 읽지 않는다. 타입은unknown이라 저자가 자기 타입으로 좁힌다. - 결과는
return— JSON 직렬화 가능한 값이어야 한다(객체·배열·원시·null). 함수·순환참조는 직렬화 에러로 실패가 된다. - 로그는
ctx.logger.*또는console.*— 데이터 채널이 아니라 진단·관측용이다. 결과는 항상return으로 보낸다. - 실패는
throw— 던진 에러는{ message, name, stack }형태로 잡 결과에 기록된다.
액션이 여러 개라면 windforce.json의 actions에 키를 더 넣고, SDK createApp({ actions })로 action별 핸들러를 묶어 ctx.action으로 분기한다. 같은 계약을 Python·Go로도 쓸 수 있다(저자 표면만 언어별로 갈리고 실행 모델은 동일). 전체 계약(input·trigger·결과·에러·상태·시크릿·멀티파일·manifest)은 스크립트 개발자 계약에 정리돼 있다.
파일을 커밋·푸시한 뒤, 위 2단계의 sync demo 1을 다시 실행하면 카탈로그가 새 커밋으로 갱신된다.
4. 잡 실행 · 결과 · 로그¶
공개 실행 주소는 하나의 형태로 통일돼 있다: /api/w/{workspace}/jobs/run/{app_key}/{action_key}. <token>을 2단계에서 발급받은 값으로 바꾼다.
# 실행 — 즉시 job_id를 돌려준다 (비동기 큐로 들어간다)
curl -X POST localhost:8080/api/w/demo/jobs/run/greet/hello \
-H "Authorization: Bearer <token>" -d '{"name":"world"}'
# => {"job_id":"…"}
# 결과 — 잡이 끝나기 전이면 202를 돌려준다 (잠시 후 다시 호출)
curl localhost:8080/api/w/demo/jobs/<id>/result -H "Authorization: Bearer <token>"
# => {"status":"success","result":{"message":"hi world"}}
# 로그 — 잡이 stdout/stderr·ctx.logger로 남긴 출력
curl localhost:8080/api/w/demo/jobs/<id>/logs -H "Authorization: Bearer <token>"
<id>는 실행 응답의 job_id다. 실행은 비동기다 — run은 잡을 큐에 넣고 곧장 job_id를 돌려주고, 워커가 잡을 claim해 실행한 뒤 결과·로그를 영속 기록으로 남긴다. 프로덕션 배포에서는 워커가 2계층 샌드박스(gVisor + bubblewrap)로 격리 실행한다 — 샌드박싱 참고. 결과 조회는 완료 전이면 {"status":"pending"}과 HTTP 202를 받고, 성공하면 {"status":"success","result": …} 형태로 오며 result 안에 액션이 return한 값(여기서는 {"message":"hi world"})이 담긴다.
전체 흐름¶
flowchart TD
author["저자: windforce.json + main(ctx)<br/>git repo에 커밋"]
cli["bootstrap-workspace<br/>create-token · add-gitsource · sync"]
catalog["app / action 카탈로그<br/>(commit·entrypoint·스키마 self-pin)"]
run["POST /api/w/demo/jobs/run/greet/hello"]
queue["PG 큐<br/>(비동기)"]
worker["worker<br/>(잡 실행)"]
result["GET .../jobs/{id}/result · /logs"]
author --> cli
cli --> catalog
run --> queue
catalog --> queue
queue --> worker
worker --> result
더 보기¶
- 핵심 개념 — Workspace · App · Action · Job, sync와 카탈로그의 관계.
- 사용자 가이드 — 콘솔에서 앱·액션을 만들고 호출하는 법.
- 스크립트 개발자 계약 —
main(ctx)·입력·결과·에러·상태·시크릿·멀티파일·manifest의 전체 계약(TypeScript·Python·Go). - 아키텍처 정본 — 3 평면 + PG 큐, 정확성 불변식, 재현성.