A distributed task scheduling system built in Go, using Redis for persistence and RabbitMQ as a message broker. The project follows clean architecture principles with a clear separation between domain, repository, messaging, and API layers.
┌─────────────┐ HTTP ┌─────────────────────────────────┐
│ CLI Client │ ──────────► │ REST API (Gin) │
└─────────────┘ │ /tasks /tasks/:id /health │
└──────────────┬──────────────────┘
│ CRUD
┌─────────▼──────────┐
│ Redis Repository │
│ - task:{id} │
│ - tasks (Set) │
│ - scheduled_tasks │
│ (Sorted Set) │
└─────────▲──────────┘
│ poll every 30s
┌──────────────┴──────────────────┐
│ Task Worker │
│ - polls scheduled_tasks ZSet │
│ - publishes to RabbitMQ │
│ - consumes from tasks_queue │
│ - executes via sh -c │
└──────────────┬──────────────────┘
│ AMQP
┌─────────▼──────────┐
│ RabbitMQ │
│ exchange: tasks │
│ queue: tasks_queue │
└────────────────────┘
Three independent binaries:
cmd/api— HTTP REST servercmd/worker— scheduler + consumer + executorcmd/client— CLI to interact with the API
| Component | Technology |
|---|---|
| Language | Go 1.21+ |
| HTTP | Gin |
| Storage | Redis (via go-redis/v9) |
| Messaging | RabbitMQ (via amqp091-go) |
| Docs | Swagger (swaggo) |
| Runtime | Docker + Docker Compose |
- Go 1.21+
- Docker and Docker Compose
docker-compose up -dThis starts Redis on :6379 and RabbitMQ on :5672 (management UI on :15672).
go run ./cmd/apigo run ./cmd/worker# Create a task
go run ./cmd/client create --id my-task-1 --name "My Task" --command "echo hello"
# List all tasks
go run ./cmd/client list
# Get a specific task
go run ./cmd/client get --id my-task-1
# Schedule a task
go run ./cmd/client schedule --id my-task-1 --at "2025-12-01T15:00:00Z"
# Execute a task immediately
go run ./cmd/client execute --id my-task-1
# Update a task
go run ./cmd/client update --id my-task-1 --name "Updated Name"
# Delete a task
go run ./cmd/client delete --id my-task-1| Variable | Default | Description |
|---|---|---|
REDIS_ADDRESS |
localhost:6379 |
Redis server address |
REDIS_MQ_URL |
amqp://user:password@localhost:5672/ |
RabbitMQ connection URL |
SERVER_PORT |
8080 |
HTTP API port |
| Method | Path | Description |
|---|---|---|
| GET | /health |
Health check |
| POST | /tasks |
Create a task |
| GET | /tasks |
List tasks (optional ?status=) |
| GET | /tasks/:id |
Get task by ID |
| PUT | /tasks/:id |
Update task |
| DELETE | /tasks/:id |
Delete task |
| POST | /tasks/:id/execute |
Trigger immediate execution |
| POST | /tasks/:id/schedule |
Schedule task for future |
Swagger docs available at /swagger/index.html when running the API.
created → pending → running → completed
↘ failed
Tasks with a scheduled_at time are stored in a Redis Sorted Set (scheduled_tasks) scored by Unix timestamp. The worker polls this set every 30 seconds, publishes due tasks to RabbitMQ, and the consumer picks them up for execution via sh -c.
task_scheduler/
├── cmd/
│ ├── api/ # API binary entrypoint
│ ├── client/ # CLI binary + commands
│ └── worker/ # Worker binary entrypoint
├── internal/
│ ├── api/ # HTTP server, routes, handlers
│ ├── domain/ # Task entity + validation
│ ├── messaging/ # RabbitMQ implementation
│ ├── repository/ # TaskHandler interface + Redis impl
│ └── worker/ # Scheduler, consumer, processor
├── pkg/
│ └── config/ # Environment configuration
├── docs/ # Swagger generated docs
└── docker-compose.yml
go test ./...- Redis is used as the primary store; data is lost if Redis restarts without persistence configured
- The polling window (30s, 5min lookahead) can cause slight delays in task execution
sh -cexecution is not sandboxed; commands run with the worker process’s privileges- No retry count limit on failed tasks
- No authentication on the HTTP API
See the Architecture Decisions section for open design questions.