Components#
A component is a reusable unit of state. It’s the Orchard equivalent of a function: it takes inputs, performs some work, and returns outputs. Scenarios compose components to describe complex state without repeating themselves.
Defining a component#
A component lives in its own file. The file contains one component "<name>" { ... }
block:
# components/merchant.hcl
component "merchant" {
variable "name" {
default = "Default Merchant"
}
variable "region" {
default = "us"
}
action "postgres_query" "create" {
query = "INSERT INTO merchants (name, region) VALUES ('${var.name}', '${var.region}') RETURNING id, name, region"
}
output "id" { value = action.postgres_query.create.id }
output "name" { value = action.postgres_query.create.name }
output "region" { value = action.postgres_query.create.region }
}A component has the same vocabulary as a scenario — variables, actions, outputs —
but no required_providers or provider blocks. Components inherit their providers
from the enclosing scenario.
Using a component#
Scenarios instantiate a component with component "<type>" "<name>" { ... }:
component "merchant" "acme" {
source = "../components/merchant.hcl"
inputs = {
name = "Acme Corp"
region = "us"
}
}- Two labels. The first matches the component’s declared name (
merchant). The second is an instance name (acme) — unique within the scenario. sourceis a path to the component file, resolved relative to the file that contains thecomponentblock.inputsis an object literal. Keys must match the component’s declared variables; required variables (those with no default) must be present.
Consuming component outputs#
Component instances expose their outputs as component.<type>.<name>.<output>:
action "postgres_query" "create_product" {
query = "INSERT INTO products (merchant_id, name, price) VALUES (${component.merchant.acme.id}, 'Widget', 9.99) RETURNING id"
}Orchard parses the reference, adds a dependency edge, and runs the merchant.acme
component before create_product.
Why use components#
- Reuse. A well-designed
merchantcomponent can be instantiated many times across many scenarios with different inputs. - Abstraction. A component hides the SQL/HTTP/exec details behind a named interface. Scenario authors think in terms of “a merchant”, not in terms of three SQL inserts.
- Testability. Components can be exercised in isolation with a scenario that instantiates just that component.
When not to use components#
- One-off work. If the SQL or HTTP call only appears in one scenario, just use
an
actiondirectly. Don’t abstract prematurely. - State that spans scenarios. Components are stateless definitions — they describe how to produce state, not how to track it across runs. If you need state persistence, that belongs in the target system, not in Orchard.