Skip to content

Latest commit

Β 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

βš™οΈ Workflow Engine β€” Multi-Step Approval System

CI Pipeline Java Spring Boot Coverage Docker License: MIT

A production-ready RESTful Workflow Engine built with Spring Boot 3 that orchestrates configurable multi-step approval pipelines. Designed with clean architecture, JWT-based security, and full CI/CD automation.


πŸ“Œ Table of Contents


πŸ” Overview

The Workflow Engine is a backend system that automates multi-step approval processes for organizational workflows such as leave applications and expense claims. Requests move through ordered approval steps, each gated by role-based authorization. Every action is recorded in a complete audit trail.

Use cases it solves:

  • Leave approval pipelines (e.g., LEAVE β†’ Approver β†’ Admin)
  • Expense claim routing with sequential sign-off
  • Any domain requiring ordered, role-gated approvals

✨ Features

Feature Details
πŸ” JWT Authentication Stateless auth using JJWT 0.12.5 with BCrypt password hashing
πŸ›‘οΈ Role-Based Access Control REQUESTER, APPROVER, ADMIN roles with method-level @PreAuthorize
πŸ”„ Multi-Step Approval Engine Configurable ordered steps per request type; engine auto-advances or finalizes
🚦 Request Lifecycle Full PENDING β†’ APPROVED / REJECTED state management
πŸ“‹ Audit History Immutable per-request approval history with actor, action, and timestamp
βœ… 90%+ Test Coverage Enforced by JaCoCo; build fails below threshold
🐳 Dockerized Multi-stage Docker build with Eclipse Temurin JRE 17 Alpine image
πŸ€– CI/CD GitHub Actions pipeline with Maven cache, JaCoCo report upload, and SonarCloud scan
πŸ—οΈ JPA Auditing BaseEntity with createdBy / createdAt auto-populated via AuditorAware

πŸ›οΈ Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                          Client (HTTP)                              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                β”‚
                   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                   β”‚    JWT Auth Filter       β”‚  ← validates Bearer token
                   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                β”‚
          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
          β”‚               REST Controllers              β”‚
          β”‚   AuthController         RequestController  β”‚
          β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                β”‚
          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
          β”‚               Service Layer                 β”‚
          β”‚         RequestService  |  UserService      β”‚
          β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                 β”‚                          β”‚
   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
   β”‚  Approval Engine   β”‚      β”‚     Auth / User Mgmt    β”‚
   β”‚  ─ Step lookup     β”‚      β”‚     ─ Register / Login  β”‚
   β”‚  ─ Role validation β”‚      β”‚     ─ Role assignment   β”‚
   β”‚  ─ History record  β”‚      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
   β”‚  ─ State advance   β”‚
   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜
                 β”‚
   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
   β”‚                 JPA Repositories (H2 / any RDBMS)  β”‚
   β”‚  RequestRepo | ApprovalStepRepo | HistoryRepo | ... β”‚
   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Domain Model

User ─── ManyToMany ──► Role
Request ─── OneToMany ──► ApprovalHistory
ApprovalStep (type + stepOrder + role)   ← workflow config table

πŸ› οΈ Tech Stack

Layer Technology
Language Java 17
Framework Spring Boot 3.5
Security Spring Security 6 + JJWT 0.12.5
Persistence Spring Data JPA + H2 (dev)
Validation Spring Validation (Bean Validation 3)
Build Maven + Maven Wrapper
Testing JUnit 5 + Spring Security Test + Testcontainers
Coverage JaCoCo (β‰₯ 90% line coverage enforced)
Code Quality SonarCloud + Qodana
Containerization Docker (multi-stage, Eclipse Temurin 17 Alpine)
CI/CD GitHub Actions

πŸ“‘ API Reference

Auth Endpoints

Method Endpoint Access Description
POST /auth/register-user Public Register a new user with roles
POST /auth/login Public Authenticate and receive a JWT

Login response:

{
  "id": 1,
  "email": "user@example.com",
  "token": "<JWT>",
  "roles": ["ROLE_REQUESTER"]
}

Request / Workflow Endpoints

All endpoints below require a valid Authorization: Bearer <token> header.

Method Endpoint Role Required Description
POST /requests REQUESTER Create a new workflow request
GET /requests/{id} REQUESTER, APPROVER, ADMIN Fetch request details
POST /requests/{id}/approve?userId=&role= APPROVER, ADMIN Approve current step
POST /requests/{id}/reject?userId=&role= APPROVER, ADMIN Reject the request
GET /requests/history/{id} REQUESTER, ADMIN Get full approval audit trail

Create request body:

{
  "type": "LEAVE",
  "username": "john.doe"
}

πŸ”„ How the Approval Flow Works

1. REQUESTER creates a request  β†’  status: PENDING, currentStepOrder: 1

2. APPROVER (step 1) calls /approve
       └── Engine checks: role matches ApprovalStep for (type, stepOrder=1)?
           β”œβ”€β”€ βœ… YES β†’ records history, advances to stepOrder: 2
           └── ❌ NO  β†’ throws "Unauthorized for this step"

3. ADMIN (step 2) calls /approve
       └── Engine checks: any next step exists?
           β”œβ”€β”€ YES β†’ advance step order
           └── NO  β†’ status: APPROVED  βœ…

4. Any approver calls /reject at any step β†’ status: REJECTED ❌

5. GET /requests/history/{id} β†’ full ordered audit trail

