Skip to content

Job Definitions

Job Definitions are resources that wrap components to make them schedulable. They define what code should run when a job is executed.

What are Job Definitions?

A Job Definition is a simple resource that associates a unique ID with a component. It acts as a template for creating jobs:

Component (code) → Job Definition (wrapper) → Job (scheduled instance)

Why Job Definitions?

Job Definitions provide a layer of indirection that enables:

  • Reusability: One component can have multiple job definitions
  • Organization: Group related scheduled tasks
  • Permissions: Control who can schedule which components
  • Statistics: Track performance by job definition

Structure

A Job Definition consists of just two fields:

  • jobDefinitionId - Unique identifier for the definition
  • componentId - Which component to execute
yaml
uri: /resources/job-definitions/nightly-order-processing
spec:
  componentId: process-orders.hreactor

Complete Example

Here's how to create a scheduled background task:

  1. Create a component with the logic to execute:

    filtrera
    //process-orders.hreactor
    import 'resources'
    
    param processingDate: text
    
    let orders = query orders(orderNumber)
      filter $'createdAt >= {processingDate}'
      orderBy 'createdAt asc'
    
    from orders select order =>
      sendEmail {
        to = order.customer.email
        subject = 'Processing Update'
        body = { html = '<p>Your order is being processed</p>' }
        dynamic = { orderId = order.id }
      }
  2. Create a job definition that wraps the component:

    yaml
    uri: /resources/components/process-orders.hreactor
    spec:
      codeFile: process-orders.hreactor
    
    ---
    uri: /resources/job-definitions/nightly-order-processing
    spec:
      componentId: process-orders.hreactor
  3. Deploy the manifest:

    bash
    h_ manage apply h_manifest.yaml
  4. Schedule a job using the definition:

    http
    POST /resources/jobs
    {
      "jobDefinitionId": "nightly-order-processing",
      "parameters": {
        "processingDate": "2025-11-15T00:00:00Z"
      },
      "runAt": "2025-11-16T02:00:00Z"
    }

Now the component will run at 2 AM with the specified parameters.

One Component, Multiple Definitions

A single component can be wrapped by multiple job definitions for different purposes:

yaml
# Same component for different use cases
uri: /resources/job-definitions/hourly-sync
spec:
  componentId: data-sync.hreactor

---
uri: /resources/job-definitions/daily-full-sync
spec:
  componentId: data-sync.hreactor

---
uri: /resources/job-definitions/manual-sync
spec:
  componentId: data-sync.hreactor

This allows:

  • Different scheduling patterns (hourly vs daily)
  • Different monitoring/statistics tracking
  • Different permission scopes
  • Same underlying logic

Managing Job Definitions

Job definitions are managed via the /resources/job-definitions API:

  • Create/Update: PUT /resources/job-definitions/{definitionId}
  • Get: GET /resources/job-definitions/{definitionId}
  • List: GET /resources/job-definitions
  • Delete: DELETE /resources/job-definitions/{definitionId}

See the API Reference for complete endpoint documentation.

WARNING

Deletion Warning: If you delete a job definition while scheduled jobs for it still exist, those jobs will fail when they attempt to run. Consider waiting for pending jobs to complete before deleting definitions.

Job Definition vs Job

Job Definition:

  • Template/configuration
  • Points to a component
  • Permanent resource
  • One per scheduled task type

Job:

  • Scheduled instance
  • References a job definition
  • Temporary (deleted after retention period)
  • Many instances per definition

Example:

  • Job Definition: nightly-order-processing (permanent)
  • Jobs: Individual executions (Nov 15 2AM, Nov 16 2AM, Nov 17 2AM, etc.)

Access Control

Managing job definitions requires permissions:

job-definitions:read     # View job definitions
job-definitions:write    # Create, update, delete job definitions

Statistics & Monitoring

Job statistics are tracked by jobDefinitionId, making it easy to monitor performance of specific scheduled tasks:

http
GET /resources/jobs/statistics?jobDefinitionId=nightly-order-processing

This allows you to track:

  • Success/failure rates for this specific task
  • Execution time trends
  • Queue depth for this definition

See Jobs documentation for complete statistics information.

Recurring Jobs Pattern

Hantera doesn't have built-in cron scheduling. Instead, components can schedule themselves to create recurring jobs using relative time scheduling.

How It Works

A component schedules the next run at the end of its execution:

filtrera
//nightly-processor.hreactor

// Do the work
let orders = query orders(orderNumber)
  filter 'status = pending'

let processResult = orders select order => processOrder(order)

// Schedule next run (24 hours from now)
from {
  effect = 'scheduleJob'
  definition = 'nightly-order-processing'
  at = now addDays 1
  parameters = {}
}

This creates a self-perpetuating chain: Job completes → Schedules next run → Next job executes → Schedules another run → ...

INFO

Timing: The at time is relative to when the job completes, not when it starts. If a job takes 10 minutes to run and schedules itself with now addHours 1, the next run is 1 hour and 10 minutes from the previous start.

Common Patterns

Hourly Processing

filtrera
// At end of component
from {
  effect = 'scheduleJob'
  definition = 'hourly-sync'
  at = now addHours 1
  parameters = {}
}

Daily Processing

filtrera
// At end of component
from {
  effect = 'scheduleJob'
  definition = 'daily-report'
  at = now addDays 1
  parameters = {}
}

Weekly Processing

filtrera
// At end of component
from {
  effect = 'scheduleJob'
  definition = 'weekly-cleanup'
  at = now addDays 7
  parameters = {}
}

Custom Intervals

filtrera
// Every 30 minutes
from {
  effect = 'scheduleJob'
  definition = 'frequent-sync'
  at = now addMinutes 30
  parameters = {}
}

Starting the Recurring Chain

Create the first job manually via API or rule to start the chain:

http
POST /resources/jobs
{
  "jobDefinitionId": "nightly-order-processing",
  "parameters": {},
  "runAt": "2025-11-16T02:00:00Z"
}

After this first execution, the job schedules itself automatically.

Stopping Recurring Jobs

To stop a recurring job chain, cancel the next pending job:

http
DELETE /resources/jobs/{jobId}

When a job is cancelled, it never executes, so it never schedules the next job. The chain breaks automatically.

Alternative methods:

  • Update the component to remove self-scheduling logic (permanent change)
  • Delete the job definition (will fail any pending jobs)

Best Practices

Use Descriptive IDs

Choose clear job definition IDs that describe purpose and frequency:

  • hourly-inventory-sync, daily-report-generation, weekly-cleanup
  • job1, sync, task

Use Separate Definitions for Different Purposes

Create separate job definitions when you need distinct monitoring or different use cases:

yaml
# Same component, different purposes/monitoring
uri: /resources/job-definitions/sync-customers
spec:
  componentId: sync.hreactor

---
uri: /resources/job-definitions/sync-products
spec:
  componentId: sync.hreactor

This allows independent statistics tracking and clearer monitoring of each use case.

Check for Pending Jobs Before Deletion

Before deleting a job definition, verify no pending jobs exist:

http
GET /resources/jobs?jobDefinitionId=my-definition&status=pending

See Also

© 2024 Hantera AB. All rights reserved.