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):
- Consumer-side (fastest): revert the
PackageReferenceversion bump, restore, rebuild, redeploy — the previous version's.nupkgis never deleted from either staging location. - 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. - A framework-repo-side rollback (if a bad version must be un-published) uses
git revert, nevergit reset --hardon a pushed commit, and never reuses a version number for a corrected re-release.