The approval_steps table is the configuration table β€” you define the workflow by inserting rows:

request_type step_order role
LEAVE 1 APPROVER
LEAVE 2 ADMIN
EXPENSE 1 APPROVER

πŸš€ Getting Started

Prerequisites

  • Java 17+
  • Maven 3.8+ (or use ./mvnw)

Clone & Run

git clone https://github.com/jaysharmagithub/workflowengine.git
cd workflowengine

# Run the application
./mvnw spring-boot:run

The app starts on http://localhost:8080 by default (configurable via PORT env var).

Quick Test with cURL

# 1. Register a user
curl -X POST http://localhost:8080/auth/register-user \
  -H "Content-Type: application/json" \
  -d '{"firstName":"Jay","lastName":"Sharma","email":"jay@example.com","password":"pass123","roles":[{"name":"ROLE_REQUESTER"}]}'

# 2. Login
curl -X POST http://localhost:8080/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"jay@example.com","password":"pass123"}'

# 3. Create a workflow request (use token from step 2)
curl -X POST http://localhost:8080/requests \
  -H "Authorization: Bearer <TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"type":"LEAVE","username":"jay@example.com"}'

🐳 Running with Docker

# Build the image
docker build -t workflowengine .

# Run the container
docker run -p 8080:8080 workflowengine

The multi-stage Dockerfile:

  1. Stage 1 β€” Builds the JAR using maven:3.8.4-openjdk-17
  2. Stage 2 β€” Runs with slim eclipse-temurin:17-jre-alpine for minimal image size

πŸ§ͺ Testing & Coverage

# Run all tests with coverage report
./mvnw clean verify
  • Coverage report generated at target/site/jacoco/index.html
  • Build fails if line coverage drops below 90% (enforced via JaCoCo <check> rule)
  • Excludes: entity, dto, config packages, and the main application class

πŸ€– CI/CD Pipeline

Every push to main and every Pull Request triggers:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Checkout   │───►│  Setup JDK 17    │───►│ mvn clean verify     │───►│ Upload JaCoCo β”‚
β”‚              β”‚    β”‚  (Temurin)       β”‚    β”‚ (Tests + Coverage)   β”‚    β”‚ Report        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                                                                β”‚
                                                                        β”Œβ”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”
                                                                        β”‚  SonarCloud   β”‚
                                                                        β”‚  Scan         β”‚
                                                                        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Maven dependency cache is keyed to pom.xml hash for fast builds.


πŸ“ Project Structure

workflowengine/
β”œβ”€β”€ .github/
β”‚   └── workflows/
β”‚       β”œβ”€β”€ ci.yml                     # CI pipeline
β”‚       └── qodana_code_quality.yml    # Static analysis
β”œβ”€β”€ src/main/java/com/techpulseIt/workflowengine/
β”‚   β”œβ”€β”€ config/                        # JPA Auditing config & AuditorAware
β”‚   β”œβ”€β”€ controller/
β”‚   β”‚   β”œβ”€β”€ AuthController.java        # /auth endpoints
β”‚   β”‚   └── RequestController.java     # /requests endpoints
β”‚   β”œβ”€β”€ dto/                           # Response DTOs
β”‚   β”œβ”€β”€ entity/
β”‚   β”‚   β”œβ”€β”€ BaseEntity.java            # Audit fields (id, createdBy, createdAt)
β”‚   β”‚   β”œβ”€β”€ User.java
β”‚   β”‚   β”œβ”€β”€ Role.java
β”‚   β”‚   β”œβ”€β”€ Request.java               # Workflow request
β”‚   β”‚   β”œβ”€β”€ ApprovalStep.java          # Workflow config (type + order + role)
β”‚   β”‚   └── ApprovalHistory.java       # Immutable audit trail
β”‚   β”œβ”€β”€ exception/                     # Global exception handler + custom exceptions
β”‚   β”œβ”€β”€ repository/                    # Spring Data JPA repositories
β”‚   β”œβ”€β”€ request/                       # Request body POJOs
β”‚   β”œβ”€β”€ response/                      # Unified API response wrappers
β”‚   β”œβ”€β”€ security/
β”‚   β”‚   β”œβ”€β”€ SecurityConfig.java        # Spring Security filter chain
β”‚   β”‚   β”œβ”€β”€ CorsConfig.java
β”‚   β”‚   └── jwt/                       # JWT filter, entry point, utilities
β”‚   └── service/
β”‚       β”œβ”€β”€ RequestService.java        # Core approval engine logic
β”‚       β”œβ”€β”€ UserServiceImpl.java
β”‚       └── RoleServiceImpl.java
β”œβ”€β”€ src/test/                          # Unit & integration tests
β”œβ”€β”€ Dockerfile                         # Multi-stage Docker build
└── pom.xml

πŸ”‘ Key Design Decisions

  • Stateless security β€” No HTTP sessions; every request is authenticated via JWT, making the service horizontally scalable.
  • Data-driven workflow β€” The approval chain is defined in the approval_steps table, not in code, so new workflows can be added without redeployment.
  • Self-approval prevention β€” The engine explicitly blocks a requester from approving their own request.
  • Fail-fast coverage gate β€” JaCoCo enforces β‰₯ 90% line coverage at build time, preventing untested code from merging.
  • Clean layer separation β€” Controllers handle HTTP concerns, Services own business logic, Repositories are pure data access.

πŸ“„ License

This project is licensed under the MIT License.


Built with ❀️ by Jay Sharma

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages