> For the complete documentation index, see [llms.txt](https://light-services.kodkod.me/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://light-services.kodkod.me/deep-dive/configuration.md).

# Configuration

Operandi provides a flexible configuration system that allows you to customize behavior at three levels: global, per-service, and per-call.

## Global Configuration

Configure Operandi globally using an initializer. For Rails applications, create `config/initializers/operandi.rb`:

```ruby
Operandi.configure do |config|
  # Type enforcement
  config.require_arg_type = true        # Require type option for all arguments
  config.require_output_type = true     # Require type option for all outputs

  # Transaction settings
  config.use_transactions = true        # Wrap each service in a database transaction

  # Error behavior
  config.load_errors = true             # Copy errors to parent service in context chain
  config.break_on_error = true          # Stop step execution when an error is added
  config.raise_on_error = false         # Raise an exception when an error is added
  config.rollback_on_error = true       # Rollback transaction when an error is added

  # Warning behavior
  config.load_warnings = true           # Copy warnings to parent service in context chain
  config.break_on_warning = false       # Stop step execution when a warning is added
  config.raise_on_warning = false       # Raise an exception when a warning is added
  config.rollback_on_warning = false    # Rollback transaction when a warning is added
end
```

## Default Values

| Option                | Default | Description                                                                         |
| --------------------- | ------- | ----------------------------------------------------------------------------------- |
| `require_arg_type`    | `true`  | Raises `Operandi::MissingTypeError` when defining arguments without a `type` option |
| `require_output_type` | `true`  | Raises `Operandi::MissingTypeError` when defining outputs without a `type` option   |
| `use_transactions`    | `true`  | Wraps service execution in `ActiveRecord::Base.transaction`                         |
| `load_errors`         | `true`  | Propagates errors to parent service when using `.with(self)`                        |
| `break_on_error`      | `true`  | Stops executing remaining steps when an error is added                              |
| `raise_on_error`      | `false` | Raises `Operandi::Error` when an error is added                                     |
| `rollback_on_error`   | `true`  | Rolls back the transaction when an error is added                                   |
| `load_warnings`       | `true`  | Propagates warnings to parent service when using `.with(self)`                      |
| `break_on_warning`    | `false` | Stops executing remaining steps when a warning is added                             |
| `raise_on_warning`    | `false` | Raises `Operandi::Error` when a warning is added                                    |
| `rollback_on_warning` | `false` | Rolls back the transaction when a warning is added                                  |

## Per-Service Configuration

Override global configuration for a specific service class using the `config` class method:

```ruby
class CriticalPaymentService < ApplicationService
  # This service will raise exceptions instead of collecting errors
  config raise_on_error: true

  step :process_payment
  step :send_receipt

  # ...
end
```

```ruby
class NonCriticalNotificationService < ApplicationService
  # This service doesn't need transactions and shouldn't stop on errors
  config use_transactions: false, break_on_error: false

  step :send_push_notification
  step :send_email_notification

  # ...
end
```

## Per-Call Configuration

Override configuration for a single service call:

```ruby
# Pass config as second argument to run
MyService.run({ name: "John" }, { raise_on_error: true })

# Or use with() for context-based calls
MyService.with({ raise_on_error: true }).run(name: "John")

# Combine with parent service context
ChildService
  .with(self, { use_transactions: false })
  .run(data: some_data)
```

## Configuration Precedence

Configuration is merged in this order (later overrides earlier):

1. Global configuration (from initializer)
2. Per-service configuration (from `config` class method)
3. Per-call configuration (from `run` or `with` arguments)

```ruby
# Global: raise_on_error = false
Operandi.configure do |config|
  config.raise_on_error = false
end

# Per-service: raise_on_error = true (overrides global)
class MyService < ApplicationService
  config raise_on_error: true
end

# Per-call: raise_on_error = false (overrides per-service)
MyService.run(args, { raise_on_error: false })
```

## Common Configuration Patterns

### Strict Mode for Critical Services

```ruby
class Payment::Process < ApplicationService
  config raise_on_error: true, rollback_on_error: true
  
  # Any error will raise an exception and rollback the transaction
end
```

### Fire-and-Forget Services

```ruby
class Analytics::Track < ApplicationService
  config use_transactions: false, break_on_error: false, load_errors: false
  
  # Errors won't stop execution or propagate to parent services
end
```

### Background Job Services

```ruby
class BackgroundTaskService < ApplicationService
  # Background jobs typically handle their own transactions
  config use_transactions: false
end
```

### Type Enforcement (Enabled by Default)

By default, all arguments and outputs must have a `type` option. This helps catch type-related bugs early and makes your services self-documenting.

```ruby
class User::Create < ApplicationService
  arg :name, type: String        # ✓ Valid
  arg :email                     # ✗ Raises MissingTypeError
  output :user, type: User       # ✓ Valid
  output :token                  # ✗ Raises MissingTypeError
end
```

To disable type enforcement globally (not recommended):

```ruby
Operandi.configure do |config|
  config.require_arg_type = false     # Disable for arguments
  config.require_output_type = false  # Disable for outputs
end
```

Or disable for specific services:

```ruby
class LegacyService < ApplicationService
  config require_arg_type: false, require_output_type: false
  
  arg :data              # Allowed when require_arg_type is disabled
  output :result         # Allowed when require_output_type is disabled
end
```

You can also control them independently:

```ruby
class StrictInputService < ApplicationService
  # Require types for arguments but not outputs
  config require_arg_type: true, require_output_type: false
  
  arg :data, type: Hash  # Type required
  output :result         # Type not required
end
```

## Disabling Transactions

If you're not using ActiveRecord or want to manage transactions yourself:

```ruby
Operandi.configure do |config|
  config.use_transactions = false
end
```

Or disable for specific services:

```ruby
class MyService < ApplicationService
  config use_transactions: false
end
```

{% hint style="info" %}
When `use_transactions` is `true`, Operandi uses `ActiveRecord::Base.transaction(requires_new: true)` to create savepoints, allowing nested services to rollback independently.
{% endhint %}

## What's Next?

Now that you understand configuration, learn about the core concepts:

[Next: Concepts](/introduction/concepts.md)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## 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, and the optional `goal` query parameter:

```
GET https://light-services.kodkod.me/deep-dive/configuration.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

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.
