REUSE A BEHAVIOR WITH THE FACTORY PATTERN
This guide shows you how to define a single behavior and call it multiple times with different inputs to produce distinct resource instances. Use this when you have several resources of the same type — subnets, security groups, DNS records — that share the same schema but differ in their parameters.
Prerequisites
- Swamp installed and on your
PATH - An initialised repo (
swamp repo init) - A behavior type installed for the resources you want to create
- Familiarity with behavior definitions and workflow definitions
Decide whether the factory pattern fits
The factory pattern applies when multiple resources share a type and method signatures but differ only in their parameter values.
| Situation | Approach |
|---|---|
| 4 subnets with different CIDRs and AZs | Factory pattern (1 behavior) |
| 3 EIPs with different tags | Factory pattern (1 behavior) |
| A VPC and a subnet (different resource types) | Separate behaviors |
| Resources with different method signatures | Separate behaviors |
| Long-running methods that must run in parallel | Factory pattern (separate locks) |
Each behavior holds an exclusive per-behavior lock for the duration of a method
execution. If two workflow steps target the same behavior, the second waits for
the first to release the lock. The factory pattern sidesteps this: each call
with a different instanceName produces a separate data instance, so concurrent
steps do not contend.
Add an inputs schema to the behavior
Create the behavior, then add an inputs section with an instanceName
property and whatever parameters vary between instances:
swamp behavior create @user/aws-subnet prod-subnet --jsonEdit the behavior definition at the returned path:
name: prod-subnet
version: 1
tags: {}
inputs:
properties:
instanceName:
type: string
description: Unique name for this subnet instance
cidrBlock:
type: string
description: CIDR block for the subnet
availabilityZone:
type: string
description: AWS availability zone
required: ["instanceName", "cidrBlock", "availabilityZone"]
globalArguments:
name: ${{ inputs.instanceName }}
VpcId: ${{ data.latest("prod-vpc", "main").attributes.VpcId }}
CidrBlock: ${{ inputs.cidrBlock }}
AvailabilityZone: ${{ inputs.availabilityZone }}
Tags:
- Key: Name
Value: ${{ inputs.instanceName }}
methods:
create:
arguments: {}
delete:
arguments: {}The name: ${{ inputs.instanceName }} line is what makes the pattern work. It
sets the data instance name, so each call with a different instanceName
produces a separately addressable data artifact.
Validate the behavior:
swamp behavior validate prod-subnet --jsonCall the behavior multiple times from a workflow
Create a workflow with one step per instance. Steps within a job run in parallel:
name: create-subnets
version: 1
jobs:
- name: create-subnets
steps:
- name: create-public-a
task:
type: behavior
behavior: prod-subnet
methodName: create
inputs:
instanceName: public-a
cidrBlock: "10.0.1.0/24"
availabilityZone: us-east-1a
- name: create-public-b
task:
type: behavior
behavior: prod-subnet
methodName: create
inputs:
instanceName: public-b
cidrBlock: "10.0.2.0/24"
availabilityZone: us-east-1b
- name: create-private-a
task:
type: behavior
behavior: prod-subnet
methodName: create
inputs:
instanceName: private-a
cidrBlock: "10.0.3.0/24"
availabilityZone: us-east-1a
- name: create-private-b
task:
type: behavior
behavior: prod-subnet
methodName: create
inputs:
instanceName: private-b
cidrBlock: "10.0.4.0/24"
availabilityZone: us-east-1bRun the workflow:
swamp workflow run create-subnetsAll four steps execute concurrently. Each produces a distinct data instance
(public-a, public-b, private-a, private-b) under the prod-subnet
behavior.
Reference factory instance data downstream
Use data.latest() with the behavior name and instance name to read a specific
factory output in a downstream behavior or workflow step:
globalArguments:
subnetId: ${{ data.latest("prod-subnet", "public-a").attributes.SubnetId }}To browse all instances produced by a factory behavior:
swamp data query 'behaviorName == "prod-subnet"' \
--select '{"name": name, "subnet": attributes.SubnetId, "az": attributes.AvailabilityZone}'Write delete steps for factory behaviors
Delete steps must provide instanceName so the system knows which data instance
to target. Other inputs are only required if the delete method's implementation
accesses those globalArguments at runtime — unresolved expressions for inputs
that are not provided are silently skipped.
name: delete-subnets
version: 1
jobs:
- name: delete-subnets
steps:
- name: delete-public-a
task:
type: behavior
behavior: prod-subnet
methodName: delete
inputs:
instanceName: public-a
identifier: ${{ data.latest("prod-subnet", "public-a").attributes.SubnetId }}
- name: delete-public-b
task:
type: behavior
behavior: prod-subnet
methodName: delete
inputs:
instanceName: public-b
identifier: ${{ data.latest("prod-subnet", "public-b").attributes.SubnetId }}If the delete steps depend on other resources being removed first, use job-level
dependsOn to enforce reverse dependency order — see
dependsOn.
Reference
- Behavior definitions — full schema
for
inputs,globalArguments, andmethods - CEL expressions —
data.latest(),data.query(), and theinputs.*namespace - Workflows — job and step ordering,
dependsOn, and parallel execution