# Job

The `JoobQ::Job` module provides an abstract structure for defining jobs within the JoobQ asynchronous job processing framework. This module includes functionality for managing job statuses, scheduling, retries, timeouts, and more.

### Defining a Job

To define a custom job, include the `JoobQ::Job` module in your job class and implement the `perform` method. The `perform` method is where you put the logic that should be executed when the job runs.

```crystal
class ExampleJob
  include JoobQ::Job

  def perform
    puts "Executing job logic"
  end
end
```

After defining your job, you can enqueue it, schedule it, or delay its execution using the provided methods.

### Job Properties

* **`jid`**: The unique identifier for the job (UUID).
* **`queue`**: The queue to which the job is assigned.
* **`retries`**: The maximum number of retries allowed if the job fails.
* **`expires`**: The expiration time of the job in Unix milliseconds.
* **`status`**: The current status of the job.
* **`timeout`**: The maximum execution time allowed for the job.

### Job Statuses

The `JoobQ::Job::Status` enum defines the possible states for a job:

* **`Pending`**: The job has been created but not scheduled.
* **`Scheduled`**: The job is scheduled to run at a specific time.
* **`Running`**: The job is currently executing.
* **`Completed`**: The job finished successfully.
* **`Retrying`**: The job is retrying after a failure.
* **`Failed`**: The job execution failed after exhausting retries.
* **`Expired`**: The job expired before execution.

Each status has corresponding predicate and setter methods for checking and updating job status.

```crystal
job = ExampleJob.new
job.running! # Sets the job's status to Running
job.running? # Checks if the job's status is Running
```

**Best Practices for Defining Jobs**

When defining jobs in JoobQ, it's important to follow certain best practices to ensure reliability and maintainability. Here are some key recommendations:

**Idempotency**

Jobs must be idempotent. This means that running the same job multiple times should produce the same result. Idempotency is crucial for ensuring that jobs can be retried safely without causing unintended side effects. To achieve idempotency:

* Avoid modifying external state directly within the job.
* Use unique identifiers to track job execution and prevent duplicate processing.
* Ensure that any side effects (e.g., database updates, API calls) are safe to repeat.

**Simple Primitive Types for Arguments**

Job arguments must be simple primitive types such as integers, strings, and booleans. This ensures that the job data can be easily serialized and deserialized, and reduces the risk of errors during job execution. Complex objects or data structures should be avoided as job arguments.

**Number of Arguments**

Keep the number of arguments for jobs to a minimum. Having too many arguments can make the job definition complex and harder to maintain. As a best practice:

* Limit the number of arguments to 3-5.
* Group related arguments into a single object if necessary.
* Use default values for optional arguments to simplify job invocation.

By following these best practices, you can ensure that your jobs are reliable, maintainable, and easy to work with in the JoobQ framework.

## Enqueueing and Execution Methods

### Batch Enqueue

To enqueue multiple jobs at once, use the `batch_enqueue` method:

```crystal
ExampleJob.batch_enqueue([job1, job2, job3])
```

### Enqueue a Single Job

To enqueue a single job for processing:

```crystal
ExampleJob.enqueue(param: "value")
```

### Perform a Job Immediately

If you want to execute a job immediately without enqueuing it:

```crystal
ExampleJob.perform(param: "value")
```

## Delay and Scheduling Methods

### Delaying a Job

To delay a job for a specific timespan before it is processed:

```crystal
ExampleJob.delay(2.minutes, param: "value")
```

### Enqueue After a Delay

To enqueue a job to be processed after a specified delay:

```crystal
ExampleJob.enqueue_at(1.minute, param: "value")
```

### Scheduling a Recurring Job

To schedule a job to run at a specific interval:

```crystal
ExampleJob.schedule(5.seconds, param: "value")
```

## Timeout Handling

Jobs can have a timeout to ensure they do not run indefinitely. Use `with_timeout` to enforce a timeout on the job's execution. If the block takes longer than the specified timeout, it raises a `Timeout::TimeoutError`.

```crystal
def perform
  with_timeout do
    # Simulate a long-running task
    puts "Starting a task that should timeout..."
    sleep 10.seconds
  rescue Timeout::TimeoutError => e
    puts e.message # => "execution expired after 5 seconds"
  end
end
```

### Example Usage

Here's a complete example demonstrating how to define, enqueue, and execute a job using `JoobQ::Job`:

```crystal
require "joobq"

# Define a job
class ExampleJob
  include JoobQ::Job
  property x : Int32

  def initialize(@x : Int32)
  end

  def perform
    puts "Performing job with x = #{x}"
  end
end

# Enqueue the job
ExampleJob.enqueue(x: 42)

# Delay the job
ExampleJob.delay(1.minute, x: 42)

# Schedule a recurring job
ExampleJob.schedule(10.minutes, x: 42)
```

In this example, `ExampleJob` is defined with a property `x` and a `perform` method. The job is enqueued, delayed, and scheduled for recurring execution.

{% hint style="info" %}
Best Practices for Defining Jobs

* **Idempotency: Ensure that your jobs are idempotent, meaning that running the job multiple times produces the same result. This is important in case of retries.**
* **Handle Failures Gracefully: Use retries to handle transient errors and ensure your `perform` method handles exceptions properly.**
* **Timeouts: Use `with_timeout` to prevent jobs from running indefinitely and consuming resources.**
* **Test Jobs Thoroughly: Write tests for your job logic to ensure that jobs perform as expected and handle edge cases correctly.**
  {% endhint %}

***

The `JoobQ::Job` module provides a flexible and powerful way to define asynchronous tasks that can be enqueued, delayed, and scheduled. Use these features to create reliable background jobs and streamline your application’s workflow!


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.joobq.io/guides/job.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
