# Design: Safe Core Cluster-Query Migration in Backup/Restore Models

**Date:** 2026-06-04  
**Repository:** `dataflow-service`  
**Scope:** `app/models/backup`, `app/models/restore`

## 1. Background

`app/models/backup` and `app/models/restore` still contain `CoreGrpcClient` dependencies.  
The target is to reduce regional core-svc dependency by moving **cluster-query** callsites to
`MgmtSvcGrpcClient`, while preserving behavior and avoiding breaking changes.

This design follows a strict compatibility boundary:
- Migrate only query paths with clear mgmt equivalents.
- Keep core-only capabilities unchanged (snapshot/cloud-region/cidr/legacy pagination semantics).

## 2. Non-Breaking Migration Boundary

### 2.1 In Scope (migrate now)

| File | Current call | New call | Why safe |
|---|---|---|---|
| `app/models/restore/impl/restore_base.go` | `coreCli.GetAnyClusterByID` | `mgmtSvcCli.GetAnyDedicatedClusterBasicByID` | Used for backup-cluster metadata during restore param build |
| `app/models/restore/impl/pitr_restore.go` | `coreCli.GetAnyClusterByID` | `mgmtSvcCli.GetAnyDedicatedClusterBasicByID` | Only reads cluster version |
| `app/models/restore/impl/restore_router.go` | `coreCli.GetAnyClusterByID` | `mgmtSvcCli.GetAnyDedicatedClusterBasicByID` | Only reads cloud provider for CIDR check |
| `app/models/backup/impl/compact_backup_model.go` | `coreGrpcClient.GetAnyClusterByID` | `mgmtSvcCli.GetAnyDedicatedClusterBasicByID` | Only validates tenant/org ownership |

### 2.2 Out of Scope (keep core for now)

These depend on core-only APIs or non-trivial semantics and are intentionally unchanged:
- Snapshot APIs: `CreateClusterSnapshot`, `GetClusterSnapshot`, `ListClusterSnapshot`
- Region/CIDR APIs: `ListCloudRegions`, `GetProjectRegionCIDR`
- Legacy list/count/status semantics: `ListClustersWithoutIds`, `CountClusters`, `OrderedClusterIds`, `IsDevTier`, `CLEARED`

## 3. Design Changes

### 3.1 `pitr_restore.go`

- Keep both clients on `PiTRestore` (`coreCli`, `mgmtSvcCli`) to avoid unrelated constructor churn.
- In `restoreClusterVersionBuilder`, switch cluster lookup to:
  - `mgmtSvcCli.GetAnyDedicatedClusterBasicByID(ctx, req.createRestoreReq.ClusterID)`
- Field mapping:
  - `core.Cluster.Version` -> `mgmt.Cluster.GetDedicatedCluster().GetVersion()`
- Error handling remains unchanged (same logger + `NewInternalErr` path).

### 3.2 `restore_base.go`

- Keep `coreCli` for `GetClusterSnapshot` (core-only API).
- Replace `backupCluster` lookup in `restoreClusterParamBuilder`:
  - `coreCli.GetAnyClusterByID(...)` -> `mgmtSvcCli.GetAnyDedicatedClusterBasicByID(...)`
- Field mapping used by create-cluster request build:
  - `core.Cluster.TenantId` -> `mgmt.Cluster.GetOrganizationId()`
  - `core.Cluster.CloudProviderName` -> `mgmt.Cluster.GetCloudProvider()`
  - `core.Cluster.RegionName` -> `mgmt.Cluster.GetRegion()`
- Keep 3-layer-architecture gate semantics unchanged in this step.

### 3.3 `restore_router.go`

- Add `mgmtSvcCli grpcli.MgmtSvcGrpcClient` dependency to constructor and internal state.
- Keep `coreCli` because `GetProjectRegionCIDR` remains core-only.
- In `validate()`, switch cluster lookup from core to mgmt:
  - `mgmtSvcCli.GetAnyDedicatedClusterBasicByID(ctx, clusterID)`
- Field mapping for CIDR call:
  - `string(coreCluster.CloudProviderName)` -> `mgmtCluster.GetCloudProvider()`

### 3.4 `compact_backup_model.go`

- Add `mgmtSvcCli grpcli.MgmtSvcGrpcClient` dependency in model struct/constructor.
- Replace `GetAnyClusterByID` lookup in `preCheck()` with:
  - `mgmtSvcCli.GetAnyDedicatedClusterBasicByID(ctx, req.ClusterID)`
- Field mapping:
  - `coreCluster.GetTenantId()` -> `mgmtCluster.GetOrganizationId()`
- Keep existing validation semantics and error messages.

### 3.5 Container and Tests

- Update DI wiring in `app/container/container.go` for modified constructors.
- Update affected unit tests to mock `MgmtSvcGrpcClient` for migrated callsites.
- Do not edit generated mocks manually; regenerate when interface signatures change.

## 4. Current Dependency Status for Requested Chains

### 4.1 Restore chain

- Migrated by this design: cluster lookup callsites in `restore_base.go`, `pitr_restore.go`, `restore_router.go`.
- Still core (intentional): `GetClusterSnapshot`, `GetProjectRegionCIDR`.

### 4.2 Backup list/get chain

- `snapshot_backup.go` `Query/List` does not call cluster lookup directly.
- Still core (intentional): `ListCloudRegions` in list response conversion, and recycle-bin cluster listing paths using `CountClusters/ListClusters`.

### 4.3 BackupSetting query chain

- Cluster lookup already mgmt in `QueryBackupSettingForAPI` / `QueryBackupRegionOptions`.
- Still core (intentional): `queryCloudRegions()` uses `coreCli.ListCloudRegions`.

### 4.4 Compact chain

- In-scope for this model migration:
  - `compact_backup_model.go` cluster lookup is migrated to mgmt.
- Still core (intentional, out of this spec scope):
  - `compact_backup_scheduler.go` keeps `GetAnyClusterByID` / `GetAvailableClusterByID`.
  - Workflow compact actions under `app/workflow/action/backup/compact/` still use `coreGrpcClient.GetAnyClusterByID`.

## 5. Compatibility Rules

1. No gRPC/OpenAPI contract changes.
2. No behavior changes in business branching or error code selection.
3. Only replace data source and field access for the three in-scope callsites.
4. Preserve existing core dependencies for out-of-scope capabilities.

## 6. Verification Plan

1. Targeted tests for impacted implementations (`backup/impl`, `restore/impl`).
2. Full local regression:
   - `make app-unit-test-local`
3. Regenerate mocks only if constructor/interface updates require it:
   - `make generate4test`

## 7. Risk Assessment

- **Risk level:** Medium-Low
- **Main risk:** subtle field-semantic mismatch between core and mgmt (`tenant` vs `organization`, version/cloud-provider extraction).
- **Mitigation:** scoped migration (3 callsites only), explicit field mapping, and full unit-test regression.
