Build CoreOrdered learning track

Workflow, Approval, and Case Lifecycle Engine

Learn Java Large Scale ERP - Part 017

Deep dive into workflow, approval, and case lifecycle engine design for large-scale Java ERP, including human tasks, escalation, delegation, SLA, audit evidence, idempotency, long-running orchestration, and defensible state transitions.

18 min read3443 words
PrevNext
Lesson 1734 lesson track0718 Build Core
#java#erp#workflow#approval+5 more

Part 017 — Workflow, Approval, and Case Lifecycle Engine

1. Target Skill Part Ini

Workflow ERP bukan sekadar fitur “approve/reject”. Dalam ERP besar, workflow adalah control plane yang mengatur kapan sebuah dokumen boleh bergerak, siapa boleh mengambil keputusan, apa bukti yang harus tersimpan, bagaimana eskalasi terjadi, bagaimana delegasi bekerja, bagaimana SLA dihitung, dan bagaimana sistem tetap konsisten ketika user, scheduler, integrasi eksternal, dan batch process bergerak bersamaan.

Skill inti part ini: mampu mendesain workflow, approval, dan case lifecycle engine dalam Java ERP yang deterministic, auditable, resilient, idempotent, policy-aware, dan bisa dipakai lintas domain tanpa mengorbankan invariant domain.

Kesalahan umum adalah membuat workflow sebagai kolom status sederhana:

purchase_order.status = WAITING_APPROVAL
purchase_order.approved_by = user_id

Untuk aplikasi kecil, itu cukup. Untuk ERP besar, model seperti itu cepat runtuh karena tidak bisa menjawab:

  • siapa approver yang seharusnya saat keputusan dibuat;
  • apakah approver punya authority terhadap legal entity, branch, cost center, amount, dan commodity;
  • apakah approver sedang dalam conflict of interest atau segregation-of-duties violation;
  • apakah approval dibuat sebelum atau sesudah perubahan nilai dokumen;
  • apakah approval berlaku untuk versi dokumen yang sama;
  • apakah task di-reassign, didelegasikan, atau dieskalasi;
  • apakah keputusan bisa dipertanggungjawabkan ketika ada audit atau dispute;
  • apakah retry dari API menyebabkan duplicate approval;
  • apakah workflow masih valid ketika rule berubah di tengah proses;
  • apakah dokumen boleh di-cancel ketika ada approval task aktif;
  • apakah posting downstream boleh berjalan saat approval belum final.

Workflow ERP harus dipahami sebagai kombinasi dari:

workflow definition
+ workflow instance
+ business document lifecycle
+ authorization decision
+ assignment policy
+ SLA/timer policy
+ audit evidence
+ immutable transition history
+ domain invariant guard

2. Kaufman Deconstruction: Memecah Skill Workflow ERP

Mengikuti pendekatan Josh Kaufman, kita tidak belajar workflow sebagai satu topik besar. Kita pecah menjadi sub-skill yang bisa dilatih dan divalidasi.

Sub-skillPertanyaan yang Harus Bisa DijawabOutput Engineering
Lifecycle modellingDokumen melewati state apa saja?state diagram, transition matrix
Approval topologyApproval linear, parallel, quorum, atau conditional?workflow graph, join policy
AssignmentTask diberikan ke siapa dan berdasarkan apa?candidate rule, authority scope
AuthorizationSiapa boleh melakukan action apa pada state apa?policy decision, SoD rule
EvidenceBukti apa yang harus tersimpan?immutable decision log, input snapshot
Timer/SLAKapan task overdue, escalate, auto-close, atau remind?timer job, escalation rule
DelegationApa beda delegate, reassign, proxy, substitute?delegation model, audit link
VersioningApa yang terjadi jika workflow definition berubah?definition snapshot, migration policy
IdempotencyBagaimana retry tidak membuat keputusan ganda?command id, decision deduplication
ConcurrencyBagaimana dua approver submit bersamaan?lock, transition guard, version check
IntegrationBagaimana workflow memanggil service eksternal?outbox, command handler, callback
ObservabilityBagaimana operator tahu task stuck?metrics, search, dashboard, alert
RecoveryBagaimana membetulkan workflow salah?admin correction with evidence

Target kita bukan hafal BPMN atau library tertentu. Targetnya adalah mampu melihat workflow sebagai sistem kendali atas transaksi ERP.


3. Mental Model: Workflow Bukan Owner Kebenaran Domain

Prinsip paling penting:

Workflow engine mengatur urutan dan otorisasi gerak, tetapi domain aggregate tetap menjaga kebenaran bisnis.

Contoh: purchase order tidak boleh diposting hanya karena workflow berkata approved. Domain PurchaseOrder tetap harus memvalidasi:

  • total masih sama dengan versi yang disetujui;
  • vendor masih aktif;
  • budget masih tersedia;
  • period belum locked;
  • currency valid;
  • line item belum berubah tanpa re-approval;
  • approval decision berlaku untuk document revision yang tepat.

