This project implements a configurable approval workflow engine for a property management platform using Go, PostgreSQL, pgxpool, and Chi router.
The engine supports:
- Configurable workflow templates
- Sequential approval flows
- User or role-based approvers
- Runtime workflow instances
- Immutable audit logging
- Concurrency-safe approvals
- Polymorphic workflow entities
- Workflow approval/rejection callbacks
Example business scenarios:
- Booking cancellation approval
- Unit price update approval
- Commission approval workflows
- Go 1.24
- PostgreSQL
- pgxpool
- Chi Router
- SQL migrations
- Bash automation scripts
cmd/api/
main.go
internal/httpapi/
handler.go
mock.go
internal/workflow/
approve.go
reject.go
trigger.go
template.go
instance.go
service.go
model.go
error.go
migrations/
001_default.sql
002_seed.sql
script/
test_api.sh
test_concurrency.sh
README.md
Makefile
git clone <repo>
cd <repo>createdb workflow_engineCreate .env
DATABASE_URL=postgres://postgres:postgres@localhost:5432/workflow_engine?sslmode=disablepsql "$DATABASE_URL" -f migrations/001_default.sql
psql "$DATABASE_URL" -f migrations/002_seed.sqlgo run ./cmd/apiServer:
http://localhost:8080
The repository includes automated API testing scripts.
Run:
./script/test_api.shThis script will:
- Reset the database
- Re-run migrations
- Seed test data
- Start the API server
- Execute workflow lifecycle API tests
- Validate workflow progression
Run:
./script/test_concurrency.shThis validates that only one approver can act on the same workflow step concurrently.
The engine uses PostgreSQL row-level locking via:
SELECT ... FOR UPDATEto guarantee transactional consistency.
The schema separates:
| Layer | Purpose |
|---|---|
| workflow_template + workflow_step | reusable configuration |
| workflow_instance + workflow_instance_step | runtime transactional state |
This allows workflow definitions to evolve independently from running workflow executions.
The workflow engine supports polymorphic entity references using:
(entity_type, entity_id)
This allows workflows to attach to:
- bookings
- units
- commission records
- future entities
without schema modifications.
Example:
booking.cancellation_requested
- System event fires
- Active workflow template resolved
- Workflow instance created
- Runtime workflow steps generated
- First step activated (
awaiting_action) - Approver acts
- Workflow advances or terminates
- Audit log recorded
Workflow instance steps snapshot assigned users/roles at instance creation time.
This prevents mid-workflow role changes from affecting historical workflow executions.
Example:
- User A was Sales Manager during workflow creation
- User role later changes
- Existing workflow remains historically consistent
The workflow engine prevents concurrent approvals using PostgreSQL row-level locking.
Approval/rejection flow:
- Begin transaction
- Lock workflow step row using
SELECT ... FOR UPDATE - Validate step status is still
awaiting_action - Apply approval/rejection atomically
- Commit transaction
This guarantees:
- only one approver succeeds
- no double approvals
- no race conditions
- consistent workflow progression
If two approvers attempt approval simultaneously:
- first request succeeds
- second request receives descriptive error
| Status | Meaning |
|---|---|
| pending | created but not started |
| in_progress | currently processing |
| approved | fully approved |
| rejected | rejected by approver |
| cancelled | manually cancelled |
| Status | Meaning |
|---|---|
| pending | waiting previous step |
| awaiting_action | active approver step |
| approved | approved |
| rejected | rejected |
POST /api/v1/workflow/instancesExample:
{
"event":"booking.cancellation_requested",
"entity_type":"booking",
"entity_id":2,
"initiated_by":3
}GET /api/v1/workflow/instances/{instanceID}Returns:
- workflow instance
- runtime steps
- approvals/rejections
- comments
- timestamps
GET /api/v1/workflow/templates/{templateID}GET /api/v1/workflow/steps/pending?user_id=1Returns all workflow steps awaiting approver action.
Supports:
- direct user assignments
- role-based assignments
POST /api/v1/workflow/steps/{stepID}/approveExample:
{
"acted_by_user_id":1,
"comment":"Approved by sales manager"
}POST /api/v1/workflow/steps/{stepID}/rejectExample:
{
"acted_by_user_id":2,
"comment":"Finance rejected request"
}All workflow decisions are written into immutable audit logs.
Audit entries capture:
- workflow instance
- workflow step
- action
- approver
- comment
- timestamp
This ensures traceability and compliance.
- Authentication and authorization are out of scope
- Notifications are represented using callback hooks
- Role resolution is abstracted behind an interface
- Sequential workflows are implemented
- Templates cannot be modified while active workflow instances exist
Seed migration includes:
- 2 projects
- 10 units
- 3 users
- 5 bookings
- sample workflow templates
- sample workflow steps
- sample workflow instance
This implementation focuses on:
- correctness
- transactional integrity
- clean architecture
- workflow extensibility
- concurrency safety