How do you handle dependencies between Crossplane managed resources?
Quick Answer
Crossplane handles resource dependencies through references and selectors that create implicit ordering, where a managed resource can reference another resource's output fields, and Crossplane automatically waits for the referenced resource to be ready before proceeding with the dependent resource's creation.
Detailed Answer
Managing dependencies between infrastructure resources is fundamental to provisioning complex architectures. The payments-api database cannot be created before the VPC and subnets exist, the security group needs the VPC ID before it can be created, and the RDS instance needs both the subnet group and security group to be ready before it can launch. Crossplane handles these dependencies through a reference system that both establishes ordering and propagates values between resources, ensuring that the order-processing-service infrastructure is provisioned in the correct sequence without explicit dependency declarations.
The primary dependency mechanism is the reference and selector system built into every managed resource's spec. When a managed resource has a field that accepts a reference to another resource, Crossplane automatically resolves that reference by watching the referenced resource and waiting for it to become ready. For example, the SecurityGroup for the checkout-service database has a vpcIdRef that points to the VPC resource by name. Crossplane watches the VPC, waits for its external ID to be populated, resolves the VPC ID, and injects it into the security group's vpcId field. Until the VPC is ready, the security group remains in a pending state with a Synced condition message indicating it is waiting for the reference to resolve. This implicit ordering means platform teams do not need to declare an explicit depends_on like in Terraform.
Selectors provide a more flexible alternative to direct references when the exact resource name is not known at authoring time. Instead of referencing a specific VPC by name, a selector can match by labels, such as selecting the VPC with labels app: payments-api and environment: prod. This pattern is particularly powerful in Compositions where the composed resources are generated dynamically. The Composition assigns matching labels to the VPC and security group resources, and the security group uses a vpcIdSelector with matchControllerRef set to true, which automatically selects the VPC resource owned by the same composite resource. This controller reference matching ensures that the checkout-service security group only references the checkout-service VPC, even when multiple composite resources create VPCs in the same cluster.
Within Compositions, dependencies between composed resources use a combination of patches and the readinessChecks field. The Composition defines a VPC, a subnet, a subnet group, a security group, and an RDS instance as composed resources. Patches flow values between these resources using the FromCompositeFieldPath and ToCompositeFieldPath patch types. The VPC's ID is patched ToCompositeFieldPath into a status field on the composite resource, and the security group reads it back FromCompositeFieldPath. This two-hop patching through the composite resource creates an implicit dependency chain. The readinessChecks on each composed resource define when that resource is considered ready, and dependent resources will show their references as unresolvable until the upstream resource passes its readiness check. For the inventory-sync service, the RDS instance readiness check verifies that the status field equals Available, and downstream resources like Route53 DNS records only get created after the database endpoint is populated.
Cross-resource dependencies in complex architectures sometimes require external resources that exist outside the current Composition. The user-auth-service database might need to reference a shared VPC managed by the networking team's Composition. Crossplane supports this through cross-resource references using the resource's name or through the usage resource type. The Usage resource declares that one resource depends on another, and Crossplane enforces that the depended-upon resource cannot be deleted while the usage relationship exists. This prevents scenarios where the networking team deletes the shared VPC while the user-auth-service RDS instance still needs it. Usage resources also support the reason field to document why the dependency exists, creating a self-documenting infrastructure graph that teams can query to understand blast radius before making changes.
A common production pitfall is circular dependencies where resource A references resource B and resource B references resource A. Crossplane detects some circular reference patterns and reports them as errors, but indirect cycles through the composite resource status fields can be harder to diagnose. Teams should design their Composition dependency graphs as directed acyclic graphs and document the flow in architecture diagrams. Another gotcha is that reference resolution failures are retried indefinitely, which means a typo in a reference name silently blocks provisioning without any timeout. Platform teams should monitor for resources stuck in the Synced=False state with reference resolution errors and set up alerts that trigger after a configurable threshold to catch these issues before the order-processing-service team opens a support ticket wondering why their database has been pending for hours.