Skip to main content

Audience Database Consolidation — Technical Documentation

Last updated: March 2026 Track: A (Database Consolidation) Status: Planned


Table of Contents

  1. Overview
  2. Current Architecture
  3. Target Architecture
  4. Repository Map
  5. Field Mapping
  6. Phase A1 — Make AudienceHeader the Authoritative Source
  7. Phase A2 — Redirect Sodastream Writes Through Data API
  8. Phase A3 — Decommission SoProQueueAudience
  9. Verification
  10. Risks & Mitigations

1. Overview

Audience management currently operates across a dual-database architecture: audience definitions live in both SoProQueueAudience (SoProQueue DB, owned by Sodastream) and AudienceHeader (Audience DB, owned by sopromasterdata). A one-way daily sync (SoproSyncService, 4 AM UTC) copies 16 fields from SoProQueueAudience → AudienceHeader, creating up to 24 hours of data staleness.

This document describes the three-phase plan to consolidate to a single source of truth (AudienceHeader), then decommission SoProQueueAudience entirely.

This track is independent of the UI migration (Track B). No frontend work is required.


2. Current Architecture

Problems

ProblemImpact
Dual source of truthSoProQueueAudience and AudienceHeader can diverge
One-way sync onlySettings changes in Sodastream take up to 24 hours to reach Data API
Stale dataImport consumer reads from SoProQueueAudience; Data API may show different values
Tight couplingImport process depends on direct SoProQueue DB access for config
Redundant storageAll 16 synced fields exist in both databases

3. Target Architecture

After consolidation:

  • AudienceHeader is the only audience definition store
  • SoProQueueAudience table is dropped
  • SoproSyncService audience sync is removed (campaign/client sync remains)
  • Import consumer reads config from Data API instead of SoProQueue DB

4. Repository Map

RepositoryLayerKey FilesChanges
sopromasterdataAdmin ControllerAPI/SoProMasterDBAPI/Controllers/Admin/AudienceController.csAdd UpdateAudienceSettings, GetAudienceConfig endpoints
sopromasterdataServiceAudienceServices/AudienceHeaderService.csAdd UpdateSettingsAsync()
sopromasterdataService InterfaceAudienceServices/Interfaces/IAudienceHeaderService.csAdd UpdateSettingsAsync() signature
sopromasterdataSodaStream ServiceSodaStreamServices/SodaStreamService.csAdd UpdateSoProQueueAudienceAsync() (A1), remove it (A3)
sopromasterdataSync ServiceAudienceServices/SoproSyncService.csAdd delta metrics (A1), remove audience sync (A3)
sopromasterdataDTOShared/SoProMasterDBAdapter/Models/Requests/UpdateAudienceSettingsRequest.csNew file (A1)
sopromasterdataDTOShared/SoProMasterDBAdapter/Models/Responses/AudienceConfigResponse.csNew file (A3)
sopro-sodastreamControllerWeb/Web/Controllers/SoProMasterSearchController.csRoute settings writes through Data API (A2)
sopro-sodastreamEntitySoProQueueEntities/SoProQueueAudience.csDelete (A3)
sopro-sodastream-coreConsumersopro-sodastream-core/SoProQueueConsumer/Jobs/Audience/TaskAudienceBuilderAutomationsDaily.csSwitch config source to Data API (A3)
sopro-sodastream-coreEntitySoProQueue/Data/SoProQueueEntities/SoProQueueAudience.csDelete (A3)

5. Field Mapping

The 16 fields currently synced by SoproSyncService.BuildFieldsToUpdate():

#SoProQueueAudience FieldAudienceHeader FieldTypeNotes
1DailyImportVolumeContactsPerDayintDaily import target
2ProspectsPerCompanyPerDayContactsPerCompanyPerDayintPer-company cap
3IsAutomaticImportIsEmailFindingActiveboolAuto email-finding toggle
4IsProspectVisisbleIsCampaignAudienceActiveboolCampaign visibility flag
5IsAudienceActiveIsAudienceEnabledboolMaster active toggle
6IsAdSyncIsAdSyncboolAd sync flag
7IsAdSyncActiveIsAdSyncActiveboolAd sync active flag
8EmailProfileIdsEmailProfileIdsstringComma-separated IDs
9EmailProfileNamesEmailProfileNamesstringDisplay names
10DeletedEmailProfileIdsDeletedEmailProfileIdsstringRemoved profile IDs
11AudienceNameNamestringDisplay name
12AudienceDuplicateProspectDaysAudienceDuplicateProspectDaysint?Dedup window in days
13OptionOptionint?Audience option flag
14CampaignIdCampaignIdintLinked campaign
15ApproachIdApproachIdint?Extracted from Parameters JSON
16Parameters (JSON)N/AstringContains Messaging/Approach config