Workflow adalah control plane. Domain adalah system of record untuk invariant.

Desain yang sehat:

Workflow can say: “approval path is complete.”
Domain must still say: “this document is valid to post now.”

Desain yang berbahaya:

if workflow.status == APPROVED:
    postToLedger(document)

Karena APPROVED bisa stale terhadap dokumen yang berubah, rule yang berubah, organization scope yang berubah, atau period yang terkunci.


4. Workflow vs Approval vs Case Lifecycle vs State Machine

Empat konsep ini sering dicampur.

KonsepFokusContohRisiko Jika Dicampur
State machineLegal transition satu entityPO: Draft → Submitted → Approvedstate menjadi ambigu
WorkflowOrchestration multi-stepapproval routing + notification + escalationdomain logic bocor ke process graph
ApprovalHuman/business decisionmanager approves amount > 100Mapproval tidak punya evidence
Case lifecycleKoordinasi exception/non-linear workinvoice discrepancy casetidak ada ownership dan SLA

Dalam ERP besar:

  • state machine menjaga lifecycle dokumen;
  • workflow mengatur proses yang mungkin melibatkan banyak actor dan system;
  • approval adalah jenis task/decision dalam workflow;
  • case lifecycle adalah workflow yang lebih fleksibel, sering dipakai untuk exception handling, dispute, investigation, dan remediation.

Contoh mapping:

PurchaseOrder aggregate:
  state = SUBMITTED

Workflow instance:
  process = PO_APPROVAL_V7
  active node = FINANCE_REVIEW

Approval task:
  assigned to = finance-manager-group
  decision = pending

Case:
  type = BUDGET_EXCEPTION
  owner = procurement operations
  SLA = 2 business days

5. Reference Architecture Workflow Engine ERP

Workflow engine ERP bisa dibangun internal atau menggunakan platform seperti BPMN/workflow engine. Di kedua pilihan, boundary-nya harus jelas.

Komponen Utama

KomponenTanggung JawabTidak Boleh Melakukan
Definition Registrymenyimpan workflow template/versionmengubah instance historis diam-diam
Instance Runtimemenjalankan node/transitionmengabaikan domain guard
Task Servicemembuat, assign, complete human taskmenyimpan business truth final
Assignment Enginemenentukan candidate/assigneebypass authorization
Timer/SLA Enginereminder, overdue, escalationmelakukan keputusan bisnis tanpa policy
Delegation Servicesubstitute/reassign/proxymenghapus jejak actor asli
Workflow Auditmencatat command, decision, transitionmutable log
Domain Guard Servicevalidasi invariant domainmenjadi workflow engine baru

6. Data Model Inti Workflow

Workflow ERP perlu model yang eksplisit. Jangan menyimpan workflow dalam JSON blob tunggal tanpa index dan audit path.

6.1 Core Entities

6.2 Minimal Relational Schema

create table workflow_definition (
    id uuid primary key,
    code varchar(120) not null unique,
    domain_type varchar(120) not null,
    name varchar(240) not null,
    status varchar(40) not null,
    created_at timestamptz not null,
    created_by uuid not null
);

create table workflow_version (
    id uuid primary key,
    definition_id uuid not null references workflow_definition(id),
    version_no integer not null,
    publication_state varchar(40) not null,
    effective_from timestamptz not null,
    effective_to timestamptz,
    definition_json jsonb not null,
    checksum varchar(128) not null,
    created_at timestamptz not null,
    created_by uuid not null,
    unique (definition_id, version_no)
);

create table workflow_instance (
    id uuid primary key,
    workflow_version_id uuid not null references workflow_version(id),
    business_object_type varchar(120) not null,
    business_object_id uuid not null,
    business_revision integer not null,
    instance_state varchar(40) not null,
    current_node_key varchar(120),
    correlation_id varchar(160) not null,
    idempotency_key varchar(160) not null,
    version integer not null default 0,
    started_at timestamptz not null,
    completed_at timestamptz,
    unique (business_object_type, business_object_id, business_revision),
    unique (correlation_id),
    unique (idempotency_key)
);

create table workflow_task (
    id uuid primary key,
    workflow_instance_id uuid not null references workflow_instance(id),
    node_key varchar(120) not null,
    task_type varchar(80) not null,
    task_state varchar(40) not null,
    assignment_mode varchar(40) not null,
    assigned_user_id uuid,
    candidate_group varchar(160),
    due_at timestamptz,
    opened_at timestamptz not null,
    completed_at timestamptz,
    version integer not null default 0
);

