Documentation
# Background Tasks API

The `aether-tasks` module provides a persistent background job queue for async task processing.

## Overview

Background tasks allow you to defer expensive operations (sending emails, processing images, generating reports) to be executed asynchronously. Tasks are persisted to a store, so they survive server restarts.

## Installation

```kotlin
// build.gradle.kts
implementation("codes.yousef.aether:aether-tasks:0.5.0.2")
```

## Basic Usage

### Registering Task Handlers

```kotlin
import codes.yousef.aether.tasks.*

// Initialize the task system
val taskStore = InMemoryTaskStore()  // Or DatabaseTaskStore for production
val dispatcher = TaskDispatcher(taskStore)
val worker = TaskWorker(taskStore)

// Register a task handler
TaskRegistry.register<SendEmailArgs, EmailResult>("send_email") { args ->
    val result = emailService.send(args.to, args.subject, args.body)
    EmailResult(success = result.delivered, messageId = result.id)
}

// Start the worker
worker.start()
```

### Enqueueing Tasks

```kotlin
@Serializable
data class SendEmailArgs(val to: String, val subject: String, val body: String)

@Serializable  
data class EmailResult(val success: Boolean, val messageId: String?)

// Enqueue a task
val taskId = dispatcher.enqueue(
    taskName = "send_email",
    args = SendEmailArgs(
        to = "user@example.com",
        subject = "Welcome!",
        body = "Thanks for signing up."
    )
)

// Enqueue with options
val taskId = dispatcher.enqueue(
    taskName = "send_email",
    args = args,
    priority = TaskPriority.HIGH,
    delay = 5.minutes,  // Start after 5 minutes
    retryConfig = RetryConfig(
        maxAttempts = 3,
        baseDelayMillis = 1000,
        backoffMultiplier = 2.0
    )
)
```

## Task Stores

### InMemoryTaskStore

For development and testing. Tasks are lost on restart.

```kotlin
val store = InMemoryTaskStore()
```

### DatabaseTaskStore

For production. Persists tasks to the database via `aether-db`.

```kotlin
val store = DatabaseTaskStore(driver)

// Run migrations to create the tasks table
store.migrate()
```

## Task Status

Tasks progress through these states:

| Status | Description |
|--------|-------------|
| `PENDING` | Waiting to be picked up by a worker |
| `SCHEDULED` | Scheduled for future execution |
| `PROCESSING` | Currently being executed |
| `COMPLETED` | Finished successfully |
| `FAILED` | Failed after all retry attempts |
| `CANCELLED` | Manually cancelled |
| `RETRYING` | Failed but will retry |

## Task Priority

Tasks are processed in priority order:

```kotlin
enum class TaskPriority {
    LOW,      // Background, non-urgent
    NORMAL,   // Default priority
    HIGH,     // Important, process soon
    CRITICAL  // Process immediately
}
```

## Retry Configuration

Configure automatic retries with exponential backoff:

```kotlin
val retryConfig = RetryConfig(
    maxAttempts = 5,           // Total attempts including first try
    baseDelayMillis = 1000,    // Initial delay (1 second)
    backoffMultiplier = 2.0,   // Double delay each retry
    maxDelayMillis = 60_000,   // Cap delay at 1 minute
    useJitter = true           // Add randomness to prevent thundering herd
)

// Delays: 1s, 2s, 4s, 8s, 16s (capped at 60s)
```

## Task Worker

The worker polls the store and executes tasks:

```kotlin
val worker = TaskWorker(
    store = taskStore,
    concurrency = 4,           // Process 4 tasks simultaneously
    pollInterval = 1.seconds   // Check for new tasks every second
)

// Start processing
worker.start()

// Graceful shutdown
worker.stop()
```

## Task Signals

Subscribe to task lifecycle events:

```kotlin
import codes.yousef.aether.tasks.TaskSignals

TaskSignals.taskStarted.connect { task ->
    println("Task ${task.id} started: ${task.taskName}")
}

TaskSignals.taskCompleted.connect { task ->
    println("Task ${task.id} completed")
}

TaskSignals.taskFailed.connect { task ->
    println("Task ${task.id} failed: ${task.error}")
}

TaskSignals.taskRetrying.connect { task ->
    println("Task ${task.id} retrying (attempt ${task.attempts})")
}
```

## Monitoring

Get task queue statistics:

```kotlin
val stats = dispatcher.stats()
println("""
    Pending: ${stats.pending}
    Scheduled: ${stats.scheduled}
    Processing: ${stats.processing}
    Completed: ${stats.completed}
    Failed: ${stats.failed}
""")
```

## Task Management

```kotlin
// Get task by ID
val task = dispatcher.getTask(taskId)

// Cancel a pending task
dispatcher.cancel(taskId)

// Retry a failed task
dispatcher.retry(taskId)

// List tasks by status
val failedTasks = dispatcher.listByStatus(TaskStatus.FAILED)
```

## Example: Email Queue

```kotlin
@Serializable
data class EmailTask(
    val to: String,
    val template: String,
    val data: Map<String, String>
)

// Register handler
TaskRegistry.register<EmailTask, Unit>("send_templated_email") { task ->
    val html = templateEngine.render(task.template, task.data)
    mailService.send(task.to, html)
}

// Usage in your application
suspend fun sendWelcomeEmail(user: User) {
    dispatcher.enqueue(
        taskName = "send_templated_email",
        args = EmailTask(
            to = user.email,
            template = "welcome",
            data = mapOf("name" to user.name)
        ),
        priority = TaskPriority.HIGH
    )
}
```

## Best Practices

1. **Use DatabaseTaskStore in production** - Tasks survive restarts
2. **Set appropriate priorities** - Don't make everything CRITICAL
3. **Configure retries wisely** - Exponential backoff prevents overload
4. **Monitor failed tasks** - Set up alerts for stuck tasks
5. **Keep payloads small** - Store references, not large data blobs
6. **Idempotent handlers** - Tasks may run more than once on failure

Architected in Kotlin. Rendered with Materia. Powered by Aether.
© 2026 Yousef.?