Source: sopromasterdata/AudienceServices/SoproSyncService.csBuildFieldsToUpdate() method


6. Phase A1 — Make AudienceHeader the Authoritative Source

Goal: All writes go through Data API first. Reverse sync keeps SoProQueueAudience updated immediately for backward compatibility.

6.1 New API Endpoint

PUT /admin/Audience/UpdateAudienceSettings

Request DTO: UpdateAudienceSettingsRequest

FieldTypeDescription
AudienceIdGuidTarget audience
ContactsPerDayint?Daily import volume
ContactsPerCompanyPerDayint?Per-company cap
IsEmailFindingActivebool?Auto email-finding
IsCampaignAudienceActivebool?Campaign visibility
IsAudienceEnabledbool?Master active toggle
IsAdSyncbool?Ad sync flag
IsAdSyncActivebool?Ad sync active
EmailProfileIdsstring?Email profile IDs
EmailProfileNamesstring?Profile display names
DeletedEmailProfileIdsstring?Removed profiles
Namestring?Audience name
AudienceDuplicateProspectDaysint?Dedup window
Optionint?Audience option
ApproachIdint?Approach ID

Nullable fields — only provided fields are updated (PATCH semantics).

6.2 Service Flow

6.3 Reverse Sync Implementation

New method in SodaStreamService:

Task UpdateSoProQueueAudienceAsync(Guid audienceId, UpdateAudienceSettingsRequest request);

Maps AudienceHeader field names back to SoProQueueAudience field names (inverse of the 16-field mapping), then updates the SoProQueue DB record directly.

6.4 Forward Sync Safety Net

Keep SoproSyncService.SyncAudiencesFromSodastreamAsync() running daily. Add logging to track how many fields differ — should trend to zero as all writes go through Data API.

6.5 File Modifications

FileAction
sopromasterdata/.../Admin/AudienceController.csAdd UpdateAudienceSettings action
sopromasterdata/AudienceServices/AudienceHeaderService.csAdd UpdateSettingsAsync()
sopromasterdata/AudienceServices/Interfaces/IAudienceHeaderService.csAdd interface method
sopromasterdata/SodaStreamServices/SodaStreamService.csAdd UpdateSoProQueueAudienceAsync()
sopromasterdata/SodaStreamServices/Interfaces/ISodaStreamService.csAdd interface method
sopromasterdata/Shared/.../UpdateAudienceSettingsRequest.csNew file — request DTO

7. Phase A2 — Redirect Sodastream Writes Through Data API

Goal: Sodastream settings edits call Data API first. Data API handles both AudienceHeader update and reverse sync to SoProQueueAudience. Sodastream reads unchanged.

7.1 Sodastream Controller Changes

7.2 Delta Monitoring

Add metric tracking to SoproSyncService:

Logger.LogInformation("🔍 Audience sync delta: {AudienceId} has {DeltaCount} field differences", audienceId, deltaCount);

When DeltaCount is consistently 0 for all audiences, Phase A2 is validated — all writes are flowing through Data API.

7.3 File Modifications

FileAction
sopro-sodastream/.../SoProMasterSearchController.csRoute settings writes through Data API
sopromasterdata/AudienceServices/SoproSyncService.csAdd delta count logging/metrics

8. Phase A3 — Decommission SoProQueueAudience

Goal: Remove SoProQueueAudience entirely. All consumers read from AudienceHeader via Data API.

8.1 New Config Endpoint

GET /api/Audience/GetAudienceConfig/{audienceId}

Response DTO: AudienceConfigResponse