create table workflow_decision (
    id uuid primary key,
    workflow_task_id uuid not null references workflow_task(id),
    decision varchar(40) not null,
    actor_id uuid not null,
    actor_delegation_id uuid,
    decided_at timestamptz not null,
    reason_code varchar(120),
    comment text,
    input_snapshot_hash varchar(128) not null,
    idempotency_key varchar(160) not null,
    unique (workflow_task_id, actor_id, idempotency_key)
);

create table workflow_event_log (
    id uuid primary key,
    workflow_instance_id uuid not null references workflow_instance(id),
    event_type varchar(120) not null,
    event_time timestamptz not null,
    actor_id uuid,
    payload jsonb not null,
    hash varchar(128) not null,
    previous_hash varchar(128)
);

Catatan penting:

  • business_revision membuat approval terikat pada versi dokumen.
  • workflow_version_id memastikan instance tidak berubah ketika workflow definition baru dipublish.
  • version untuk optimistic locking.
  • idempotency_key untuk command retry.
  • workflow_event_log sebaiknya append-only.

7. Workflow Definition: Declarative, Versioned, and Validated

Workflow definition harus diperlakukan sebagai artifact governance, bukan konfigurasi bebas tanpa kontrol.

Contoh DSL internal sederhana:

{
  "code": "PO_APPROVAL",
  "version": 7,
  "start": "SUBMIT",
  "nodes": [
    {
      "key": "BUDGET_REVIEW",
      "type": "approval",
      "candidateRule": "budget.owner(document.costCenter)",
      "dueInHours": 24,
      "transitions": [
        { "event": "APPROVE", "to": "FINANCE_REVIEW" },
        { "event": "REJECT", "to": "REJECTED" },
        { "event": "REQUEST_CHANGE", "to": "DRAFT_RETURNED" }
      ]
    },
    {
      "key": "FINANCE_REVIEW",
      "type": "approval",
      "candidateRule": "finance.approver(document.legalEntity, document.amount)",
      "guard": "document.total >= 100000000",
      "transitions": [
        { "event": "APPROVE", "to": "APPROVED" },
        { "event": "REJECT", "to": "REJECTED" }
      ]
    }
  ]
}

Definition validation wajib dilakukan sebelum publish:

ValidationTujuan
semua node reachablemencegah dead node
semua transition target validmencegah broken graph
terminal state eksplisitmencegah workflow menggantung
tidak ada infinite loop tanpa exit conditionmencegah livelock
semua approval node punya assignment rulemencegah orphan task
guard expression terdaftar dan typedmencegah runtime script chaos
SLA policy validmencegah timer tak terukur
SoD policy attachedmencegah approval abuse
migration policy jelasmencegah instance lama rusak

Mermaid view:


8. Approval Topology

Approval topology menentukan bagaimana keputusan dikumpulkan.

8.1 Linear Approval

Satu task selesai, lalu task berikutnya dibuat.

Requester -> Supervisor -> Finance -> Director

Cocok untuk:

  • small amount approval;
  • authority berjenjang;
  • dokumen yang butuh clear accountability.

Risiko:

  • bottleneck;
  • SLA panjang;
  • sulit parallelize.

8.2 Parallel Approval

Beberapa approver menilai bersamaan.

Cocok untuk:

  • contract approval;
  • high-value procurement;
  • project budget;
  • vendor onboarding.

Risiko:

  • partial reject semantics harus jelas;
  • perubahan dokumen di tengah review harus invalidate semua task atau sebagian;
  • join condition harus deterministic.

8.3 Quorum Approval

Approval sah jika jumlah/weight tertentu terpenuhi.

2 of 3 directors approve
or
approval weight >= 70%

Cocok untuk:

  • committee approval;
  • board-like decision;
  • risk exception.

Risiko:

  • abstain, timeout, dan reject harus punya semantic jelas;
  • SoD lebih kompleks;
  • audit harus menunjukkan quorum pada saat keputusan final.

8.4 Conditional Approval

Path bergantung pada data dokumen.

if amount <= 10M: supervisor
if amount > 10M and <= 100M: supervisor + finance
if amount > 100M: supervisor + finance + director
if item.category == "CAPEX": asset controller
if vendor.risk == HIGH: compliance review

Risiko paling besar adalah rule drift: path yang dihitung hari ini berbeda dari path saat dokumen disubmit. Karena itu workflow instance harus menyimpan routing snapshot.


9. Assignment Engine

Assignment bukan hanya assigned_to.

Model assignment yang sehat mencakup:

candidate set
+ eligibility rule
+ authority limit
+ organization scope
+ availability
+ delegation
+ conflict rule
+ final assignee claim/selection

9.1 Candidate Rule

public interface AssignmentRule {
    CandidateSet resolve(AssignmentContext context);
}

