Collection Semantics
When a resource property contains a collection (list of values or objects), formae needs to know how to compare the current state with the desired state. The updateMethod field hint controls this behavior.
The Three Collection Types
Set (Default)
When no updateMethod is specified, collections are treated as unordered sets. Elements are compared regardless of their position in the list.
@FieldHint {}
securityGroups: Listing<String>?
Behavior:
- Order doesn't matter:
["a", "b", "c"]equals["c", "a", "b"] - Duplicates are ignored
- Adding an element that already exists results in no change
- In reconcile mode, elements not in the desired state are removed
Use for: Simple lists where order doesn't matter (security group IDs, availability zones, etc.)
Array
Use updateMethod = "Array" when the order of elements matters.
@FieldHint {
updateMethod = "Array"
}
processingSteps: Listing<String>?
Behavior:
- Position-based comparison
["a", "b"]does NOT equal["b", "a"]- Elements at the same index are compared directly
Use for: Ordered lists where sequence is significant (processing pipelines, priority lists, etc.)
EntitySet
Use updateMethod = "EntitySet" with indexField for collections of objects identified by a key.
@FieldHint {
updateMethod = "EntitySet"
indexField = "Key"
}
tags: Listing<Tag>?
Behavior:
- Objects are matched by their key field (e.g.,
Keyfor tags) - If an object with the same key exists, only changed properties are updated
- New keys result in add operations
- Missing keys in reconcile mode result in remove operations
Use for: Key-value collections like tags, environment variables, or any list of objects with a unique identifier.
Example: AWS Tags
The most common use of EntitySet is for resource tags:
// Plain class - no FieldHints needed on tag properties
open class Tag {
hidden key: String
hidden value: Any
fixed Key: String = key
fixed Value: Any = value
}
@ResourceHint {
type = "AWS::EC2::VPC"
identifier = "VpcId"
}
open class Vpc extends formae.Resource {
@FieldHint {
updateMethod = "EntitySet"
indexField = "Key"
}
tags: Listing<Tag>?
}
With this schema, updating a tag value results in a targeted update that only changes the value, not the entire tag.
How It Works
When formae compares current state with desired state, it uses the updateMethod hint to determine the comparison strategy:
- No hint or Set: Compare collections as unordered sets
- Array: Compare collections position-by-position
- EntitySet: Match objects by
indexField, then compare matched objects property-by-property
Reconcile vs Patch Mode
The apply mode significantly affects how collections are handled:
Reconcile mode (--mode reconcile, default): The desired state is the complete truth. Elements not in your forma file are removed from the actual resource.
Patch mode (--mode patch): Collections are append-only. Elements in your forma file are added if missing, but existing elements are never removed.
This append-only behavior in patch mode applies to all three collection types:
| Collection Type | Reconcile Mode | Patch Mode |
|---|---|---|
| Set | Elements not in desired state are removed | New elements added, existing preserved |
| Array | Elements not in desired state are removed | New elements appended, existing preserved |
| EntitySet | Keys not in desired state are removed | New keys added, existing keys preserved |
Example: If the actual resource has tags [A, B, C] and your forma file specifies [B, D]:
- Reconcile: Result is
[B, D](A and C removed) - Patch: Result is
[A, B, C, D](D added, nothing removed)
Choosing the Right Semantics
| Scenario | updateMethod | indexField |
|---|---|---|
| List of primitive values, order irrelevant | (none) | - |
| List of primitive values, order matters | Array |
- |
| List of objects with unique key | EntitySet |
Key field name |
| List of objects without unique key | (none) | - |
When in doubt, start with the default (Set) semantics. Only specify Array or EntitySet when you have a specific reason.