Journey Workflow

Multi-service orchestration — how journeys auto-generate workflows, create child applications, and propagate state via Pulsar.

A journey is a parent service that orchestrates multiple child services in a fixed sequence. The workflow is auto-generated from the journey mapping — you do not write transitions manually.

Flow overview

Journey mapping (JourneyMappingDto)

Saved via PUT /change-requests/{changeRequestId}/journey-mapping.

{
  "serviceSequence": [
    {
      "order": 1,
      "serviceCode": "BIRTH_CERT",
      "serviceName": "Birth Certificate",
      "changeRequestId": "<uuid>",
      "required": true,
      "serviceSource": "IREMBO_3_0",
      "attachmentsMapping": null,
      "repeatingFieldsMapping": null,
      "stateMapping": {
        "approve": "BIRTH_CERTIFICATE_APPROVED",
        "reject": "BIRTH_CERTIFICATE_REJECTED"
      }
    },
    {
      "order": 2,
      "serviceCode": "PASSPORT",
      "serviceName": "Passport",
      "changeRequestId": "<uuid>",
      "required": false,
      "serviceSource": "IREMBO_3_0",
      "attachmentsMapping": null,
      "repeatingFieldsMapping": null,
      "stateMapping": { "approve": "PASSPORT_APPROVED", "reject": "PASSPORT_REJECTED" }
    }
  ],
  "triggerParentMappings": [
    {
      "childServiceCode": "BIRTH_CERT",
      "childState": "BIRTH_CERTIFICATE_APPROVED",
      "parentEvent": "APPROVE"
    },
    {
      "childServiceCode": "BIRTH_CERT",
      "childState": "BIRTH_CERTIFICATE_REJECTED",
      "parentEvent": "REJECT"
    },
    { "childServiceCode": "PASSPORT", "childState": "PASSPORT_APPROVED", "parentEvent": "APPROVE" },
    { "childServiceCode": "PASSPORT", "childState": "PASSPORT_REJECTED", "parentEvent": "APPROVE" }
  ],
  "additionalApplicationSettings": {
    "hideFromUserList": true,
    "rfaOnParent": false
  }
}

Auto-generated states

JourneyWorkflowGeneratorUtil produces these states for each service in the sequence:

StateColorNotes
NEWprimaryFixed initial state
SERVICE_CREATION_PENDINGyellowFixed second state
<SERVICE_NAME>_PROCESSINGyellowOne per service
<SERVICE_NAME>_APPROVEDsuccessOne per service
<SERVICE_NAME>_REJECTEDdangerOne per service
<SERVICE_NAME>_NOT_APPLICABLEprimaryOptional services only
<JOURNEY_NAME>_COMPLETEDsuccessFinal success state
<JOURNEY_NAME>_REJECTEDdangerFinal failure state (required service rejected)

State names are derived from serviceName — uppercased with all non-word characters replaced by _.

Required vs optional services

ScenarioResult
Required service approvedContinues to next service
Required service rejectedJourney moves to terminal <JOURNEY_NAME>_REJECTED
Optional service approvedContinues to next service
Optional service rejectedContinues to next service
Optional service skipped (DO_YOU_NEED_<SERVICE_CODE> == "No")<SERVICE_NAME>_NOT_APPLICABLE → continues

APPLY_NORMAL_SERVICE breaking action

When the parent enters <SERVICE_NAME>_PROCESSING, this breaking action fires and creates the child application automatically:

{
  "actionType": "APPLY_NORMAL_SERVICE",
  "args": { "serviceCode": "BIRTH_CERT" }
}

The child is created with:

  • creatorType: SYSTEM, applicationChannel: WEB
  • parentApplicationId set to the parent's UUID
  • Form data copied from parentFormModel["BIRTH_CERT"] — the parent form must have the child's data nested under the serviceCode key
  • These values injected into applicationFormState:
{
  "PARENT_APPLICATION_ID": "<parent UUID>",
  "PARENT_APPLICATION_NUMBER": "<parent app number>",
  "SERVICE_CODE": "BIRTH_CERT",
  "APPLICANT_TYPE": "...",
  "APPLICATION_STATE": "NEW",
  "LOCALE": "en-US"
}

Parent-child trigger flow (Pulsar)

After every state transition of a child that has a parentApplicationId, the application engine sends a Pulsar message. The consumer (PulsarTriggerParentConsumer) then:

  1. Fetches the parent's journey mapping from service-mgmt-engine
  2. Finds the matching entry in triggerParentMappings for childServiceCode + childState
  3. If no match — acknowledges and does nothing
  4. If match — merges child data into parent and triggers workflowService.processApplication(parentEvent)

Trigger timing: fires after the breaking action and state persist, but before non-breaking actions (notifications) run.

Data merging

When child data is merged into the parent:

DataMerge strategy
formModelChild's form nested under formModel["SERVICE_CODE"]
applicationDetailsDeduplicated by key — child overwrites parent on conflict
userAttachmentsDeduplicated by attachmentName — child overwrites on conflict
certificatesAppended (no deduplication)

additionalApplicationSettings

Passed directly from the journey mapping to each child application at creation time.

KeyTypeEffect
hideFromUserListbooleanHides child from the applicant's application list
rfaOnParentbooleanRestricts RFA — child cannot initiate its own RFA

attachmentsMapping on serviceSequence

Maps officer attachments or certificates from one child service into another:

{
  "officerAttachment": [
    {
      "targetField": "BIRTH_CERT_SCAN",
      "sourceService": "BIRTH_CERT",
      "attachmentKey": "CERTIFICATE_SCAN"
    }
  ],
  "certificates": [
    {
      "language": "en",
      "targetField": "BIRTH_CERT_EN",
      "sourceService": "BIRTH_CERT",
      "certificateCode": "BIRTH_CERT_EN_CERT"
    }
  ]
}

serviceSource values

ValueMeaning
IREMBO_2_0Child service is on iRembo 2.0
IREMBO_2_5Child service is on iRembo 2.5
IREMBO_3_0Child service is on iRembo 3.0 (current)

API endpoints

MethodPathDescription
GET/change-requests/{changeRequestId}/journey-mappingFetch current journey mapping
PUT/change-requests/{changeRequestId}/journey-mappingSave mapping — triggers workflow + form auto-generation

On this page