public record AssignmentContext(
        UUID tenantId,
        UUID legalEntityId,
        UUID branchId,
        UUID costCenterId,
        String documentType,
        UUID documentId,
        int documentRevision,
        Money amount,
        String currency,
        UUID requesterId,
        Instant submittedAt
) {}

public record CandidateSet(
        Set<UUID> userIds,
        Set<String> groups,
        String ruleCode,
        String explanation
) {}

9.2 Eligibility Filter

Candidate belum tentu eligible.

Filter umum:

  • user aktif;
  • user punya role yang tepat;
  • user berada dalam legal entity/branch scope;
  • authority limit cukup;
  • tidak sedang cuti atau unavailable;
  • bukan requester;
  • bukan creator/vendor maintainer/payment preparer untuk SoD tertentu;
  • tidak punya delegation conflict;
  • tidak melebihi approval workload limit jika ada.
public interface EligibilityPolicy {
    EligibilityDecision evaluate(UserCandidate candidate, ApprovalContext context);
}

public record EligibilityDecision(
        boolean allowed,
        List<String> reasons,
        Map<String, Object> evidence
) {}

9.3 Claim vs Direct Assignment

ModelCara KerjaCocok UntukRisiko
Direct assignmenttask langsung ke userownership jelasbottleneck ketika user tidak available
Candidate groupbanyak user bisa claimops queueperlu race control
Load-balancedsistem memilih userhigh-volume caseassignment harus explainable
Rule-based hierarchyberdasarkan manager/authorityapproval formalhierarchy stale

Claim task harus atomic:

update workflow_task
set assigned_user_id = :userId,
    assignment_status = 'CLAIMED',
    version = version + 1
where id = :taskId
  and assignment_status = 'UNCLAIMED'
  and version = :expectedVersion;

Jika update count = 0, berarti task sudah diklaim atau berubah.


10. Authorization and SoD in Workflow

Workflow authorization harus mengevaluasi action, bukan hanya halaman.

Can actor X approve task Y for document Z at revision R under context C?

Input minimal:

  • actor identity;
  • delegated identity jika ada;
  • task id;
  • workflow instance id;
  • business object id + revision;
  • requested action;
  • current state;
  • legal entity/branch/cost center;
  • amount/currency;
  • role/scope;
  • SoD history;
  • approval authority limit.
public enum WorkflowAction {
    SUBMIT,
    CLAIM,
    APPROVE,
    REJECT,
    REQUEST_CHANGE,
    DELEGATE,
    REASSIGN,
    ESCALATE,
    CANCEL,
    ADMIN_CORRECT
}

public interface WorkflowAuthorizationService {
    AuthorizationDecision canPerform(WorkflowActor actor,
                                     WorkflowAction action,
                                     WorkflowTask task,
                                     BusinessObjectSnapshot snapshot);
}

SoD Example

public final class MakerCheckerPolicy {
    public PolicyDecision evaluate(WorkflowActor actor, BusinessObjectSnapshot doc) {
        if (actor.userId().equals(doc.createdBy())) {
            return PolicyDecision.deny("MAKER_CANNOT_APPROVE_OWN_DOCUMENT");
        }
        if (doc.hasLineModifiedBy(actor.userId())) {
            return PolicyDecision.deny("LINE_EDITOR_CANNOT_FINAL_APPROVE");
        }
        return PolicyDecision.allow();
    }
}

SoD evidence harus disimpan, bukan hanya dievaluasi lalu hilang:

{
  "policy": "MAKER_CHECKER_PO_APPROVAL",
  "result": "DENY",
  "reason": "MAKER_CANNOT_APPROVE_OWN_DOCUMENT",
  "actorId": "...",
  "documentId": "...",
  "documentRevision": 4,
  "evaluatedAt": "2026-06-30T10:15:30Z"
}

11. Decision Handling: Idempotent, Atomic, Auditable

Approval command harus diproses sebagai command dengan idempotency key.

public record CompleteTaskCommand(
        UUID taskId,
        UUID actorId,
        String decision,
        String reasonCode,
        String comment,
        String idempotencyKey,
        int expectedTaskVersion,
        int expectedBusinessRevision
) {}

Flow:

Atomic transaction boundary:

insert decision
+ append workflow event
+ close task
+ update workflow instance
+ maybe open next task
+ write outbox event

Semua harus commit bersama. External notification dikirim setelah commit melalui outbox.


12. Guarding Against Stale Approval

Approval harus mengikat pada document revision.

Contoh masalah:

  1. PO total = 50 juta.
  2. Supervisor approve.
  3. Requester mengubah PO menjadi 500 juta.
  4. Sistem masih menganggap approved.

Solusi:

approval decision applies to document revision N only
if document changes, create revision N+1
if change affects approval-relevant fields, invalidate approval or re-route

