Skip to content
 
 

Repository files navigation

Enterprise Workflow Automation Platform

A distributed workflow automation platform built around a configuration-driven state machine engine — workflows (approval chains, review processes, etc.) are defined as data, not hardcoded Java logic, so new workflow types can be added without touching the engine's code.

Built with Java 21, Spring Boot 3.2, Kafka, PostgreSQL, and Docker, across three independently deployable microservices.

Why this exists

Most portfolio "workflow" projects are CRUD apps with a status column. This one implements an actual state machine: workflow definitions declare states, transitions, role restrictions, and SpEL conditions as JSON, and the engine evaluates them at runtime — including conditional routing (e.g. the same "Approve" action sends a request to Finance review or straight to Approved, depending on the amount).

Architecture

React dashboard
      │
      ▼
API Gateway (direct REST calls to each service in this version — see Roadmap)
      │
      ├──► workflow-engine-service (8081) ──► PostgreSQL (workflow_engine_db)
      │         │  publishes "workflow-events" to Kafka
      │         ▼
      ├──► task-service (8082) ──► PostgreSQL (task_service_db)
      │         │  consumes "workflow-events", publishes "task-events"
      │         │  calls back into workflow-engine-service when a task is completed
      │         │  runs a scheduled SLA-escalation job
      │         ▼
      └──► notification-service (8083) — no database, in-memory store
                consumes "task-events", logs "emails", exposes in-app notifications

Services

Service Responsibility Store
workflow-engine-service Owns workflow definitions and instances; runs the state machine; is the source of truth for workflow state PostgreSQL
task-service Creates human tasks when a workflow enters a state needing action; enforces SLA deadlines; escalates overdue tasks PostgreSQL
notification-service Turns task events into email/in-app alerts In-memory (deliberately no DB — see below)

The state machine, concretely

A workflow definition looks like this (a purchase-order approval flow with amount-based routing):

{
  "states": ["DRAFT", "MANAGER_REVIEW", "FINANCE_REVIEW", "APPROVED", "REJECTED"],
  "initialState": "DRAFT",
  "transitions": [
    { "from": "DRAFT", "to": "MANAGER_REVIEW", "action": "SUBMIT", "allowedRoles": ["REQUESTER"] },
    { "from": "MANAGER_REVIEW", "to": "FINANCE_REVIEW", "action": "APPROVE",
      "allowedRoles": ["MANAGER"], "condition": "#amount > 50000" },
    { "from": "MANAGER_REVIEW", "to": "APPROVED", "action": "APPROVE",
      "allowedRoles": ["MANAGER"], "condition": "#amount <= 50000" },
    { "from": "MANAGER_REVIEW", "to": "REJECTED", "action": "REJECT", "allowedRoles": ["MANAGER"] },
    { "from": "FINANCE_REVIEW", "to": "APPROVED", "action": "APPROVE", "allowedRoles": ["FINANCE"] },
    { "from": "FINANCE_REVIEW", "to": "REJECTED", "action": "REJECT", "allowedRoles": ["FINANCE"] }
  ]
}

Adding a second workflow (e.g. leave requests) means POSTing a new definition and adding one entry to task-service's task-routing.state-role-map in application.yml — no Java changes.

Why notification-service has no database

Its data is transient UI alerts, not the system of record — the audit trail lives in workflow-engine-service's state_transition_logs table. Not every microservice needs its own persistent store; this is a deliberate right-sizing decision, not an oversight.

Running it locally

Prerequisites: Java 21, Maven, Docker & Docker Compose.

# 1. Build all modules
mvn clean install

# 2. Start everything (Postgres x2, Kafka, Zookeeper, all 3 services)
docker-compose up --build

Services come up on localhost:8081 (workflow-engine), 8082 (task-service), 8083 (notification-service).

Try it end to end

