REUSE A MODEL WITH THE FACTORY PATTERN
This guide shows you how to define a single model 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 model type installed for the resources you want to create
- Familiarity with model 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 model) |
| 3 EIPs with different tags | Factory pattern (1 model) |
| A VPC and a subnet (different resource types) | Separate models |
| Resources with different method signatures | Separate models |
| Long-running methods that must run in parallel | Factory pattern (separate locks) |
Each model holds an exclusive per-model lock for the duration of a method
execution. If two workflow steps target the same model, 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 model
Create the model, then add an inputs section with an instanceName property
and whatever parameters vary between instances:
swamp model create @user/aws-subnet prod-subnet --jsonEdit the model 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 model:
swamp model validate prod-subnet --jsonCall the model 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: model_method
modelIdOrName: prod-subnet
methodName: create
inputs:
instanceName: public-a
cidrBlock: "10.0.1.0/24"
availabilityZone: us-east-1a
- name: create-public-b
task:
type: model_method
modelIdOrName: prod-subnet
methodName: create
inputs:
instanceName: public-b
cidrBlock: "10.0.2.0/24"
availabilityZone: us-east-1b
- name: create-private-a
task:
type: model_method
modelIdOrName: prod-subnet
methodName: create
inputs:
instanceName: private-a
cidrBlock: "10.0.3.0/24"
availabilityZone: us-east-1a
- name: create-private-b
task:
type: model_method
modelIdOrName: 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
model.
Reference factory instance data downstream
Use data.latest() with the model name and instance name to read a specific
factory output in a downstream model or workflow step:
globalArguments:
subnetId: ${{ data.latest("prod-subnet", "public-a").attributes.SubnetId }}To browse all instances produced by a factory model:
swamp data query 'modelName == "prod-subnet"' \
--select '{"name": name, "subnet": attributes.SubnetId, "az": attributes.AvailabilityZone}'Write delete steps for factory models
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: model_method
modelIdOrName: prod-subnet
methodName: delete
inputs:
instanceName: public-a
identifier: ${{ data.latest("prod-subnet", "public-a").attributes.SubnetId }}
- name: delete-public-b
task:
type: model_method
modelIdOrName: 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
- Model 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