Field yang biasanya approval-relevant:

  • amount;
  • vendor/customer;
  • item/category;
  • quantity;
  • payment terms;
  • delivery location;
  • tax treatment;
  • cost center/project;
  • attachment contract;
  • price override;
  • budget allocation.
public boolean requiresReapproval(DocumentRevision oldRev, DocumentRevision newRev) {
    return !Objects.equals(oldRev.vendorId(), newRev.vendorId())
        || oldRev.totalAmount().compareTo(newRev.totalAmount()) != 0
        || !Objects.equals(oldRev.costCenterId(), newRev.costCenterId())
        || !Objects.equals(oldRev.paymentTerms(), newRev.paymentTerms())
        || !Objects.equals(oldRev.approvalRelevantAttachmentHash(), newRev.approvalRelevantAttachmentHash());
}

13. Escalation and SLA Engine

SLA bukan hanya reminder. SLA menentukan operational accountability.

13.1 SLA Model

public record SlaPolicy(
        String code,
        Duration firstReminderAfter,
        Duration dueAfter,
        Duration escalateAfter,
        BusinessCalendar calendar,
        EscalationTarget escalationTarget,
        boolean pauseOnRequesterActionRequired
) {}

SLA harus memperhitungkan:

  • business calendar;
  • timezone legal entity atau branch;
  • holiday;
  • working hours;
  • pause condition;
  • re-open behavior;
  • escalation chain;
  • exception priority.

13.2 Timer Persistence

Jangan mengandalkan in-memory timer untuk ERP.

create table workflow_timer (
    id uuid primary key,
    workflow_instance_id uuid not null,
    workflow_task_id uuid,
    timer_type varchar(80) not null,
    fire_at timestamptz not null,
    status varchar(40) not null,
    attempts integer not null default 0,
    locked_by varchar(120),
    locked_until timestamptz,
    created_at timestamptz not null
);

Worker mengambil timer secara lease-based:

update workflow_timer
set locked_by = :workerId,
    locked_until = now() + interval '2 minutes'
where id in (
    select id
    from workflow_timer
    where status = 'READY'
      and fire_at <= now()
      and (locked_until is null or locked_until < now())
    order by fire_at
    limit 100
    for update skip locked
)
returning *;

13.3 Escalation Semantics

ActionMeaningAudit Impact
remindnotify same assigneeno ownership change
escalate visibilitynotify managertask remains same
reassignmove task to another actor/groupassignment history required
delegateactor intentionally gives authoritydelegation evidence required
auto-rejectsystem decision after timeouthigh-risk, needs explicit policy
auto-approveusually dangerousonly for low-risk, explicit rule

ERP besar sebaiknya sangat hati-hati dengan auto-approve. Untuk financial, procurement, vendor, payment, dan inventory adjustment, auto-approve sering tidak defensible kecuali policy-nya sangat terbatas dan disetujui governance.


14. Delegation, Substitution, and Reassignment

Tiga hal ini berbeda.

MekanismeSiapa MemulaiArtiContoh
Delegationoriginal approveruser memberi wewenang sementaracuti 5 hari
Substitutionpolicy/adminsistem menunjuk penggantimanager inactive
Reassignmentsupervisor/admin/queue ownertask dipindahkanworkload balancing

Delegation model:

create table workflow_delegation (
    id uuid primary key,
    delegator_user_id uuid not null,
    delegate_user_id uuid not null,
    scope_type varchar(80) not null,
    scope_id uuid,
    action_scope varchar(120) not null,
    effective_from timestamptz not null,
    effective_to timestamptz not null,
    status varchar(40) not null,
    reason text,
    approved_by uuid,
    created_at timestamptz not null,
    constraint no_self_delegation check (delegator_user_id <> delegate_user_id)
);

Rules:

  • delegation harus time-bound;
  • delegation harus scoped;
  • delegation tidak boleh bypass SoD;
  • decision log harus mencatat actor asli dan delegated actor;
  • delegation chain sebaiknya dibatasi;
  • delegation untuk high-risk approval bisa membutuhkan approval tambahan.

15. Case Lifecycle Engine

Case lifecycle dipakai ketika proses tidak linear dan membutuhkan koordinasi exception.

Contoh case ERP:

  • invoice mismatch;
  • vendor dispute;
  • payment exception;
  • stock discrepancy;
  • failed posting;
  • credit limit exception;
  • duplicate master data investigation;
  • tax exception;
  • regulatory inquiry;
  • migration defect remediation.

Case berbeda dari workflow biasa karena:

  • bisa memiliki banyak task paralel dan ad-hoc;
  • bisa di-reopen;
  • punya owner dan priority;
  • punya SLA dan status resolution;
  • sering menyimpan evidence, note, attachment, dan decision;
  • lebih dekat ke operational support dan compliance.

15.1 Case States

15.2 Case Record Model

