Skip to main content
Version: Latest

Architecture

SaaS DB vs. Tenant DB

Every starter-template family in this initiative follows the same shape: a template catalog lives in the central SaaS database (owned by Shumoul.MultiTenancyApi), and an explicit apply step copies selected rows into one tenant's own database (owned by Shumoul.BackEnd/Shumoul.Api). Shumoul.MultiTenancyApi never opens a tenant database connection itself.

The Preview → Apply pattern

Every template family exposes the same two-call shape:

  1. POST .../{id}/ApplyToTenant/Preview (or the flow's own equivalent path) — runs full resolution and validation, computes exactly what would be inserted/skipped/updated/blocked, and returns it. Never writes the tenant database. Never writes the central apply log.
  2. POST .../{id}/ApplyToTenant — same resolution, but commits. Writes the tenant database (insert-missing, fill-blank-only under Force=true) inside one transaction, then always writes one row to the family's own Tenant*ApplyLog table in the SaaS DB — whether the outcome was success, partial success, or failure.

The internal apply bridge

Every bridge in this initiative shares the same trust model: a matched pair of internal API keys, one per bridge purpose (never reused across purposes), checked by hand-written code — never the standard ASP.NET auth pipeline — against an endpoint marked [AllowAnonymous] and hidden from Swagger ([ApiExplorerSettings(IgnoreApi = true)]).

SideSettingPurpose
Caller (MultiTenancyApi Host)AccountingChartTemplateApplyBridgeSettingsEnabled, BackEndBaseUrl, InternalApiKey, one path property per apply flowBuilds the HTTP request, sends X-Shumoul-Internal-Key
Receiver (Shumoul.Api)InternalServiceAuthSettings.AccountingChartTemplateApplyApiKeyMust match the caller's InternalApiKey exactly

This one key/settings pair is reused, unchanged, by the accounting-accounts, accounting-mappings, inventory-account-category, cost-center, and (Phase 6/6A.1/6B) all three product-catalog apply flows — no new secret was introduced for any of them. A missing/wrong key returns 401/403 respectively; every request also carries an X-Correlation-Id that's threaded through to the central apply log.

This is architecturally distinct from the onboarding apply bridge, which uses its own separate InternalServiceAuthSettings.OnboardingApplyApiKey, and from the tenant entitlement provisioning manual-repair endpoint, which uses yet another separate key (TenantEntitlementProvisioningApiKey) — every internal bridge in the platform gets its own dedicated key, never a shared one.

Idempotency — no shared mechanism, each layer owns its own

There is no cross-cutting "idempotency service." Every apply flow independently:

  1. Matches an existing tenant row by a stable key (Account.Code, CostCenter.Code, ProductCategory.Name, Product.Name scoped to its category, UnitOfMeasure.UnitCode, …).
  2. Skips a match that already exists (never updates a structural field).
  3. Never resurrects a soft-deleted match — reports it as a failure instead.
  4. Reports what it did/would do as a list of per-item statuses (Inserted, SkippedExisting, WouldInsert, Failed, MissingDependency, …) — the exact vocabulary differs slightly per flow (see each flow's own chapter) but the shape is always the same.

The central Tenant*ApplyLog tables are for audit/troubleshooting visibility only — they are never consulted to decide whether to re-apply. Re-running Apply twice is always safe.