워크플로 계약 (CommandType W)
워크플로는 transact가 서버에서 여러 거래를 순서대로 실행하고 단계 사이에 값을 전달하는 계약입니다. 화면이 거래를 여러 번 호출해 이어 붙이는 대신 한 번의 요청으로 단계 실행·매핑·검증을 서버가 맡습니다. 서버 모듈 설정과 일반 거래는 transact 모듈을 보세요.
언제 쓰나
| 상황 | 더 단순한 방법 |
|---|---|
| 한 SQL 계약 안에서 여러 문장을 한 트랜잭션으로 저장 | dbclient 계약의 여러 seq와 TransactionScope |
| 같은 모듈 안에서 앞 결과를 뒤 입력으로 연결 | 순차 거래(S) |
| 서로 다른 실행 모듈(D·G·F·P·C)과 하위 워크플로를 섞고 중간 결과를 검증 | 워크플로 |
단계마다 별개의 요청으로 실행되므로 워크플 로 전체가 하나의 DB 트랜잭션으로 롤백되지 않습니다. 단계 안에서 데이터를 바꾸는 서비스를 여러 개 호출하면 부분 성공이 남을 수 있으니 보상 단계나 멱등성을 설계하세요.
구문
거래 계약(.txn/.json)의 Services[] 항목에 CommandType: "W"와 WorkflowSteps를 둡니다. 파일 구조와 접근 제어(Authorize, Roles, AccessScreenID)는 일반 거래 계약과 같습니다.
{
"ServiceID": "GW01", "Authorize": true, "ReturnType": "Json", "CommandType": "W", "TransactionScope": false,
"AccessScreenID": ["ABC010"],
"WorkflowSteps": [
{
"StepID": "findUser", "CommandType": "D",
"TransactionProjectID": "ABC", "TransactionID": "ABC010", "ServiceID": "GD01",
"IncludeResult": false,
"OutputMappings": [ { "SourceFieldID": "UserNo", "TargetFieldID": "UserNo", "Required": true } ],
"Assertions": [
{ "Assert": "NotEmpty", "Collection": { "Source": "Step", "FieldID": "UserNo" }, "Message": "사용자를 찾을 수 없습니다" }
]
},
{
"StepID": "loadOrders", "CommandType": "D",
"TransactionProjectID": "ABC", "TransactionID": "ABC020", "ServiceID": "LD01",
"InputMappings": [
{ "SourceStepID": "findUser", "SourceFieldID": "UserNo", "TargetFieldID": "UserNo", "TargetInputIndex": 0, "Required": true }
]
}
],
"Inputs": [ { "ModelID": "Dynamic", "Fields": [], "TestValues": [], "DefaultValues": [], "Type": "Row", "BaseFieldMappings": [], "ParameterHandling": "Rejected" } ],
"Outputs": [ { "ModelID": "Dynamic", "Fields": [], "Type": "Form" } ]
}
단계 항목 (WorkflowSteps[])
| 이름 | 타입 | 기본값 | 의미 |
|---|---|---|---|
StepID | 문자열 | {CommandType}{순번 2자리} (예 D01) | 단계 이름. 워크플로 안에서 유일해야 하고 SourceStepID가 참조합니다 |
ApplicationID, TransactionProjectID, TransactionID, ServiceID | 문자열 | 현재 워크플로 계약·서비스 값 | 실행할 서비스. 다른 계약의 서비스도 호출할 수 있 습니다 |
CommandType | 문자열 | 대상 서비스의 값 | D·G·F·P·C는 RoutingCommandUri 규칙으로 실행 모듈에 전달됩니다. W는 하위 워크플로(재귀)이며 ApplicationID|ProjectID|TransactionID|ServiceID 경로로 순환 호출을 감지해 실패시킵니다 |
ReturnType, TransactionScope, ServiceOutputs | 대상 서비스 설정 | 이 단계에서만 대상 서비스 설정을 덮어씁니다 | |
IncludeResult | 불리언 | true | 이 단계 결과를 최종 응답에 포함할지 정합니다 |
InputMappings[] | 배열 | 빈 배열 | 이 단계 입력에 값을 채웁니다 |
OutputMappings[] | 배열 | 빈 배열 | 이 단계 결과 필드에 별칭을 추가합니다 |
Assertions[] | 배열 | 빈 배열 | 단계 실행 직후 결과를 검증합니다 |
매핑 항목 (InputMappings[], OutputMappings[])
| 이름 | 의미 |
|---|---|
SourceStepID | 값을 가져올 이전 단계. 비우면 지금까지 유지된 요청 payload |
SourceFieldID | 가져올 필드(대소문자 무시). 값이 객체(또는 첫 요소가 객체인 배열)면 Field.Property 또는 Property로도 참조합니다 |
TargetFieldID | 채울 필드. 비우면 SourceFieldID와 같음 |
TargetInputIndex | 대상 서비스 Inputs 중 값을 넣을 위치(기본 0). 입력 매핑에서만 의미가 있습니다 |
DefaultValue | 원본이 없을 때 쓸 값. 입력 매핑에서 SourceFieldID가 비어 있으면 이 값이 상수로 들어갑니다 |
Required | 원본도 DefaultValue도 없을 때 true면 단계가 실패합니다. false면 입력 매핑은 빈 문자열을 넣습니다 |
OutputMappings는 결과 필드의 별칭을 추가합니다. 원래 이름도 계속 참조할 수 있고, 이후 단계의 참조와 Assertions에만 영향을 주며 응답 dataSet은 바꾸지 않습니다. 이름을 바꿔 쓰거나 필수 값을 단계 안에서 확인할 때 씁니다.
실행 순서와 결과
- 요청 payload의 입력이 첫 단계의 기본 입력입니다. 단계가 끝나면 그 단계가 사용한 payload가 다음 단계의 기본 입력이 되고,
InputMappings는 이 payload 위에 값을 추가하거나 덮어씁니다. - 단계 결과는
ReturnType에 따라 필드 목록이 됩니다:Json은[{id, value}…]를 필드 목록으로,Scalar는Scalar,NonQuery는RowsAffected,Xml은Xml필드로 만들고, 배열이 아니면Result로 둡니다. 단계 결과 전체는 항상SourceStepID로 참조할 수 있습니다. - 각 단계 뒤에
Assertions를 순서대로 평가하고 첫 실패에서 워크플로를 중단합니다. - 최종 응답
result.dataSet은IncludeResult가true인 모든 단계의 비어 있지 않은 결과를{StepID}_{FieldID}이름으로 누적한 목록입니다. null, 빈 문자열, 빈 배열·객체는 제외합니다. 앞 단계 결과를 응답에서 빼려면IncludeResult: false로 둡니다. 요청의compressionYN이Y이면 객체·배열 값은 LZString Base64로 압축됩니다.
실패하면 exceptionText에 StepID '…' Workflow 실행 오류 - … 또는 Workflow 검증 오류 - … 형태로 담깁니다.
검증 (Assertions[])
값 참조 형식
Expected, Actual, Value, Min, Max, Collection은 모두 같은 형태입니다.
| 필드 | 의미 |
|---|---|
Source | Literal(기본), Request(워크플로가 처음 받은 요청 payload), Step(단계 결과) |
Value | Literal일 때의 값(문자열·숫자·불리언·객체·배열·null) |
FieldID | Request·Step에서 찾을 필드(대소문자 무시, 객체는 Field.Property) |
SourceStepID | Step에서 이전 단계 결과를 참조할 때 지정합니다. 생략하면 현재 단계 결과 |
Source를 생략해도 FieldID가 있고 Value가 없으면 Step으로 해석합니다(명시하는 편이 읽기 쉽습니다). 값을 찾지 못하면 FieldID '…' 검증 값 확인 필요로 실패합니다.
검증 종류 15종
Assert | 사용하는 필드 | 통과 조건 |
|---|---|---|
Equal / NotEqual | Expected, Actual | JSON 값 깊은 비교가 같다 / 다르다 |
True / False | Value | 불리언 또는 "true"/"false"로 변환되는 값 |
Null / NotNull | Value | JSON null 여부(빈 문자열은 null이 아님) |
Contains / DoesNotContain | Value, Collection | 문자열 포함, 배열 요소 일치, 객체 속성명·값 일치(문자열은 대소문자 구분) |
Empty / NotEmpty | Collection | 문자열 길이·배열 개수·객체 속성 수가 0 / 0이 아님(null은 빈 값으로 보 지 않음) |
Single | Collection | 길이·개수·속성 수가 정확히 1 |
InRange | Value, Min, Max | 숫자 또는 날짜가 Min ≤ Value ≤ Max |
Throws | ExceptionType(선택) | 단계가 실패했음을 기대합니다. 실패했고 타입·메시지에 문자열이 포함되면 성공으로 전환되어 다음 단계로 진행 |
IsType | Value, TypeName | JSON 타입 String, Integer/Int, Float, Number, Boolean/Bool, Object, Array, Null |
Same | Expected, Actual | 값이 아니라 같은 JSON 토큰 인스턴스(일반 비교는 Equal) |
모든 검증에 Message를 둘 수 있고, 실패하면 그 문구가 exceptionText가 됩니다. 알 수 없는 Assert 이름은 Assert '…' 확인 필요로 실패합니다.
"Assertions": [
{ "Assert": "NotEmpty", "Collection": { "Source": "Step", "FieldID": "ServerName" }, "Message": "ServerName 값이 비어 있 습니다" },
{ "Assert": "Equal", "Expected": { "Source": "Literal", "Value": "OK" }, "Actual": { "Source": "Step", "FieldID": "ResultCode" } },
{ "Assert": "InRange", "Value": { "Source": "Step", "FieldID": "Score" }, "Min": { "Value": 0 }, "Max": { "Value": 100 } },
{ "Assert": "Contains", "Value": { "Source": "Request", "FieldID": "Role" }, "Collection": { "Source": "Step", "SourceStepID": "loadRoles", "FieldID": "Roles" } },
{ "Assert": "Throws", "ExceptionType": "InvalidOperationException", "Message": "입력 매핑 오류가 발생해야 합니다" }
]
자주 하는 실수
NotEmpty·Empty·Single은Value가 아니라Collection필드를 읽습니다.NotEmpty는 null을 빈 값으로 보지 않아 통과합니다. null을 막으려면NotNull을 함께 쓰세요.Null·NotNull은Value를 읽습니다. 빈 문자열을 null로 기대하지 마세요.- 같은 이름의 필드가 여러 단계에 있으면
SourceStepID를 명시합니다(생략하면 현재 단계). Throws를 일반 단계에 넣으면 정상 실행 시 검증이 실패합니다. 실패가 기대되는 단계에만 쓰세요.
호출
- 엔드포인트는
POST /transact/api/workflow/execute이고 요청 전문은 거래 실행과 같습니다.transaction.commandType을"W"로 보냅니다. - 화면에서는
syn.$w.transactionAction('GW01', { endpoint: 'workflow', commandType: 'W' })처럼 호출합니다.endpoint옵션이 URL의/transaction/을/workflow/로 바꿉니다. 응답 개수가 화면outputs개수와 같아야 하는 점은 일반 거래와 같습니다. - 요청 검증은 일반 거래와 같습니다:
AllowRequestTransactions,PublicTransactions,AccessScreenID, 환경(AvailableEnvironment),SecurityHardening(유량 제한·입력 크기), 인증 토큰.
일회성 워크플로
헤더 X-Workflow-Contract에 TransactionInfo JSON을 실으면 계약 파일 없이 그 요청에서만 워크플로를 실행할 수 있습니다.
- BearerToken의
Policy.Claims에DynamicWorkflow=Y와DynamicWorkflowTransaction=<ProjectID|TransactionID>가 필요합니다. - 서버
module.json의DynamicWorkflowTransaction·DynamicWorkflowServices가 최종 허용 범위입니다. - 헤더의
ServiceID는 요청의functionID와 같아야 합니다.
서버 측 임의 흐름 실행을 여는 기능이므로 운영에서는 허용 범위를 최소화하고 EnforceWorkflowContractGuard를 켜세요.
설계할 때 확인할 것
- 단계로 나눌 서비스가 이미 계약으로 존재해야 합니다. 단계마다 입력 필드 이름과 결과 필드 이름을 표로 적고, 이름이 다르면
TargetFieldID로 맞춥니다. - 각 단계에 빈 결과, 필수 값, 범위를
Assertions로 두고 운영자가 원인을 알 수 있게Message를 씁니다. - 단계가 늘수록 장애 범위가 넓어집니다. 단계별
timeout(대상 서비스 설정), 폴백, 검증을 계약에 명시합니다. Authorize·Roles·AccessScreenID는 워크플로 서비스 자체에 적용되고, 각 단계의 대상 서비스에는 단계 실행 경로의 설정이 적용됩니다. 민감한 서비스를 워크플로로 우회 노출하지 않는지 확인하세요.- AI 파이프라인(의도 분류 → 함수 선 택 → 실행)은
P단계의PromptResult를OutputMappings로 다음 단계 입력에 넘기는 패턴이 샘플에 있습니다. 모델 출력은 비결정적이므로Assertions로 형식을 검증하고 실패 경로를 두세요.
관련 문서: 거래 호출, 식별자와 파일 경로, transact 모듈.