public record CaseRecord(
        UUID id,
        String caseType,
        String severity,
        String state,
        UUID businessObjectId,
        String businessObjectType,
        UUID ownerUserId,
        String ownerGroup,
        Instant openedAt,
        Instant dueAt,
        Instant closedAt,
        List<CaseAction> actions
) {}

Case action harus append-only:

COMMENT_ADDED
ATTACHMENT_ADDED
OWNER_CHANGED
STATUS_CHANGED
ROOT_CAUSE_SET
RESOLUTION_SET
LINKED_DOCUMENT_ADDED
WORKFLOW_TASK_CREATED
ESCALATED
CLOSED
REOPENED

16. Long-Running Workflow and External Systems

ERP workflow sering berlangsung selama jam, hari, bahkan minggu. Jangan membungkusnya dalam database transaction panjang.

Pattern:

short DB transaction per step
+ durable workflow state
+ durable timer
+ outbox event
+ idempotent external call
+ callback correlation

16.1 External Service Task

Misalnya approval high-risk vendor memanggil compliance screening.

Rule:

  • workflow tidak boleh menunggu synchronous HTTP call dalam transaction utama;
  • external call harus idempotent;
  • callback harus punya correlation id;
  • timeout harus menghasilkan case atau exception path;
  • hasil external harus disnapshot.

17. Java Package Design

Contoh struktur package:

com.company.erp.workflow
  api
    WorkflowCommandController
    WorkflowQueryController
  application
    StartWorkflowUseCase
    CompleteTaskUseCase
    ClaimTaskUseCase
    EscalateTaskUseCase
    ReassignTaskUseCase
  domain
    WorkflowInstance
    WorkflowTask
    WorkflowDefinition
    WorkflowTransition
    WorkflowEvent
    WorkflowGuard
    WorkflowPolicy
  assignment
    AssignmentEngine
    CandidateResolver
    EligibilityPolicy
  authorization
    WorkflowAuthorizationService
    SodPolicy
    AuthorityLimitPolicy
  timer
    WorkflowTimerWorker
    SlaPolicy
    EscalationPolicy
  caseflow
    CaseRecord
    CaseAction
    CaseLifecycleService
  persistence
    WorkflowInstanceRepository
    WorkflowTaskRepository
    WorkflowEventLogRepository
  integration
    WorkflowOutboxPublisher
    NotificationPort
    DomainGuardPort

Dependency direction:

api -> application -> domain
application -> ports
infrastructure -> ports

Domain workflow tidak boleh import controller, JPA entity detail, Kafka client, atau HTTP client.


18. Core Application Service Example

@Transactional
public CompleteTaskResult completeTask(CompleteTaskCommand command) {
    var existing = idempotencyRepository.find(command.idempotencyKey());
    if (existing.isPresent()) {
        return existing.get().toCompleteTaskResult();
    }

    WorkflowTask task = taskRepository.getForUpdate(command.taskId());
    WorkflowInstance instance = instanceRepository.getForUpdate(task.instanceId());

    task.assertVersion(command.expectedTaskVersion());
    task.assertCompletable();

    BusinessObjectSnapshot snapshot = domainSnapshotPort.load(
            instance.businessObjectType(),
            instance.businessObjectId()
    );

    if (snapshot.revision() != command.expectedBusinessRevision()) {
        throw new StaleBusinessRevisionException(instance.businessObjectId());
    }

    AuthorizationDecision authz = authorizationService.canPerform(
            WorkflowActor.user(command.actorId()),
            WorkflowAction.valueOf(command.decision()),
            task,
            snapshot
    );
    authz.throwIfDenied();

    Transition transition = transitionResolver.resolve(
            instance,
            task,
            command.decision(),
            snapshot
    );

    domainGuardPort.validateTransition(instance, transition, snapshot);

    WorkflowDecision decision = task.complete(
            command.actorId(),
            command.decision(),
            command.reasonCode(),
            command.comment(),
            snapshot.hash(),
            command.idempotencyKey()
    );

    List<WorkflowTask> nextTasks = instance.apply(transition, decision, clock.instant());

    decisionRepository.save(decision);
    taskRepository.save(task);
    instanceRepository.save(instance);
    nextTasks.forEach(taskRepository::save);

    eventLogRepository.append(instance.id(), WorkflowEvent.taskCompleted(decision, authz.evidence()));
    outboxRepository.save(WorkflowOutbox.taskCompleted(instance, decision));

    idempotencyRepository.save(command.idempotencyKey(), CompleteTaskResult.accepted(instance.id()));

    return CompleteTaskResult.accepted(instance.id());
}

Hal yang sengaja terlihat:

  • idempotency check di awal;
  • lock instance/task;
  • version check;
  • business revision check;
  • authorization + SoD evidence;
  • domain guard;
  • append event log;
  • outbox;
  • idempotency result persisted.