# 1. Create the purchase-order-approval workflow definition
curl -X POST http://localhost:8081/api/v1/workflow-definitions \
  -H "Content-Type: application/json" \
  -d '{
    "key": "purchase-order-approval",
    "name": "Purchase Order Approval",
    "definitionJson": {
      "states": ["DRAFT","MANAGER_REVIEW","FINANCE_REVIEW","APPROVED","REJECTED"],
      "initialState": "DRAFT",
      "transitions": [
        {"from":"DRAFT","to":"MANAGER_REVIEW","action":"SUBMIT","allowedRoles":["REQUESTER"]},
        {"from":"MANAGER_REVIEW","to":"FINANCE_REVIEW","action":"APPROVE","allowedRoles":["MANAGER"],"condition":"#amount > 50000"},
        {"from":"MANAGER_REVIEW","to":"APPROVED","action":"APPROVE","allowedRoles":["MANAGER"],"condition":"#amount <= 50000"},
        {"from":"MANAGER_REVIEW","to":"REJECTED","action":"REJECT","allowedRoles":["MANAGER"]},
        {"from":"FINANCE_REVIEW","to":"APPROVED","action":"APPROVE","allowedRoles":["FINANCE"]},
        {"from":"FINANCE_REVIEW","to":"REJECTED","action":"REJECT","allowedRoles":["FINANCE"]}
      ]
    }
  }'

# 2. Start an instance
curl -X POST http://localhost:8081/api/v1/workflow-instances \
  -H "Content-Type: application/json" \
  -d '{"workflowDefinitionKey":"purchase-order-approval","createdByUserId":"babina","context":{"amount":75000}}'
# → note the returned "id", e.g. "abc-123"

# 3. Submit it (DRAFT -> MANAGER_REVIEW)
curl -X POST http://localhost:8081/api/v1/workflow-instances/abc-123/actions \
  -H "Content-Type: application/json" \
  -d '{"action":"SUBMIT","actorUserId":"babina","actorRole":"REQUESTER"}'

# 4. task-service auto-creates a MANAGER task - check it
curl "http://localhost:8082/api/v1/tasks?role=MANAGER"

# 5. Complete the task as the manager (amount > 50000 -> routes to FINANCE_REVIEW automatically)
curl -X POST http://localhost:8082/api/v1/tasks/{taskId}/complete \
  -H "Content-Type: application/json" \
  -d '{"action":"APPROVE","actorUserId":"manager1","actorRole":"MANAGER"}'

# 6. See the full audit trail
curl http://localhost:8081/api/v1/workflow-instances/abc-123/history

Tech stack

  • Backend: Java 21, Spring Boot 3.2, Spring Data JPA, Spring Kafka, Spring Validation
  • Messaging: Apache Kafka (event-driven task creation and notifications)
  • Databases: PostgreSQL (per-service, polyglot persistence), Flyway migrations
  • Build: Maven multi-module
  • Containerization: Docker, Docker Compose
  • Resilience/consistency choices: synchronous REST for user-triggered actions that need an immediate response (task completion → workflow transition), async Kafka events for side effects (notifications, task creation) that can tolerate eventual consistency

Roadmap / honest scope notes

This is a from-scratch build, so a few things are intentionally out of scope for v1 rather than hidden:

  • No API Gateway / service discovery (Eureka) yet — services call each other directly via configured URLs. Straightforward to add if the service count grows.
  • No React frontend yet — the API is fully functional and demoed above via curl; a task-inbox + workflow-status dashboard is the natural next step.
  • No auth/JWT yetactorUserId / actorRole are passed directly in requests. A real deployment needs Spring Security + JWT validating that the caller actually has the claimed role.
  • SLA escalation currently logs + notifies — it doesn't yet reassign or auto-escalate to a manager's manager.

Project structure

workflow-automation-platform/
├── common/                    # Shared Kafka event contracts (WorkflowInstanceEvent, TaskEvent)
├── workflow-engine-service/   # State machine core, workflow definitions & instances
├── task-service/              # Task creation, SLA tracking, escalation
├── notification-service/      # Event-driven alerts
└── docker-compose.yml

About

自己写的状态机 Enterprise workflow automation platform with a configuration-driven state machine engine, Kafka-based task orchestration, and SLA escalation — Java 21, Spring Boot 3.2, PostgreSQL, Docker

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages