Skip to main content
Custom workers in the iii Engine allow developers to extend the core functionality of the system. A worker acts as a container for logic that can register functions, triggers, and integrate with external systems.

Introduction

Workers are dynamically loaded and configured, often utilizing an Adapter pattern to allow for swappable backend implementations (e.g., swapping an in-memory event bus for a Redis-backed one). The engine provides a trait-based system where workers implement the CoreWorker trait for lifecycle management and the ConfigurableWorker trait for handling configuration and adapter injection.

Worker Architecture

The worker system is built around two primary traits: CoreWorker and ConfigurableWorker.

Core Traits

Lifecycle Flow

The following diagram illustrates the lifecycle of a worker from creation to initialization.

Implementing a Configurable Worker

Developing a custom worker typically involves defining an adapter interface, implementing specific adapters, and then wrapping them in a worker structure.

Step 1: Define the Adapter Trait

Define an async_trait that specifies the behavior your worker’s backend must implement. This allows users to switch implementations via configuration.
Why async_trait? Rust’s async traits require this macro to handle the complexity of async function pointers.

Step 2: Implement Adapter Registration

To make adapters discoverable by the configuration system, you must define a registration struct and use the inventory crate.
Purpose: This registration system allows the engine to discover and instantiate adapters dynamically based on configuration.

Step 3: Create Adapter Factories

Define factory functions that instantiate your specific adapter implementations (e.g., InMemory or Logging).

Step 4: Implement Adapter Logic

Create the actual adapter implementations.
Simple in-memory implementation for development and testing.
Wrapper adapter that logs all events while delegating to another adapter.

Step 5: Implement the Worker Logic

The worker struct holds the Engine reference and the injected Adapter.

Registering Functions

Workers expose functionality to the engine (and thus to SDK workers) by registering functions. This is typically done in the initialize method or register_functions.

Registration Request Structure

When registering a function, you must provide a RegisterFunctionRequest.
When using the #[service] macro with #[function] attributes, request_format and response_format are auto-generated as standard JSON Schema from your Rust types (via schemars). Your input/output types must derive JsonSchema. Manual schema specification is only needed for custom worker registration.

Example Registration

Handling Function Invocations

To handle invocations, the worker (or a specific handler struct) must implement the FunctionHandler trait.

Registering Triggers

Workers can also act as sources of events by registering TriggerTypes. This allows the engine to route external events (like Cron ticks or HTTP requests) to specific functions.

Trigger Architecture

Implementation

To support triggers, a worker implements TriggerRegistrator.
Then, register the trigger type during initialization:

Configuration

Workers are configured via iii-config.yaml or JSON passed during initialization. The ConfigurableWorker trait maps this configuration to a Rust struct.

Configuration Struct

Usage in Config File

Nested Adapters: The logging adapter wraps the in-memory adapter, creating a decorator pattern for cross-cutting concerns.

Complete Example

Here’s a complete custom worker implementation:

examples/custom_event_module.rs

iii-config.yaml

Best Practices

Always use adapters for external integrations to allow swapping implementations.
Handle cleanup in the worker’s drop implementation or provide shutdown hooks.
Always define request and response formats for functions to enable validation and documentation.
Use the tracing crate for structured logging with context.

Next Steps

Core Workers

Explore built-in core workers

Adapters

Learn more about the adapter pattern