19. Workflow Query Model

Jangan query operational workflow langsung dari banyak join OLTP untuk dashboard besar.

Buat read model:

workflow_task_search
- task_id
- instance_id
- business_object_type
- business_object_number
- business_object_summary
- legal_entity_id
- branch_id
- cost_center_id
- task_state
- assigned_user_id
- candidate_group
- priority
- due_at
- overdue_flag
- amount
- currency
- created_at

Update read model melalui outbox/CDC/event handler.

Search screen umum:

  • My pending tasks;
  • Group queue;
  • Overdue tasks;
  • High value approvals;
  • Escalated items;
  • Workflow instances by document;
  • Stuck workflow;
  • Completed decisions by actor;
  • Admin correction log.

20. Observability: Business-Level Signals

Technical observability tidak cukup.

Metrics penting:

workflow_instances_started_total{definition,version,domain}
workflow_instances_completed_total{definition,version,domain,outcome}
workflow_tasks_open_total{task_type,group,legal_entity}
workflow_tasks_overdue_total{task_type,group,severity}
workflow_task_completion_seconds{task_type,group}
workflow_escalations_total{policy,level}
workflow_authorization_denied_total{policy,reason}
workflow_stale_revision_rejected_total{domain}
workflow_timer_lag_seconds{worker}
workflow_outbox_pending_total{event_type}

Logs harus mengandung correlation id:

{
  "event": "workflow.task.completed",
  "correlationId": "PO-2026-000123:r4",
  "workflowInstanceId": "...",
  "taskId": "...",
  "actorId": "...",
  "decision": "APPROVE",
  "documentType": "PURCHASE_ORDER",
  "documentId": "...",
  "businessRevision": 4
}

Trace boundary:

HTTP request
 -> command handler
 -> authorization decision
 -> workflow transition
 -> domain guard
 -> DB commit
 -> outbox publish
 -> notification worker

21. Admin Correction Without Destroying Evidence

ERP workflow butuh admin tooling. Tetapi admin tooling tidak boleh menjadi backdoor yang menghapus fakta historis.

Admin action yang defensible:

  • force reassign with reason;
  • cancel duplicate workflow instance;
  • reopen task due to system error;
  • attach missing evidence;
  • mark external callback as failed and retry;
  • migrate instance to new definition version under approved migration plan;
  • create corrective transition event.

Admin action yang berbahaya:

  • update status langsung di database;
  • delete approval task;
  • edit decision actor;
  • mengubah timestamp approval;
  • mengganti workflow version tanpa event;
  • menghapus evidence yang sudah dipakai audit.

Correction harus berupa event baru:

wrong event remains visible
correction event explains why and who approved correction
current projection reflects corrected state

22. BPMN, Internal Engine, or Durable Workflow Platform?

Ada tiga pendekatan umum.

OptionKelebihanKekuranganCocok Untuk
Internal lightweight enginesangat cocok dengan domain ERPperlu build tooling sendiriapproval/stateful document workflow
BPMN enginevisual, standard-ish, powerful orchestrationbisa overkill dan domain bocor ke diagramcomplex cross-system process
Durable workflow platformresilient long-running orchestrationdeterminism constraints, operational model barusystem orchestration, async retries

Keputusan praktis:

  • Untuk approval dokumen ERP yang sangat domain-specific, internal engine sering lebih terkendali.
  • Untuk proses lintas sistem yang panjang dan divisualisasikan oleh business analyst, BPMN engine bisa masuk akal.
  • Untuk orchestration teknis yang butuh replay, retry, durable execution, dan worker model, durable workflow platform bisa masuk akal.

Tetapi jangan membiarkan engine eksternal menjadi satu-satunya sumber kebenaran lifecycle dokumen ERP. Dokumen tetap harus punya state dan transition guard sendiri.


23. Failure Modes

Failure ModeGejalaAkar MasalahMitigasi
Duplicate approvaltask completed dua kaliretry tanpa idempotencyidempotency key + unique constraint
Stale approvaldokumen berubah setelah approveapproval tidak bind ke revisionrevision snapshot + reapproval rule
Orphan tasktask aktif tapi instance selesaitransition tidak atomicsingle transaction + invariant check
Ghost workflowinstance aktif untuk dokumen cancelleddomain lifecycle tidak sinkroncancellation protocol
Escalation stormribuan remindertimer retry tidak controlledlease, backoff, dedup
SoD bypassmaker approve sendiriauthz hanya role-basedpolicy decision with evidence
Definition driftinstance lama ikut rule baruno version snapshotworkflow version immutable
Approval black boxauditor tidak tahu alasanno decision traceevidence snapshot
Manual DB fixstatus benar tapi audit rusakno correction mechanismadmin correction events
Reporting overloaddashboard lambatquery OLTP directlyread model/search index