FieldTypeDescription
AudienceIdGuidAudience identifier
DailyImportVolumeintMapped from ContactsPerDay
ProspectsPerCompanyPerDayintMapped from ContactsPerCompanyPerDay
IsAutomaticImportboolMapped from IsEmailFindingActive
EmailProfileIdsstringComma-separated profile IDs
AudienceDuplicateProspectDaysint?Dedup window
ApproachIdint?Approach ID
IsActiveboolCombined from IsAudienceEnabled + IsCampaignAudienceActive

Design note: This DTO uses SoProQueueAudience-compatible field names to minimize changes in TaskAudienceBuilderAutomationsDaily.

8.2 Import Consumer Switch

Feature flag: UseDataApiForAudienceConfig in TaskAudienceBuilderAutomationsDaily. When true, reads from Data API. When false, reads from SoProQueueAudience (existing behavior). Allows instant rollback.

8.3 Removal Checklist

StepFileAction
1sopro-sodastream-core/.../TaskAudienceBuilderAutomationsDaily.csReplace SoProQueueAudience reads with Data API calls
2sopromasterdata/SodaStreamServices/SodaStreamService.csRemove UpdateSoProQueueAudienceAsync(), CreateSoProQueueAudienceAsync()
3sopromasterdata/SodaStreamServices/Interfaces/ISodaStreamService.csRemove interface methods
4sopromasterdata/AudienceServices/AudienceHeaderService.csRemove reverse sync calls from UpdateSettingsAsync() and CreateFromFilterDataAsync()
5sopromasterdata/AudienceServices/SoproSyncService.csRemove SyncAudiencesFromSodastreamAsync() (keep campaign/client sync)
6sopro-sodastream/SoProQueueEntities/SoProQueueAudience.csDelete entity file
7sopro-sodastream-core/.../SoProQueueEntities/SoProQueueAudience.csDelete entity file
8Both reposRemove DbSet references, update DbContext
9SoProQueue DBDrop SoProQueueAudience table (after verification period)

8.4 Grep Verification

After all code changes, run across all repos:

grep -r "SoProQueueAudience" --include="*.cs" .

Expected: zero results (excluding migration history files).


9. Verification

Phase A1 Verification

  1. Create audience via POST /admin/Audience/CreateAudienceFromSearch
    • Verify both AudienceHeader and SoProQueueAudience records created
  2. Update settings via PUT /admin/Audience/UpdateAudienceSettings
    • Verify AudienceHeader updated
    • Verify SoProQueueAudience updated immediately (reverse sync)
  3. Run import via TaskAudienceBuilderAutomationsDaily
    • Verify import uses correct settings from SoProQueueAudience

Phase A2 Verification

  1. Edit settings in Sodastream UI
    • Verify Data API UpdateAudienceSettings called (HTTP logs)
    • Verify AudienceHeader updated
    • Verify SoProQueueAudience reverse-synced
  2. Run SoproSyncService manually
    • Verify zero field deltas for all audiences

Phase A3 Verification

  1. Enable UseDataApiForAudienceConfig feature flag
  2. Run import via TaskAudienceBuilderAutomationsDaily
    • Verify config read from Data API (HTTP logs)
    • Verify import produces identical results
  3. Grep all repos for SoProQueueAudience — zero references
  4. Monitor imports for 1 week before table drop

10. Risks & Mitigations

RiskImpactMitigation
Concurrent edits during A2 (both UIs editing)Last-write-wins, potential data lossAdd LastModified timestamp to AudienceHeader for conflict detection
A3 import switch breaks productionImport pipeline fails, no new prospectsFeature-flag UseDataApiForAudienceConfig for instant rollback
Data API downtime during A3Import consumer can't read configAdd retry policy with exponential backoff; consider local config cache
Incomplete field mappingImport uses wrong settingsValidate all 16 fields map correctly in A1; integration test the config endpoint
SoproSyncService removal breaks other syncCampaign/client sync lostOnly remove audience sync; campaign and client sync remain untouched

Integration Points

SystemIntegrationPhase
TaskAudienceBuilderAutomationsDailyConfig source switchA3
SoProMasterSearchControllerWrite redirectA2
SoproSyncServiceDelta monitoring → removalA1–A3
AudienceHeaderService.CreateFromFilterDataAsyncAlready creates both recordsExisting
QueueAudienceImportAsyncRead source switchA3