Clean Architecture ????????? Crossplane ?????????????????????
Clean Architecture ???????????????????????????????????????????????? software ????????? Robert C. Martin (Uncle Bob) ?????????????????? concerns ????????????????????? layers ?????????????????? Inner layers ?????????????????????????????? outer layers ??????????????? code maintainable, testable ????????? flexible ????????????????????????????????????????????? Crossplane Compositions ?????????????????????????????????????????????????????????????????? infrastructure-as-code ?????????????????? abstraction layers ??????????????????
Crossplane Composition ???????????????????????????????????? Clean Architecture ???????????????????????? XRD Layer (API Contract) ??????????????? interface ????????? application teams ????????? ??????????????????????????? cloud provider, Composition Layer (Business Logic) ??????????????????????????? provision resources ????????? provider, Managed Resource Layer (Infrastructure) resources ?????????????????? cloud provider
???????????????????????? Clean Architecture ?????????????????? Crossplane Portability ????????????????????? cloud provider ???????????????????????? application teams, Testability ?????????????????????????????? layer ???????????????????????????, Maintainability ????????????????????? implementation ???????????????????????? API, Reusability ????????? XRD ????????????????????? compositions ?????????
??????????????????????????? Composition ????????? Clean Architecture
???????????????????????????????????? repository
# === Clean Architecture Repository Structure ===
cat > repo-structure.txt << 'EOF'
crossplane-platform/
# Layer 1: API Definitions (XRDs)
apis/
database/
v1alpha1/
definition.yaml # XRD: XDatabase
README.md
cache/
v1alpha1/
definition.yaml # XRD: XCache
messaging/
v1alpha1/
definition.yaml # XRD: XMessaging
network/
v1alpha1/
definition.yaml # XRD: XNetwork
# Layer 2: Compositions (Implementation per provider)
compositions/
aws/
database/
composition.yaml # AWS RDS implementation
patches.yaml
cache/
composition.yaml # AWS ElastiCache
messaging/
composition.yaml # AWS SNS + SQS
gcp/
database/
composition.yaml # GCP Cloud SQL
cache/
composition.yaml # GCP Memorystore
messaging/
composition.yaml # GCP Pub/Sub
azure/
database/
composition.yaml # Azure Database
# Layer 3: Configuration
config/
providers/
aws.yaml
gcp.yaml
provider-configs/
aws-default.yaml
gcp-default.yaml
# Layer 4: Claims (Consumer examples)
examples/
dev/
database.yaml
cache.yaml
staging/
database.yaml
production/
database.yaml
# Testing
tests/
unit/
test_xrds.py
test_compositions.py
integration/
test_aws_database.py
e2e/
test_full_stack.py
# CI/CD
.github/
workflows/
validate.yml
deploy.yml
EOF
# Create directory structure
mkdir -p apis/{database,cache,messaging,network}/v1alpha1
mkdir -p compositions/{aws,gcp,azure}/{database,cache,messaging}
mkdir -p config/{providers,provider-configs}
mkdir -p examples/{dev,staging,production}
mkdir -p tests/{unit,integration,e2e}
echo "Clean Architecture structure created"
??????????????? Layered Compositions
Implementation ????????? Clean Architecture layers
# === Layer 1: XRD (API Contract) ===
# apis/database/v1alpha1/definition.yaml
cat > apis/database/v1alpha1/definition.yaml << 'EOF'
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
name: xdatabases.platform.example.io
spec:
group: platform.example.io
names:
kind: XDatabase
plural: xdatabases
claimNames:
kind: Database
plural: databases
versions:
- name: v1alpha1
served: true
referenceable: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
# Clean API: no cloud-specific params
engine:
type: string
enum: ["postgres", "mysql", "mariadb"]
version:
type: string
size:
type: string
enum: ["small", "medium", "large", "xlarge"]
description: "T-shirt size abstraction"
highAvailability:
type: boolean
default: false
backup:
type: object
properties:
enabled:
type: boolean
default: true
retentionDays:
type: integer
default: 7
environment:
type: string
enum: ["dev", "staging", "production"]
required:
- engine
- size
- environment
status:
type: object
properties:
endpoint:
type: string
port:
type: integer
status:
type: string
EOF
# === Layer 2: Composition (AWS Implementation) ===
# compositions/aws/database/composition.yaml
cat > compositions/aws/database/composition.yaml << 'EOF'
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
name: xdatabase-aws
labels:
provider: aws
crossplane.io/xrd: xdatabases.platform.example.io
spec:
compositeTypeRef:
apiVersion: platform.example.io/v1alpha1
kind: XDatabase
patchSets:
- name: common-tags
patches:
- type: FromCompositeFieldPath
fromFieldPath: spec.environment
toFieldPath: spec.forProvider.tags.environment
- type: FromCompositeFieldPath
fromFieldPath: metadata.name
toFieldPath: spec.forProvider.tags.managed-by
transforms:
- type: string
string:
fmt: "crossplane-%s"
- name: size-mapping
patches:
- type: FromCompositeFieldPath
fromFieldPath: spec.size
toFieldPath: spec.forProvider.instanceClass
transforms:
- type: map
map:
small: db.t3.micro
medium: db.t3.medium
large: db.r6g.large
xlarge: db.r6g.xlarge
resources:
- name: rds-instance
base:
apiVersion: rds.aws.upbound.io/v1beta1
kind: Instance
spec:
forProvider:
region: ap-southeast-1
allocatedStorage: 20
autoMinorVersionUpgrade: true
publiclyAccessible: false
skipFinalSnapshot: false
patches:
- type: PatchSet
patchSetName: common-tags
- type: PatchSet
patchSetName: size-mapping
- fromFieldPath: spec.engine
toFieldPath: spec.forProvider.engine
- fromFieldPath: spec.version
toFieldPath: spec.forProvider.engineVersion
- fromFieldPath: spec.highAvailability
toFieldPath: spec.forProvider.multiAz
- fromFieldPath: spec.backup.retentionDays
toFieldPath: spec.forProvider.backupRetentionPeriod
- type: ToCompositeFieldPath
fromFieldPath: status.atProvider.endpoint
toFieldPath: status.endpoint
- type: ToCompositeFieldPath
fromFieldPath: status.atProvider.port
toFieldPath: status.port
EOF
echo "Layered compositions created"
Testing ????????? Validation
??????????????? Crossplane Compositions
#!/usr/bin/env python3
# tests/unit/test_compositions.py ??? Composition Tests
import json
import logging
import subprocess
from typing import Dict, List
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("test")
class CompositionTester:
"""Test Crossplane Compositions"""
def __init__(self):
self.results = []
def validate_xrd(self, xrd_path):
"""Validate XRD schema"""
checks = {
"has_claim_names": False,
"has_versions": False,
"has_required_fields": False,
"has_status_fields": False,
}
# Simulate reading YAML
# In production: use pyyaml to parse
checks["has_claim_names"] = True # Check claimNames exists
checks["has_versions"] = True # Check versions array
checks["has_required_fields"] = True # Check required spec fields
checks["has_status_fields"] = True # Check status subresource
passed = all(checks.values())
self.results.append({
"test": f"validate_xrd:{xrd_path}",
"passed": passed,
"checks": checks,
})
return passed
def validate_composition(self, comp_path):
"""Validate Composition patches and resources"""
checks = {
"has_composite_type_ref": True,
"has_resources": True,
"patches_valid": True,
"no_hardcoded_values": True,
"has_status_patches": True,
}
passed = all(checks.values())
self.results.append({
"test": f"validate_composition:{comp_path}",
"passed": passed,
"checks": checks,
})
return passed
def test_size_mapping(self):
"""Test T-shirt size to instance class mapping"""
mappings = {
"small": "db.t3.micro",
"medium": "db.t3.medium",
"large": "db.r6g.large",
"xlarge": "db.r6g.xlarge",
}
for size, expected_class in mappings.items():
# Simulate patch transform
result = mappings.get(size)
passed = result == expected_class
self.results.append({
"test": f"size_mapping:{size}",
"passed": passed,
"expected": expected_class,
"got": result,
})
return all(r["passed"] for r in self.results if "size_mapping" in r["test"])
def test_environment_isolation(self):
"""Test that environments are properly isolated"""
envs = ["dev", "staging", "production"]
checks = []
for env in envs:
check = {
"env": env,
"has_network_isolation": env in ["staging", "production"],
"has_encryption": env == "production",
"has_backup": env in ["staging", "production"],
"ha_enabled": env == "production",
}
checks.append(check)
self.results.append({
"test": "environment_isolation",
"passed": True,
"environments": checks,
})
return True
def summary(self):
total = len(self.results)
passed = sum(1 for r in self.results if r["passed"])
failed = total - passed
return {
"total": total,
"passed": passed,
"failed": failed,
"pass_rate": round(passed / total * 100, 1) if total > 0 else 0,
}
tester = CompositionTester()
tester.validate_xrd("apis/database/v1alpha1/definition.yaml")
tester.validate_composition("compositions/aws/database/composition.yaml")
tester.test_size_mapping()
tester.test_environment_isolation()
summary = tester.summary()
print(f"Test Results: {summary['passed']}/{summary['total']} passed ({summary['pass_rate']}%)")
for r in tester.results:
status = "PASS" if r["passed"] else "FAIL"
print(f" [{status}] {r['test']}")
GitOps ????????? Environment Management
?????????????????? environments ???????????? GitOps
# === GitOps Environment Management ===
# 1. Kustomize Overlays per Environment
cat > kustomization-base.yaml << 'EOF'
# base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../apis/database/v1alpha1/definition.yaml
- ../apis/cache/v1alpha1/definition.yaml
- ../apis/messaging/v1alpha1/definition.yaml
EOF
cat > kustomization-aws.yaml << 'EOF'
# overlays/aws/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
- ../../compositions/aws/database/composition.yaml
- ../../compositions/aws/cache/composition.yaml
- ../../compositions/aws/messaging/composition.yaml
- ../../config/providers/aws.yaml
- ../../config/provider-configs/aws-default.yaml
EOF
# 2. ArgoCD ApplicationSet (multi-cluster)
cat > appset-crossplane.yaml << 'EOF'
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: crossplane-platform
namespace: argocd
spec:
generators:
- list:
elements:
- cluster: management
url: https://mgmt.k8s.example.com
provider: aws
env: production
- cluster: dev
url: https://dev.k8s.example.com
provider: aws
env: dev
template:
metadata:
name: "crossplane-{{cluster}}"
spec:
project: infrastructure
source:
repoURL: https://github.com/org/crossplane-platform.git
targetRevision: main
path: "overlays/{{provider}}"
destination:
server: "{{url}}"
syncPolicy:
automated:
prune: true
selfHeal: true
EOF
# 3. GitHub Actions CI/CD
cat > .github/workflows/validate.yml << 'EOF'
name: Validate Crossplane
on:
pull_request:
paths: ['apis/**', 'compositions/**']
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Validate YAML
run: |
pip install pyyaml
python -c "
import yaml, glob, sys
errors = 0
for f in glob.glob('**/*.yaml', recursive=True):
try:
with open(f) as fh:
list(yaml.safe_load_all(fh))
except Exception as e:
print(f'ERROR: {f}: {e}')
errors += 1
print(f'Validated {len(glob.glob(\"**/*.yaml\", recursive=True))} files, {errors} errors')
sys.exit(1 if errors > 0 else 0)
"
- name: Run Tests
run: |
pip install pytest pyyaml
pytest tests/ -v
- name: Kustomize Build Test
run: |
kustomize build overlays/aws/ > /dev/null
echo "AWS overlay: OK"
kustomize build overlays/gcp/ > /dev/null
echo "GCP overlay: OK"
EOF
echo "GitOps configured"
Best Practices ????????? Anti-Patterns
????????????????????????????????????????????????????????????????????????????????????????????????????????????
#!/usr/bin/env python3
# best_practices.py ??? Crossplane Clean Architecture Best Practices
import json
import logging
from typing import Dict, List
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("practices")
class BestPractices:
def __init__(self):
pass
def practices(self):
return {
"do": {
"abstract_cloud_specifics": {
"description": "XRD ???????????????????????? cloud-specific parameters",
"example": "????????? size: small/medium/large ????????? instanceClass: db.t3.micro",
"benefit": "????????????????????? cloud provider ???????????????????????? consumers",
},
"use_patchsets": {
"description": "????????? PatchSets ?????????????????? patches ???????????????????????????",
"benefit": "?????? duplication, maintain ????????????????????????",
},
"version_xrds": {
"description": "????????? versioning ?????????????????? XRDs (v1alpha1, v1beta1, v1)",
"benefit": "Breaking changes ???????????????????????? existing consumers",
},
"test_compositions": {
"description": "???????????????????????? composition ???????????? deploy",
"tools": ["crossplane beta render", "kuttl", "pytest"],
},
"use_composition_revisions": {
"description": "Enable composition revisions ?????????????????? safe updates",
"benefit": "Rollback ?????????????????? composition update ?????????????????????",
},
"separate_concerns": {
"description": "????????? XRD, Composition, Claims ?????? directories ?????????????????????",
"benefit": "Clear ownership, easy to navigate",
},
},
"dont": {
"expose_cloud_params_in_xrd": {
"description": "??????????????????????????? instanceClass, region, ARN ?????? XRD spec",
"reason": "????????? consumers ????????? cloud provider",
"fix": "????????? transforms (map) ?????? composition ?????????",
},
"monolithic_composition": {
"description": "????????????????????????????????????????????????????????? composition ???????????????",
"reason": "????????? maintain, ????????? test",
"fix": "????????? composition ????????? concern (network, database, cache)",
},
"skip_status_patches": {
"description": "??????????????????????????? ToCompositeFieldPath ?????????????????? status",
"reason": "Consumers ?????????????????? endpoint, connection details",
},
"hardcode_credentials": {
"description": "?????????????????? hardcode credentials ?????? compositions",
"fix": "????????? connectionDetails, writeConnectionSecretToRef",
},
},
}
bp = BestPractices()
practices = bp.practices()
print("Best Practices (DO):")
for name, info in practices["do"].items():
print(f" {name}: {info['description']}")
print("\nAnti-Patterns (DON'T):")
for name, info in practices["dont"].items():
print(f" {name}: {info['description']}")
print(f" Fix: {info.get('fix', info.get('reason', ''))}")
FAQ ??????????????????????????????????????????
Q: Clean Architecture ???????????????????????????????????? Crossplane ??????????
A: ????????????????????????????????????????????? project ??????????????? ?????????????????????????????????????????????????????????????????? ?????? 2+ cloud providers ??????????????????????????? compositions ????????????????????????????????? provider, ?????? 5+ compositions ????????????????????? maintain, ?????????????????? teams ????????? platform (platform team vs application teams), ????????????????????? portability ???????????? cloud ????????? ?????????????????? project ???????????? (1 provider, 2-3 compositions) ??????????????????????????????????????????????????? ????????????????????? ???????????????????????????????????????????????????????????? refactor ???????????? Clean Architecture ??????????????? complexity ???????????????????????????
Q: T-shirt sizing (small/medium/large) ?????????????????? exact specs ??????????????????????
A: ????????????????????? audience ????????? consumers ???????????? application developers ??????????????????????????? infrastructure details ????????? T-shirt sizing ??????????????????????????? ??????????????????????????????????????? db.t3.micro ????????????????????? ???????????????????????? small ????????? consumers ???????????? infra engineers ?????????????????????????????? control ????????????????????? ????????? option ????????? exact specs ????????? (override) ?????????????????? ????????? T-shirt size ???????????? default ??????????????? optional override fields ?????????????????? power users ???????????? size: medium (default) + optional instanceClassOverride: db.r6g.xlarge
Q: Composition Functions ????????????????????? ????????????????????????????
A: Composition Functions ?????????????????????????????????????????????????????? Crossplane ????????????????????????????????? logic ???????????? code (Go, Python, KCL) ????????? patches ??????????????? Logic ?????????????????????????????? patches ???????????????????????? (conditional resources, loops), ????????? programming language ??????????????????????????????, Unit test ???????????????????????? patches ????????????????????? ??????????????? complexity ???????????? maintain code + container image, Debugging ????????????????????? patches, ???????????? build ????????? deploy function image ??????????????? ????????? patches ?????????????????? simple mappings, ????????? Functions ?????????????????? complex logic (conditional provisioning, dynamic resource count) ???????????????????????? patches ???????????? ????????????????????????????????????????????????????????????????????? Functions
Q: ?????????????????? secrets ????????? connection details ??????????????????????
A: Crossplane ?????? built-in mechanism ?????????????????? secrets ?????? Composition ????????? connectionDetails block ??????????????? fields ????????????????????????????????? consumer (endpoint, username, password), ?????? Claim ????????? writeConnectionSecretToRef ??????????????? Secret name ???????????????????????????????????? namespace ????????? consumer Crossplane ??????????????? Kubernetes Secret ??????????????????????????? application ???????????? Secret ?????????????????? ?????????????????? Clean Architecture ????????? abstract connection details ????????????????????? standard format ????????? provider ???????????? endpoint, port, username, password, database ???????????????????????????????????? AWS RDS ???????????? GCP Cloud SQL consumer ??????????????? Secret format ???????????????????????? ????????????????????? External Secrets Operator ?????????????????? sync ????????? Vault/AWS Secrets Manager ?????????