24. Anti-Patterns

24.1 Status Column as Workflow Engine

status = APPROVED

Tanpa task, decision, assignee, evidence, routing, dan revision, status ini tidak cukup.

24.2 Workflow Owns Domain Invariant

Jika BPMN diagram menentukan apakah stock boleh keluar, domain menjadi rapuh. Workflow boleh memanggil guard, bukan menggantikan guard.

24.3 Dynamic Script Everywhere

Script-based workflow terdengar fleksibel, tetapi tanpa typed context, validation, versioning, test, dan sandbox, ia akan menjadi production risk.

24.4 Mutable Approval History

Approval history yang bisa diedit adalah red flag audit.

24.5 One Global Workflow for All Documents

PO, invoice, stock adjustment, vendor onboarding, dan payment punya invariant dan risk profile berbeda. Shared engine boleh sama, tetapi definition dan policy harus domain-aware.


25. Practice: 20-Hour Deliberate Workflow Drill

Latihan ini mengikuti Kaufman: pecah skill, praktik cepat, feedback ketat.

Hour 1–2: Model One Document Lifecycle

Ambil dokumen PurchaseOrder.

Output:

  • state diagram;
  • transition matrix;
  • approval-relevant fields;
  • terminal states.

Hour 3–5: Build Approval Topology

Modelkan:

  • amount-based routing;
  • cost-center owner;
  • finance review;
  • director review;
  • request change path.

Output:

  • workflow graph;
  • candidate rule;
  • SoD rule.

Hour 6–8: Define Data Model

Buat schema:

  • workflow definition;
  • workflow version;
  • workflow instance;
  • task;
  • decision;
  • event log;
  • timer.

Hour 9–11: Implement Command Handler

Implement:

  • submit;
  • claim;
  • approve;
  • reject;
  • request change;
  • cancel.

Tambahkan:

  • idempotency key;
  • optimistic lock;
  • business revision check.

Hour 12–14: Add SLA and Escalation

Implement timer table dan worker lease.

Test:

  • overdue task;
  • reminder;
  • escalation;
  • duplicate timer execution.

Hour 15–17: Audit and Evidence

Buat decision trace.

Pastikan bisa menjawab:

Who approved what, when, under which policy, for which document revision, with what authority?

Hour 18–20: Failure Simulation

Simulasikan:

  • double click approve;
  • stale revision;
  • assignee inactive;
  • workflow definition changed;
  • timer worker crash;
  • domain guard rejects after workflow approval.

26. Design Review Checklist

Gunakan checklist ini saat review workflow ERP.

Lifecycle

  • Apakah state dokumen dan workflow instance dipisah jelas?
  • Apakah terminal state eksplisit?
  • Apakah illegal transition ditolak?
  • Apakah cancel/reopen/revise punya semantic jelas?

Approval

  • Apakah approval mengikat ke document revision?
  • Apakah approver dihitung dari rule yang versioned?
  • Apakah approval path disnapshot?
  • Apakah parallel/quorum semantics jelas?

Authorization

  • Apakah action-level authorization ada?
  • Apakah SoD dievaluasi dan disimpan sebagai evidence?
  • Apakah delegation tidak bypass policy?
  • Apakah admin action diaudit?

Reliability

  • Apakah command idempotent?
  • Apakah task completion atomic?
  • Apakah outbox dipakai untuk side effect?
  • Apakah timer durable?
  • Apakah worker crash safe?

Audit

  • Apakah decision log append-only?
  • Apakah event log punya actor, timestamp, reason, snapshot hash?
  • Apakah correction tidak menghapus histori?
  • Apakah report audit bisa direkonstruksi?

Operability

  • Apakah task stuck terlihat?
  • Apakah overdue/escalation metrics ada?
  • Apakah admin bisa recover tanpa DB patch?
  • Apakah correlation id lintas service tersedia?

27. Key Takeaways

  • Workflow ERP adalah control plane, bukan pengganti domain invariant.
  • Approval harus terikat pada document revision dan policy evidence.
  • Task, decision, escalation, delegation, dan event log harus menjadi first-class model.
  • Long-running workflow harus diproses sebagai rangkaian transaksi pendek yang durable.
  • Idempotency, optimistic locking, outbox, durable timer, dan append-only audit adalah fondasi reliability.
  • Case lifecycle diperlukan untuk exception work yang tidak cocok dengan approval linear.
  • Admin correction harus menambah evidence, bukan menghapus sejarah.

28. Source Notes

Beberapa referensi resmi dan teknis yang relevan untuk part ini:

Lesson Recap

You just completed lesson 17 in build core. Use the series map if you want to review the broader track, or continue directly into the next lesson while the context is still warm.

Continue The Track

Keep the momentum while the lesson is still fresh. Move backward for review or continue forward into the next concept.