Context
For separate-bucket multitenancy, the OSS module currently relies on the @cap-js/attachments MTX sidecar plugin to handle tenant onboarding/offboarding (creating/deleting per-tenant object store instances and bindings via the Service Manager API). The Java app only has a read-only SM client for runtime credential resolution (getBinding()).
This works but couples us to the JS plugin for a critical lifecycle operation, and we lose control over:
- Bucket content cleanup on unsubscribe (JS sidecar only deletes the SM instance/binding, doesn't empty the bucket first)
- Cache warming on subscribe (first request after provisioning hits a cold cache)
- Error resilience (orphaned instance cleanup on binding failure, resilient unsubscribe that continues after partial failures)
Goal
Implement a full Service Manager client in the Java OSS module that handles the complete tenant lifecycle independently, without requiring @cap-js/attachments in the MTX sidecar.
Scope
SM Client (write operations)
createInstance(tenantId, planId) — create object store instance with polling for async completion
createBinding(tenantId, instanceId) — create service binding
deleteBinding(bindingId) — delete service binding
deleteInstance(instanceId) — delete instance with async polling
getOfferingId() / getPlanId(offeringId) — discover objectstore offering and plan (standard / s3-standard)
Lifecycle Orchestration
- Subscribe handler (
DeploymentService.EVENT_SUBSCRIBE): idempotency check → create instance → create binding → warm client cache. Clean up orphaned instance if binding fails.
- Unsubscribe handler (
DeploymentService.EVENT_UNSUBSCRIBE): delete bucket contents → evict cache → delete binding → delete instance. Errors logged but don't block flow.
Token Provider
- OAuth2 client credentials flow + mTLS support
- In-memory token caching with refresh margin
Registration
- Wire SM client, lifecycle handler, and subscribe/unsubscribe event handlers in
Registration.registerSeparateMode()
- Service Manager binding discovery from
CdsEnvironment
Notes
- The existing read-only
getBinding() in ServiceManagerClient can be extended rather than rewritten
- Both
standard and s3-standard plans should be supported (try standard first, fall back to s3-standard)
- Labels:
tenant_id: [tenantId], service: ["OBJECT_STORE"]
- Reference implementation:
cap-js/attachments lib/mtx/server.js
Context
For separate-bucket multitenancy, the OSS module currently relies on the
@cap-js/attachmentsMTX sidecar plugin to handle tenant onboarding/offboarding (creating/deleting per-tenant object store instances and bindings via the Service Manager API). The Java app only has a read-only SM client for runtime credential resolution (getBinding()).This works but couples us to the JS plugin for a critical lifecycle operation, and we lose control over:
Goal
Implement a full Service Manager client in the Java OSS module that handles the complete tenant lifecycle independently, without requiring
@cap-js/attachmentsin the MTX sidecar.Scope
SM Client (write operations)
createInstance(tenantId, planId)— create object store instance with polling for async completioncreateBinding(tenantId, instanceId)— create service bindingdeleteBinding(bindingId)— delete service bindingdeleteInstance(instanceId)— delete instance with async pollinggetOfferingId()/getPlanId(offeringId)— discover objectstore offering and plan (standard / s3-standard)Lifecycle Orchestration
DeploymentService.EVENT_SUBSCRIBE): idempotency check → create instance → create binding → warm client cache. Clean up orphaned instance if binding fails.DeploymentService.EVENT_UNSUBSCRIBE): delete bucket contents → evict cache → delete binding → delete instance. Errors logged but don't block flow.Token Provider
Registration
Registration.registerSeparateMode()CdsEnvironmentNotes
getBinding()inServiceManagerClientcan be extended rather than rewrittenstandardands3-standardplans should be supported (try standard first, fall back to s3-standard)tenant_id: [tenantId],service: ["OBJECT_STORE"]cap-js/attachmentslib/mtx/server.js