CFODEV-1722 / CFODEV-1726: Add SignalR Redis Backplane + Worker background service#1144
Merged
Merged
Conversation
d4a31ec to
b93c085
Compare
d17a290 to
c7a9143
Compare
51d4fa2 to
9687083
Compare
…ne in their area logs in.
Excludes application user claims caching
c45615b to
8c92030
Compare
Carl Sixsmith (carlsixsmith-moj)
approved these changes
Jun 22, 2026
Carl Sixsmith (carlsixsmith-moj)
left a comment
Contributor
There was a problem hiding this comment.
lgtm
Carl Sixsmith (carlsixsmith-moj)
added a commit
that referenced
this pull request
Jun 25, 2026
…round service (#1144) * WIP: worker service * Remove Worker's dependency on Identity + move notify user to command * Configure appsettings * Add cats worker to infra * Publish worker container * This is a test branch that will allow users to be notified when someone in their area logs in. * Fix users * Add signlr backplain * Add k8s redis container * Update redis connection string name * Add Redis memory cache + supporting infrastructure Excludes application user claims caching * Conditionally register presence connector depending on SignalR config * Configure backplane in Aspire and cloud deployment * Fix SmartEnum cache serialization and isolate cache serializer options * Configure redis as ephemeral * Register in memory and redis user state container * Add redis container when required in Aspire * Add feature to relay user presence notifications * Relay notifications in CP * Remove delete job step from deploy and use image SHA's for migrator/seed * Deploy migrator/seed jobs as pods * Cleanup migrator/seed pod before next deploy * Remove obsolete worker extension * rename ICommand -> IExternalCommand as this now conflicts with the Mediator package * Add PresenceHub feature gating --------- Co-authored-by: Carl Sixsmith <carl.sixsmith1@justice.gov.uk>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🔗 Related Work
📌 Summary
Introduces the infrastructure needed to run CATS as multiple horizontally-scaled replicas behind a load balancer. Adds a Redis-backed distributed cache + SignalR backplane, a standalone Worker service that owns the Quartz scheduler, and cross-replica user-presence notifications. All of this sits behind three new feature flags that default to off, so existing single-replica deployments are completely unaffected.
🎯 Purpose / Motivation
CATS is a Blazor Server app that currently assumes a single process. Several things break (or silently misbehave) when you run more than one replica:
This PR makes CATS scale-out ready on Cloud Platform/Kubernetes while keeping the single-instance deployment path unchanged.
🧠 Approach
Everything new is opt-in via configuration. Each flag independently turns on one piece of scale-out behaviour, and when a flag is off the app falls back to the original in-process implementation. The flags are off in
appsettings.jsonand switched on only in the Kubernetes deployment (infra/cats-deployment.yml).1. New feature flags (
Features:*)UseSignalRBackplanefalseIConnectionMultiplexer, the Redis-backed distributed cache/backplane for FusionCache, andRedisUsersStateContainer.InMemoryUsersStateContainer+ in-process SignalR + in-memory FusionCache. No Redis dependency.UseWorkerForJobsfalseWorkerJobManagementService).QuartzJobManagementService), exactly as today.RelayUserPresenceNotificationsfalsePresenceConnectorsubscribes toUserOnlineand shows a snackbar to other users when someone in their area logs in.Because each flag is read independently with a safe default, an existing single-replica environment that sets none of them behaves exactly as before — no Redis, no Worker, no extra notifications.
2. Redis distributed cache + SignalR backplane
UseSignalRBackplaneis on:AddStackExchangeRediswith channel prefixCatsso hub messages fan out across replicas.RedisUsersStateContainerstores presence in a Redis hash (cats:presence:connections) and announces changes on a pub/sub channel (cats:presence:changed), so a login on replica A refreshes the Users grid and notifications on replica B.IConnectionMultiplexeris registered for presence pub/sub, reusing the sameredisconnection string the backplane validates.SmartEnumvalues; cache serializer options are now isolated (CacheJsonSerializerOptions) with dedicated converter tests.redis-servicein Kubernetes — it's a cache/transport, not a source of truth.3. Worker service (
src/Worker)AddWorkerInfrastructure(a slimmed-down infrastructure registration with no web/Rebus-consumer/full-Identity baggage).UseWorkerForJobsis on:GET /api/jobs,POST /api/jobs/{name}/trigger|pause|resumeGET /api/scheduler,POST /api/scheduler/standby|starthttps+http://cats-worker) orWorkerOptions:BaseUrlfor non-Aspire/Kubernetes (http://cats-worker-service:8080).🔄 Changes
src/Worker/*— standalone Quartz Worker service (Program, csproj, appsettings, launchSettings).WorkerJobManagementService+IJobManagementServiceremote implementation.RedisUsersStateContainer/InMemoryUsersStateContainersplit behindIUsersStateContainer.CacheJsonSerializerOptions+SmartEnumJsonConverterFactoryTests.UseWorkerForJobs,UseSignalRBackplane,RelayUserPresenceNotifications.redis-serviceandcats-workerdeployment.Server.UI/DependencyInjection.cs— conditional SignalR backplane / presence container registration.Infrastructure/DependencyInjection.cs— conditional in-process vs. remote Quartz; FusionCache L2/backplane.PresenceConnector.razor— flag-gated online notifications.infra/cats-deployment.yml, AspireAppHost— enable flags + provision Redis/Worker in Cloud.appsettings.json— newFeaturesentries (all defaultfalse).ConcurrentDictionarypresence API fromIUsersStateContainer.🧪 How to Test
A. Single replica / local (flags off — regression check)
Cats.AppHostwith all three flagsfalse(default).B. Scaled out (flags on)
UseSignalRBackplane,UseWorkerForJobs,RelayUserPresenceNotificationsand run multiple CATS replicas + Redis + the Worker (Aspire or Kubernetes).Expected result:
📸 Screenshots / Output (if applicable)
N/A
Details:
🙋 Notes for Reviewers
N/A