|
| 1 | += ADR 0045 - Service Orchestration: Crossplane 2.x |
| 2 | +:adr_author: Gabriel Saratura |
| 3 | +:adr_owner: Schedar |
| 4 | +:adr_reviewers: Schedar |
| 5 | +:adr_date: |
| 6 | +:adr_upd_date: |
| 7 | +:adr_status: draft |
| 8 | +:adr_tags: framework,framework2,crossplane,composition,orchestration |
| 9 | + |
| 10 | +include::partial$adr-meta.adoc[] |
| 11 | + |
| 12 | +[NOTE] |
| 13 | +.Summary |
| 14 | +==== |
| 15 | +This ADR defines the service orchestration mechanism for AppCat Framework 2.0, evaluating Crossplane 2.x composition functions against a custom Kubernetes operator. |
| 16 | +
|
| 17 | +The decision selects https://www.crossplane.io/[Crossplane] 2.0 with composition functions as the orchestration layer, providing generic service management with built-in revision control and clear separation between framework code (https://go.dev/[Go]) and service configuration (https://www.kcl-lang.io/[KCL]). |
| 18 | +==== |
| 19 | + |
| 20 | +== Context |
| 21 | + |
| 22 | +=== Problem Statement |
| 23 | + |
| 24 | +The AppCat Framework 2.0 requires a service orchestration mechanism that: |
| 25 | + |
| 26 | +* Renders Helm charts based on service configuration and user-selected parameters |
| 27 | +* Manages complete service lifecycle (create, update, delete) |
| 28 | +* Handles connection secrets and billing/maintenance/backup/metrics integration |
| 29 | +* Allows Framework Engineers to maintain core logic in Go |
| 30 | +* Enables Service Maintainers to add new services without touching Go code |
| 31 | +* Provides versioning and revision management for controlled rollouts |
| 32 | + |
| 33 | +=== Architectural Context |
| 34 | + |
| 35 | +The framework architecture separates concerns across three layers: |
| 36 | + |
| 37 | +1. **Framework Engineers** maintain the orchestration runtime (Go composition functions) |
| 38 | +2. **Service Maintainers** define service configurations (KCL compiled managed resource objects) |
| 39 | +3. **Service Users** consume services via declarative API (XRD/Composite resources) |
| 40 | + |
| 41 | +The orchestration layer sits at the boundary between framework runtime and service configuration, responsible for: |
| 42 | + |
| 43 | +* Loading service configurations at runtime |
| 44 | +* Merging static defaults with dynamic user parameters |
| 45 | +* Generating Kubernetes resources (HelmRelease, K8up backup schedules, ServiceMonitor, secrets, etc.) |
| 46 | +* Tracking revisions for upgrade workflows |
| 47 | +* Propagating status and errors to users |
| 48 | + |
| 49 | +== Options |
| 50 | + |
| 51 | +=== Option A: Crossplane 2.x with Composition Functions |
| 52 | + |
| 53 | +==== Description |
| 54 | + |
| 55 | +https://www.crossplane.io/[Crossplane] 2.0 composition functions execute in pipeline mode during reconciliation, receiving composite state and returning desired resources. |
| 56 | +A single generic function handles all services by loading service-specific configuration Kubernetes like object (ex:, `RedisServiceConfig`) at runtime, merging user parameters, and generating resources. |
| 57 | +Other functions can be added to increase framework quality such as increased service resilience and error handling. |
| 58 | + |
| 59 | +==== Advantages |
| 60 | + |
| 61 | +* **Generic Function Pattern**: Single function handles all services without Go code changes |
| 62 | +* **Built-in Composition Revisions**: Native versioning and gradual rollout capabilities |
| 63 | +* **Clear Separation**: Framework code (Go) completely separated from service configuration (KCL) |
| 64 | +* **Provider Ecosystem**: Leverage existing Crossplane providers (Helm, SQL) instead of custom implementations |
| 65 | +* **Status Aggregation**: Automatic status propagation from managed resources |
| 66 | +* **Existing Team Knowledge**: Team already proficient with Crossplane patterns |
| 67 | + |
| 68 | +==== Disadvantages |
| 69 | + |
| 70 | +* **Learning Curve**: Crossplane concepts (XRD, Composition, Revisions) require initial learning investment, though team already has experience |
| 71 | +* **Abstraction Overhead**: Multiple layers (Composite → Composition Function → HelmRelease → Helm Chart) vs direct operator reconcile loop |
| 72 | +* **Debugging Complexity**: Tracing issues through pipeline requires understanding Crossplane request/response flow and function logs |
| 73 | +* **Function Packaging**: Each function version requires OCI image build and registry push |
| 74 | +* **Performance**: Function invoked via gRPC adds latency compared to in-process operator reconciliation |
| 75 | + |
| 76 | +=== Option B: Custom Kubernetes Operator |
| 77 | + |
| 78 | +==== Description |
| 79 | + |
| 80 | +Build a custom Kubernetes operator using https://github.com/operator-framework/operator-sdk[operator-sdk] that directly reconciles service CRDs and renders Helm charts. |
| 81 | +The operator would either use per-service controllers or a generic controller loading service configuration at runtime, with custom implementations for revision management and upgrade workflows. |
| 82 | + |
| 83 | +==== Advantages |
| 84 | + |
| 85 | +* **Simpler Mental Model**: Direct reconciliation loop easier to understand than pipeline abstraction |
| 86 | +* **Faster Debugging**: Single operator process, direct logging, simpler to trace issues with standard Go debugging |
| 87 | +* **More Control**: Full control over reconciliation logic, status updates, error handling, retry behavior |
| 88 | +* **No Crossplane Dependency**: One less external dependency to manage and upgrade |
| 89 | +* **Familiar Pattern**: Standard operator pattern widely used across Kubernetes ecosystem |
| 90 | +* **Performance**: In-process reconciliation potentially faster than gRPC function invocation |
| 91 | + |
| 92 | +==== Disadvantages |
| 93 | + |
| 94 | +* **Framework Coupling**: Risk of service-specific logic creeping into operator code over time |
| 95 | +* **No Built-in Revisions**: Must implement version management, gradual rollouts, and revision tracking ourselves (complex) |
| 96 | +* **Reinventing Wheels**: Helm integration, status aggregation, provider patterns already exist in Crossplane |
| 97 | +* **More Code to Maintain**: Custom implementations needed for operator, CRD management, status propagation, connection secrets all custom implementations |
| 98 | +* **Migration Cost**: Existing Crossplane investment (team knowledge, component-appcat patterns) cannot be leveraged |
| 99 | +* **Revision Management**: Composition revisions essential for maintenance workflows would require significant custom development |
| 100 | + |
| 101 | +=== Options Not Investigated |
| 102 | + |
| 103 | +The following orchestration approaches were considered but excluded early as they do not meet core requirements: |
| 104 | + |
| 105 | +* **https://fluxcd.io/[Helm Operator (Flux)]**: Flux's Helm controller reconciles HelmRelease resources but lacks service configuration logic, connection secret generation, billing integration - essentially requires rebuilding Crossplane capabilities. |
| 106 | + |
| 107 | +* **https://developer.hashicorp.com/terraform[Terraform Operator]**: Terraform excels at cloud infrastructure provisioning but adds unnecessary complexity for Kubernetes-native workloads. |
| 108 | +Requires maintaining Terraform modules alongside Helm charts, mixing declarative models, and does not provide native Kubernetes resource management. |
| 109 | + |
| 110 | +* **https://kubevela.io/[KubeVela]**: Application-centric platform with promising capabilities but immature ecosystem compared to Crossplane. |
| 111 | +Application model doesn't align with "Helm Chart in" vision, and has less adoption in managed service space. |
| 112 | +Provides similar capabilities to Crossplane but with smaller community and fewer production deployments. |
| 113 | + |
| 114 | +These options lack critical features (revision management, status aggregation, provider ecosystem) or introduce unnecessary abstraction layers without clear benefits over Crossplane 2.x composition functions. |
| 115 | + |
| 116 | +== Decision |
| 117 | + |
| 118 | +**We use Crossplane 2.x with Composition Functions** as the service orchestration mechanism for AppCat Framework 2.x. |
| 119 | + |
| 120 | +=== Rationale |
| 121 | + |
| 122 | +The decision prioritizes long-term maintainability and scalability over short-term simplicity: |
| 123 | + |
| 124 | +1. **Built-in Revision Management**: Crossplane composition revisions provide native versioning and gradual rollout capabilities essential for maintenance workflows. |
| 125 | +Building equivalent functionality in a custom operator would be complex and require significant ongoing maintenance. |
| 126 | + |
| 127 | +2. **Generic Function Pattern**: A single composition function handles all services by loading runtime configuration, eliminating the need for service-specific Go code. |
| 128 | +This enables Service Maintainers to add services independently while keeping the framework stable. |
| 129 | + |
| 130 | +3. **Leverage Existing Ecosystem**: Crossplane providers (Helm, SQL) provide mature implementations for chart rendering, resource management, and status aggregation. |
| 131 | +Reimplementing these capabilities in a custom operator offers no clear benefit. |
| 132 | + |
| 133 | +4. **Existing Team Knowledge**: The team is already proficient with Crossplane from component-appcat, reducing adoption risk and enabling faster implementation. |
| 134 | + |
| 135 | +**Trade-off Accepted:** We accept higher abstraction complexity in exchange for built-in revision management, provider ecosystem leverage, and generic function scalability. |
0 commit comments