A lightweight workflow orchestration engine — inspired by Temporal, Camunda, and AWS Step Functions — built with Domain-Driven Design, Hexagonal Architecture, and CQRS principles.
Educational and portfolio project demonstrating clean architecture patterns, event-driven orchestration, and a full-stack TypeScript + Java implementation.
workflowEngine
├── backend/
│ ├── api
│ │ ├── controller
│ │ ├── dto
│ │ ├── exception
│ │ └── mapper
│ │
│ ├── application
│ │ ├── facade
│ │ ├── port
│ │ └── usecase
│ │ ├── commands
│ │ └── queries
│ │
│ ├── domain
│ │ ├── event
│ │ ├── exception
│ │ ├── model
│ │ └── service
│ │
│ ├── docs/ ← Backend-specific docs
│ │ ├── architecture.md
│ │ ├── database.dbml
│ │ ├── domain-model.puml
│ │ └── schema.sql
│ │
│ └── infrastructure
│ ├── event ← EventPublisher adapters (Spring Events, Logging)
│ ├── persistence
│ │ ├── adapter
│ │ ├── entity
│ │ ├── mapper
│ │ └── repository
│ │
│ └── config
│
├── frontend/
│ ├── projects/
│ │ ├── workflow-engine/ ← Reusable Angular library (ng-packagr)
│ │ │ ├── src/lib/
│ │ │ │ ├── models/ ← Domain TypeScript interfaces
│ │ │ │ ├── config/ ← Injection token (provideWorkflowEngine)
│ │ │ │ ├── services/ ← API clients (WorkflowApiService, ExecutionApiService)
│ │ │ │ └── components/ ← Standalone UI components
│ │ │ │ ├── workflow-list/
│ │ │ │ ├── workflow-detail/
│ │ │ │ ├── execution-detail/
│ │ │ │ ├── execution-history/
│ │ │ │ ├── execution-list/
│ │ │ │ └── workflow-create/
│ │ │ └── public-api.ts ← Barrel exports
│ │ │
│ │ └── shell/ ← Demo SPA consuming the library
│ │ └── src/app/
│ │ ├── app.config.ts ← provideWorkflowEngine({ apiBaseUrl: ... })
│ │ ├── app.routes.ts ← Lazy-loaded routes
│ │ ├── error.service.ts ← Global error toast state
│ │ ├── error-toast.component.ts ← Toast notification UI
│ │ └── *-page.component ← Routing page wrappers
│ │
│ ├── angular.json
│ ├── package.json
│ └── docs/ ← Frontend-specific docs
│ ├── mvp.md ← Frontend MVP scope
│ └── architecture-deepening.md ← Architecture improvement roadmap
│
├── docs/ ← Cross-cutting docs (root level)
│ ├── CONTEXT.md ← Domain glossary & architecture decisions
│ ├── mvp.md
│ ├── roadmap.md
│ └── features/ ← Feature specs (back + front)
├── docker-compose.yml ← PostgreSQL 16 for local dev
└── ...config files
Defines the rules of the system:
- States
- Transitions
- Initial state
Represents a running instance of a workflow:
- Current state
- Execution ID
- History of state changes
Pure domain service that:
- Validates transitions
- Applies state changes
- Emits domain events
- CreateWorkflow
- StartWorkflowExecution
- ExecuteTransition
- ListWorkflows
- GetWorkflow
- ListExecutions
- GetExecution
- GetNextStates
- GetHistory
- Define workflows with states (code, name, terminal flag) and transitions
- Start executions from a workflow with optional execution context (metadata)
- Perform validated state transitions with domain rule enforcement
- Track full execution history via
StateChangeddomain events - Query next possible states for any execution
- Immutable aggregate pattern — transitions produce new
WorkflowExecutioninstances
- Full CQRS REST API — separate command and query endpoints
- OpenAPI / Swagger UI — auto-generated docs at
/swagger-ui.html - Paginated execution list (
?page=0&size=20) - Workflow CRUD — create, read, update (with edit-constraint validation), delete (guarded)
- Execution CRUD — start, list, get, delete (terminal state only)
- Pre-flight editability check (
GET /workflows/{id}/editable) - Domain event publishing via
EventPublisherhexagonal port + Spring Events adapter - Webhook callbacks — per-execution callback URL for external system integration
- API key authentication via
X-API-Keyheader - Execution context — optional JSON metadata on execution start
- Prometheus metrics —
workflow_transitions_totalcounter withfrom_state/to_statetags - Structured JSON logging via Logstash Logback Encoder (MDC:
executionId,fromState,toState) - Grafana dashboards — Docker Compose configuration included
- Micrometer + Actuator —
/actuator/prometheusendpoint
- Workflow list with skeleton loading, search/filter, and selection
- Workflow detail with states table, transitions list, and Start Execution button
- Workflow create form — define states, transitions, and initial state
- Workflow edit form — pre-filled with smart edit-constraint validation
- Execution detail with current state, available transitions (pessimistic UI), and completion detection
- Execution history — vertical/horizontal timeline modes
- Paginated execution list per workflow + all-executions aggregation view
- Start Execution component — button with loading state, optional context
- Reusable UI atoms — skeleton cards, error banners, retry buttons, spinners
- Global error toast — auto-dismiss notifications
- CSS Custom Properties theming —
--we-*design system, host app overridable
- Java 21+
- Docker Desktop (for PostgreSQL via Testcontainers or local dev)
- Node.js 20+ and npm (for Angular frontend)
| Profile | Database | Persistence | Events | Metrics/Logging | Use case |
|---|---|---|---|---|---|
dev-h2 (default) |
H2 (embedded) | JPA (Spring Data) | Spring Events | Active | Fast dev, tests without Docker |
dev-pg |
PostgreSQL 16 (Docker) | JPA (Spring Data) | Spring Events | Active | Local dev matching production |
dev-memory |
None | In-memory HashMap | Logging only | Disabled | Controller/service tests |
docker compose up -dcd backend
./gradlew bootRun --args='--spring.profiles.active=dev-pg'Or from the IDE, set --spring.profiles.active=dev-pg in the run configuration.
cd frontend
npm install
ng serve # serves the shell demo app at http://localhost:4200cd backend
./gradlew testTests use H2 by default (profile dev-h2). The Testcontainers integration test (WorkflowEnginePgIntegrationTest) requires Docker Desktop running.
cd frontend
ng test # Karma unit tests (library + shell)Or run tests for a specific project:
ng test workflow-engine # library tests only
ng test shell # shell app tests onlyThe system is designed for layered testing with 22 backend test classes (80+ test methods) and 28 frontend spec files:
| Layer | Framework | Scope |
|---|---|---|
| Domain Tests | JUnit 5 + AssertJ | WorkflowEngine transition rules, state validation, event generation |
| Use Case Tests | JUnit 5 + Mockito | Command/query orchestration with mocked repositories |
| Controller Tests | @WebMvcTest |
REST endpoint behaviour, validation, error mapping |
| Persistence Adapter Tests | @DataJpaTest + H2 |
JPA adapter round-trips, Flyway migrations |
| PostgreSQL Integration | Testcontainers | Full persistence layer against real PostgreSQL 16 |
| End-to-End Test | @SpringBootTest + TestRestTemplate |
Full HTTP lifecycle (create → start → transition → history) |
| Security Tests | @WebMvcTest + MockMvc |
API key authentication filter, permitted paths |
| Layer | Framework | Scope |
|---|---|---|
| Service Tests | HttpClientTestingController |
HTTP request/response for all API services |
| Component Tests | TestBed + mocks | Rendering, loading/empty/error states, interaction |
| Fake Adapter Tests | Jasmine | In-memory adapter implementations for offline testing |
| Model/Util Tests | Jasmine | Pure function behaviour (state colors, async data util) |
- Domain-Driven Design (DDD) — ubiquitous language, aggregates, domain services
- Hexagonal Architecture — ports (interfaces) and adapters (JPA, in-memory, REST)
- CQRS-light — separate command and query use cases
- Immutability —
WorkflowExecutionis immutable; transitions produce new instances - Reference-by-ID — aggregates reference each other only by ID
- Pure domain service —
WorkflowEnginevalidates and applies transitions with zero infrastructure dependency - Event-driven —
StateChangedevents published via hexagonalEventPublisherport - Value Objects — State modeled with stable
codeidentity, used as FK target - Persistence ignorance — domain model has no JPA annotations; mappers isolate entities
- Schema migrations — Flyway for PostgreSQL, Hibernate DDL auto for H2 tests
- Pluggable profiles —
dev-h2,dev-pg,dev-memoryfor different environments
- Library architecture: reusable
workflow-enginelibrary +shelldemo SPA - Standalone components: no NgModules, all components lazy-loadable
- Autonomous components: components fetch their own data via API
- Signals-based state: loading/error/data managed with
signal(),computed()for derived state - Pessimistic updates: transition buttons show spinner + disable until API responds
- CSS Custom Properties:
--we-*design system, host app overrides via:root - Skeleton loading: shimmer-based skeleton placeholders during data fetch for perceived performance
- Error resilience: inline error messages +
@Output() errorEventfor host app integration + global error toast - Client-side search: computed signal for instant client-side filtering of workflow list
- Create workflow definition —
POST /workflows - List available workflows —
GET /workflows - Update existing workflow (if needed) —
PUT /workflows/{id} - Check editability —
GET /workflows/{id}/editable - Delete workflow (if no executions) —
DELETE /workflows/{id} - Start workflow execution (with optional context + callback URL) —
POST /workflows/{id}/executions - List executions (paginated) —
GET /workflows/{id}/executions?page=0&size=20 - Query available next states —
GET /executions/{id}/next-states - Execute a transition —
POST /executions/{id}/transition - Query execution history —
GET /executions/{id}/history - Inspect Prometheus metrics —
GET /actuator/prometheus
- Open
http://localhost:4200— see workflow list with skeleton loading, then cards - Search/filter workflows by name using the search input
- Click "+ New Workflow" — fill form with states, transitions, initial state
- Click a workflow card — see states table + transitions list
- Click "Edit" — pre-filled edit form with smart validation
- Click "Start Execution" (with optional context) — navigates to execution view
- See current state displayed prominently with available transitions
- Click a transition button — state updates + timeline refreshes (pessimistic UI)
- Reach terminal state — see completion message
- Browse all executions across workflows at
/executions
- Webhook callbacks — each execution can receive POST requests on every state change
- Prometheus + Grafana — monitor transition counts, rates, and patterns
- Swagger UI — interactive API documentation at
http://localhost:8080/swagger-ui.html
This project is a mini workflow runtime engine, inspired by systems like:
- Temporal
- Camunda
- AWS Step Functions (conceptually)
But implemented in a minimal, educational form for portfolio and system design exploration.
| Layer | Technology |
|---|---|
| Backend | Java 21 + Spring Boot 4.0.6 |
| Persistence | Spring Data JPA, PostgreSQL 16, Flyway, H2 (tests) |
| Backend Testing | JUnit 5, Mockito, Testcontainers |
| API Documentation | springdoc-openapi (Swagger UI) |
| Observability | Micrometer, Prometheus, Grafana, Logstash Logback Encoder |
| Security | API Key authentication (X-API-Key header) |
| Build | Gradle Kotlin DSL |
| Frontend | Angular 19.2, TypeScript 5.7, RxJS 7 |
| Frontend Architecture | Standalone components, Signals, CSS Custom Properties |
| Frontend Testing | Jasmine, Karma, HttpClientTestingController |
| Infrastructure | Docker Compose (PostgreSQL + Prometheus + Grafana) |