Skip to main content
Version: Next

13. Host Integration

13.1 How the ERP (Shumoul.Saas.Api) integrates

Confirmed in Shumoul.Infrastructure\Extensions\ServiceCollectionExtensions.cs, inside AddInfrustracture(this IServiceCollection services, IConfiguration config):

using Shumoul.BackgroundJobs.Extensions;
using Shumoul.BackgroundJobs.Adapters.Extensions;
// ...
// Must be called AFTER AddHangfire() and AddMultiTenancy(), which are registered by
// AddMultiTenancy(config) via Shumoul.Framework.MultiTenancy.Api. All legacy Hangfire
// registrations and recurring jobs remain unchanged — this is additive only.
services.AddShumoulBackgroundJobsFramework();
services.AddBackgroundJobsAdapters();

Required .csproj references for any host consuming this framework:

<PackageReference Include="Shumoul.BackgroundJobs" Version="1.0.0" />
<PackageReference Include="Shumoul.BackgroundJobs.Adapters" Version="1.0.0" />

Core, Abstractions, and Contracts come in transitively — a host never references them directly.

Prerequisites the host must already have registered, before calling these two extension methods (verified against the DI test suite): IBackgroundJobClient, IRecurringJobManager, JobStorage (from Hangfire's own registration), IMultiTenantContextAccessor (from Finbuckle/MultiTenancy registration), and ILogger<T> (from AddLogging()).

13.2 How a future host integrates

The same two-line call (AddShumoulBackgroundJobsFramework() + AddBackgroundJobsAdapters()), the same two package references, and the same four prerequisites apply to any future Shumoul host. Nothing about the integration is ERP-specific — this is deliberate, since the framework is designed to be reusable by "a second host," per the platform's Framework First principle. As of this writing, exactly one host consumes the framework (Shumoul.Saas.Api); Shumoul.Saas.MultiTenancyApi is confirmed not a consumer. Reusability is architecturally proven (clean dependency law, a documented module blueprint) but has not yet been exercised by an actual second, independent host.

13.3 How MultiTenancy integrates

The framework depends on exactly one MultiTenancy type set: Shumoul.Framework.MultiTenancy's IMultiTenantContextAccessor, consumed entirely inside BackgroundJobTenantResolverAdapter (in the Adapters package). This is an AsyncLocal-backed accessor rather than an HTTP-request-scoped one, because an HTTP-scoped resolver "would be meaningless inside a Hangfire worker" — there is no HTTP request during a background job execution. See §8 — Tenant Context for the full flow.

BackgroundJobAdapterOptions.RootTenantIdentifier (default "000111") configures how the adapter recognizes the platform's root tenant — pass a custom value to AddBackgroundJobsAdapters(options => options .RootTenantIdentifier = "...") only if a host's root tenant identifier genuinely differs from the platform default.

13.4 DI registration order — why it matters

Registering the adapters (AddBackgroundJobsAdapters) before their prerequisites (AddHangfire, AddMultiTenancy, AddLogging) are registered does not fail at registration time — DI registration itself is order-independent — but will fail at resolution time (e.g. the first time a job actually runs) with a missing-service exception, since the adapter constructors require those types to already be resolvable. Always register prerequisites first.

13.5 Adding a new job — package references and files

Adding a new pipeline job to an existing host requires zero new package references (the two already present cover everything) and exactly three new ERP-side files — see §12.7 for a worked example and §14 — Job Migration Guide for the full repeatable recipe.

13.6 Package release and consumer update procedure

Framework repository release workflow (manual — no CI/CD pipeline exists today):

restore → build → test (200/200) → bump all 5 <Version> together → pack
→ copy .nupkg to C:\MultiTenancy\ AND local-packages\ (both mandatory)
→ commit → push (main, no PR/review gate today — single-maintainer repo)

Consumer (ERP) update procedure, every time the framework releases a new version:

bump PackageReference (both Shumoul.BackgroundJobs and .Adapters) together, same version
→ dotnet restore --configfile nuget.config --no-cache
→ build → test (targeted BackgroundJobsTests 121/121, full suite no NEW failures vs. the documented baseline)
→ commit → push (a separate commit from the framework repo's own release commit)

Forgetting git add local-packages/*.nupkg in the framework repo is called out explicitly as an easy first-time mistake — the build succeeds locally (from the developer's own NuGet cache) but fails for anyone else, since local-packages\ — not C:\MultiTenancy\ — is the actual CI/consumer restore source (via the LocalShumoul feed defined in nuget.config). C:\MultiTenancy\ is only the developer's local staging copy.

Release gate: a release is not complete unless the framework-repo suite passes 200/200, the consumer's targeted BackgroundJobsTests subset passes 121/121, and the full consumer suite shows no new failures against its documented baseline. See §16 — Testing § Release gate.

Rollback (no database change is ever involved — the framework owns no schema):

  1. Consumer-side (fastest): revert the PackageReference version bump, restore, rebuild, redeploy — the previous version's .nupkg is never deleted from either staging location.
  2. Per-job cutover rollback: restore that job's specific legacy RecurringJob.AddOrUpdate<IWorker>(...) call, removing only that job's pipeline registration pair — the legacy worker class is never deleted, so this is a same-day, low-risk operation.
  3. A framework-repo-side rollback (if a bad version must be un-published) uses git revert, never git reset --hard on a pushed commit, and never reuses a version number for a corrected re-release.