A centralized, secure, and real-time (event-driven) configuration management system. This project enables configuration changes in microservice architectures to be managed safely and in isolation without requiring service restarts (zero-downtime).
Starts with a single command:
docker compose up --build -d
- Message Broker: Low network overhead using RabbitMQ Direct Exchange.
- Environment Support: Environment-based (Dev, Staging, Prod) record management.
- Audit & Rollback: Complete history of all changes with one-click rollback.
- Docker Compose: Spin up the entire ecosystem (DB, MQ, API, Consumer) with a single command.
- Zero Downtime: Update configurations without restarting services.
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β π π π Admin Panel (SPA) π π π β
β http://localhost:5173 β
ββββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββ
β REST API
ββββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββ
β DistributedConfigHub.Api β
β ββββββββββββββ ββββββββββββββββ βββββββββββββββββββββββββββ β
β β Controllersβ β Action Filterβ β Exception Handlers β β
β β β β (ApiKey Auth)β β (Validation + Global) β β
β βββββββ¬βββββββ ββββββββββββββββ βββββββββββββββββββββββββββ β
β β MediatR (CQRS) β
β βββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β DistributedConfigHub.Application β β
β β Commands: Create, Update, Delete, Rollback β β
β β Queries: GetAll, GetById, GetHistory β β
β β Behaviors: ValidationBehavior (Pipeline) β β
β βββββββ¬βββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β βββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β DistributedConfigHub.Infrastructure β β
β β EF Core + PG β RabbitMQ Publisher β AuditInterceptor β β
β βββββββ¬βββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββ β
ββββββββββΌβββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββ
β β
ββββββΌββββββ βββββββΌβββββββ
β π PG π β β π RabbitMQβ
β :5432 β β :5672 β
ββββββββββββ βββββββ¬βββββββ
β Signal (Direct Exchange)
ββββββββΌβββββββββββββββββββββββββββ
β DemoConsumerApp β
β ββββββββββββββββββββββββββββ β
β β Client (SDK) β β
β β β’ RabbitMqSubscriber β β
β β β’ Hot-Reload Cache β β
β β β’ Fallback (JSON file) β β
β ββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββ
| Layer | Responsibility |
|---|---|
| Domain | Entities (ConfigurationRecord, AuditLog), Enums, BaseAuditableEntity |
| Application | CQRS Command/Queries, Validators, Interfaces, DTOs |
| Infrastructure | EF Core DbContext, Repositories, RabbitMQ Publisher, Audit Interceptor |
| Api | Controllers, Action Filter (API Key), Exception Handlers, Admin Panel |
| Client (SDK) | NuGet-ready library for consumers: config cache, RabbitMQ subscriber, fallback |
| DemoConsumerApp | Example service using the SDK: live update demo |
| Tests | Unit + Integration tests (xUnit, Testcontainers) |
DistributedConfigHub.Net/
βββ π¦ DistributedConfigHub.Api/ # REST API + Admin Panel
β βββ Controllers/ # Configurations, Health
β βββ Filters/ # ApiKeyAuthorizeAttribute (X-Api-Key Security)
β βββ Infrastructure/ExceptionHandling/ # GlobalExceptionHandler
β βββ wwwroot/ # Admin Panel (Pure JS/CSS Login & Dashboard)
β
βββ π¦ DistributedConfigHub.Application/ # CQRS + Business Logic
β βββ Features/Commands/ # Create, Update, Delete, Rollback Handlers
β βββ Features/Queries/ # GetList, GetById, GetDeleted, GetHistory Handlers
β βββ Behaviors/ # Validation & TenantAuthorization (Pipeline)
β βββ Exceptions/ # Custom Application Exceptions
β βββ DTOs/ # Data Transfer Objects (ConfigurationDto)
β βββ Interfaces/ # Repository, Messaging and Context Contracts
β
βββ π¦ DistributedConfigHub.Domain/ # Domain Model (Zero Dependency)
β βββ Entities/ # ConfigurationRecord, AuditLog, BaseAuditableEntity
β βββ Enums/ # ConfigurationType (Numeric/JSON/Bool/etc)
β
βββ π¦ DistributedConfigHub.Infrastructure/# Infrastructure and Data Access
β βββ Data/ # ConfigDbContext, FluentAPI Configs, Interceptors
β βββ Migrations/ # PostgreSQL Database Schemas
β βββ Repositories/ # EF Core Repository Implementations
β βββ Messaging/ # RabbitMqPublisher (Signaling Logic)
β
βββ π¦ DistributedConfigHub.Client/ # Smart SDK (Consumer-Side)
β βββ Interfaces/ # IConfigSdkService (Contracts)
β βββ Models/ # DTOs and Settings (Item, Options)
β βββ Services/ # Application Logic (SDK, Subscriber)
β βββ ServiceCollectionExtensions.cs # DI Registration Mechanism
β
βββ π¦ DemoConsumerApp/ # "Zero Downtime" Example Application
β βββ Controllers/ # ServiceHealth (DB & SDK Health Check)
β βββ Data/ # ProductDbContext & DatabaseInitializer
β βββ local-fallback-config.json # Backup Configuration used when API is down
β
βββ π¦ DistributedConfigHub.Tests/ # Test Processes
β βββ DistributedConfigHub.UnitTests # Moq-Based Logic Tests
β βββ DistributedConfigHub.IntegrationTests # Testcontainers (Docker) Based End-to-End Tests
β
βββ π docs/ # Documentation Assets
β βββ ConfigHub.postman_collection.json # Up-to-date Postman Collection
β βββ assets/ # Demo GIF and Screenshots
β
βββ π³ docker-compose.yml # Full-stack Orchestration
βββ π README.md
In this project, configuration is not just a "Key-Value" pair; it includes metadata like ownership, environment, and active status.
- Data Integrity (ACID): Using a Composite Unique Constraint on
Name + ApplicationName + Environment, inconsistent data entry is prevented at the database level. - Audit Trail: A relational structure was chosen to provide "Rollback" and "History" features. With the
EF Core Interceptorarchitecture, every change is automatically recorded in theAuditLogtable. - Persistence: Redis is a caching solution; since configuration is the "brain" of the system, PostgreSQL was chosen for its persistence and relational querying capabilities.
RabbitMQ (Direct Exchange) was chosen to instantly reflect changes.
- Guaranteed Delivery: Unlike Redis Pub/Sub, RabbitMQ ensures the message is delivered (Queue structure).
- Targeted Routing (RoutingKey): Each application uses its own
ApplicationNameas aRoutingKey. Thus, whenSERVICE-Ais updated,SERVICE-Bis not subjected to unnecessary network traffic or CPU load. - Instant Synchronization: The moment a change is made, consumer SDKs are notified and they update their memory (cache) in under 10ms.
The system implements a "Zero Trust" principle:
- Identification (Filter): The identity of the requester is verified via API Key using the
ApiKeyAuthorizeAttribute. - Authorization (MediatR Pipeline): Using
TenantAuthorizationBehavior, an attempt by one service to access another service's data is blocked using a "Zero-Database-Trip" method before it even reaches the business logic (Handler). - Double-Trip Protection: To prevent performance loss in ID-based requests (Update/Delete), authorization is performed at the Handler level, avoiding the Double-Trip Anti-pattern.
A multi-layered protection strategy against the "Single Point of Failure" (SPOF) risk of centralized systems is implemented:
- 3-Tier Retry: When the SDK cannot reach the API, it doesn't give up immediately; it retries 3 times with exponential backoff.
- Memory-over-Disk Priority: In the event of an API error or database outage, if there is already working data in the SDK's memory, it is never deleted/corrupted. The "Graceful Stay" principle (Old but working data is better than none) applies.
- Empty Response Guard: Even if the API returns 200 OK, if it sends an "empty" list because the database connection is lost; the SDK considers this data "suspicious" and preserves its existing healthy cache.
- Local Snapshot & Graceful Degradation: The asynchronous backup on disk (
local-fallback-config.json) only kicks in if the API is down during the initial startup. Thus, even if the center crashes, the client can boot up with the "Last Known Good" values.
Note
Cache Hierarchy: The SDK uses a "RAM > Disk" hierarchy for performance. If data is in RAM while the API is down, it does not go to disk (avoiding Disk I/O cost). Disk snapshots are just an insurance for "Cold Start" scenarios where memory is empty (e.g., the central API crashes at 03:00 AM, and the consumer app restarts at that exact moment).
Clean Architecture is adopted to ensure system sustainability, growth potential, and testability, keeping business rules at the center and dependencies flowing inwards.
- Separation of Concerns:
DomainandApplicationlayers are completely agnostic of infrastructure tools (EF Core, RabbitMQ, HTTP). Thus, even if you switch to another database tomorrow, not a single line of code in the business logic changes. - Focused Business Logic (CQRS): Read (Query) and write (Command) operations are separated using
MediatR. This approach ensures that classes (Handlers) serve only a single purpose (Single Responsibility) and prevents spaghetti code. - High Testability: Since the business logic is not tightly-coupled to the database or network infrastructure, writing Unit Tests by
Mocking external services is extremely fast and reliable.
A "Lock-Free Read" principle is implemented while holding configurations in memory within the SDK:
- ConcurrentDictionary: Allows thousands of threads to read data from the dictionary simultaneously without blocking the write (update) operation.
- Volatile & Atomic Swap: The
_cachereference is marked asvolatile. When new configurations arrive from the API, instead of modifying the old dictionary, a completely new dictionary is created and the reference is swapped atomically. This ensures reader threads never see "partially filled" or "corrupt" data. - SemaphoreSlim: Used to prevent the system from sending unnecessary multiple API requests during a configuration refresh (
Reload). Reads are lock-free, but writes (the moment of update) are serially controlled.
| Service | Scope | Reason |
|---|---|---|
ConfigDbContext |
Scoped | EF Core DbContext requires a per-request lifecycle |
IConfigurationRepository |
Scoped | Dependent on DbContext, must be in the same scope |
IAuditLogRepository |
Scoped | Dependent on DbContext |
IMessagePublisher (RabbitMQ) |
Singleton | Persistent connection/channel reuse, thread-safe with SemaphoreSlim |
IAuditContextAccessor |
Singleton | Carries data on a per-request basis with AsyncLocal<T>, stateless |
AuditInterceptor |
Singleton | EF Core interceptors run as singletons |
ApiKeyAuthorizeAttribute |
Scoped | New instance per request as a ServiceFilter |
| Service | Scope | Reason |
|---|---|---|
IConfigSdkService |
Singleton | ConcurrentDictionary cache must be preserved throughout the lifecycle |
HttpClient |
IHttpClientFactory | DNS pool management, prevents socket exhaustion with SetHandlerLifetime(5min) |
RabbitMqSubscriberHostedService |
Singleton | BackgroundService is already a Singleton |
β οΈ Captive Dependency Warning:IConfigSdkServicemust be a Singleton because theBackgroundService(Singleton) injects it. Making it Transient/Scoped causes a Captive Dependency anti-pattern.
- Docker Desktop (v20+ recommended)
- Git
# 1. Clone the project
git clone https://github.com/FurkanHaydari/DistributedConfigHub.Net
cd DistributedConfigHub.Net
# 2. Spin up the entire system
docker compose up --build -d
# 3. Check container statuses
docker compose psβ³ The initial
--buildtakes about 30-60 seconds. The API starts after PostgreSQL and RabbitMQ health checks pass.
To recreate the database from scratch:
docker compose down -v # Delete volumes
docker compose up --build -d # RecreateWhen the system is up, the following addresses become active:
| Service | URL | Description |
|---|---|---|
| π₯οΈ Admin Panel | http://localhost:5173 | Management interface (config CRUD, health, audit) |
| π‘ Scalar UI (API) | http://localhost:5173/scalar/v1 | Admin API modern endpoint documentation |
| β€οΈ Health Check | http://localhost:5173/Health | PostgreSQL + RabbitMQ status check |
| π₯οΈ Demo Consumer App | http://localhost:5174 | Consumer App Service |
| π‘ Swagger UI | http://localhost:5174/swagger | Demo Consumer App API endpoint documentation |
| π RabbitMQ Panel | http://localhost:15672 | Queue management (guest / guest) |
| π PostgreSQL | localhost:5432 | Database (postgres / postgres) |
| Application | API Key | Usage |
|---|---|---|
SERVICE-A |
service-a-secret-key |
Service A Key |
SERVICE-B |
service-b-secret-key |
Service B Key |
| Method | Endpoint | Description |
|---|---|---|
GET |
/configurations?applicationName=SERVICE-A |
List configurations |
GET |
/configurations/{id} |
Get single configuration |
POST |
/configurations |
Create new configuration |
GET |
/configurations/deleted?applicationName=SERVICE-A |
List deleted (IsActive = false) configurations |
PUT |
/configurations/{id} |
Update value (+ RabbitMQ signal) |
DELETE |
/configurations/{id} |
Deactivate configuration |
GET |
/configurations/{id}/history |
View audit history |
POST |
/configurations/{id}/rollback/{auditLogId} |
Rollback to a specific point |
GET |
/Health |
PostgreSQL + RabbitMQ status check |
To quickly explore and test the API after spinning up the project, there is a ready-to-use Postman configuration in the repository.
How to Use?
- Open the Postman application and click the Import button at the top left.
- Select and import the
ConfigHub.postman_collection.jsonfile located under thedocsfolder in the project directory. - All requests in the imported folder come ready with environment variables (e.g.,
X-Api-Key,url,configId,auditLogId) and some scripts. - You can start testing the API directly by clicking the Send button without making any settings!
List Configurations:
GET /configurations?applicationName=SERVICE-A
X-Api-Key: service-a-secret-keyAdd New Configuration:
POST /configurations
Content-Type: application/json
X-Api-Key: service-a-secret-key
{
"name": "NewFeatureFlag",
"type": 0,
"value": "enabled",
"applicationName": "SERVICE-A",
"environment": "prod"
}Update Value (Live Signal):
PUT /configurations/00000000-0000-0000-0000-000000000001
Content-Type: application/json
X-Api-Key: service-a-secret-key
{
"id": "00000000-0000-0000-0000-000000000009",
"value": "true"
}# Run all tests
dotnet test
# Result: 17/17 Test Passed β
| Test Category / Class | Scope |
|---|---|
| π§ͺ Unit Tests | |
ConfigSdkServiceTests |
All kinds of network outage scenarios on the SDK side, local file (fallback) mechanism, and relative URL resolution. |
UpdateConfigurationCommandHandlerTests |
MediatR command processing, KeyNotFoundException thrown for missing records, and asynchronous RabbitMQ event triggering. |
ApiKeyAuthorizeAttributeTests |
X-Api-Key validation logic, 401 Unauthorized responses for missing/incorrect keys, and security filters. |
| π Integration Tests | |
LiveUpdateIntegrationTest |
Live signaling and data consistency test on a real RabbitMQ and PostgreSQL (Testcontainers). |
RollbackIntegrationTest |
Rollback flow to restore a configuration to an old point with millisecond accuracy via audit logs. |
SecurityAndResilienceIntegrationTest |
End-to-end verification of resilience scenarios at the infrastructure layer and API security. |
You can experience the system live by following the steps below in order.
Before starting the live test, prepare the following windows:
| Window | Content | Note |
|---|---|---|
| π₯οΈ Terminal 1 | Docker commands | Main terminal |
| π₯οΈ Terminal 2 | docker compose logs -f demo_consumer |
Consumer logs (live) |
| π₯οΈ Terminal 3 | docker compose logs -f config_api |
Config Hub logs (live) |
| π Browser Tab 1 | http://localhost:5173 |
Admin Panel |
| π Browser Tab 2 | http://localhost:5174 |
Consumer Product Page |
| π Browser Tab 3 | http://localhost:15672 |
RabbitMQ (optional) |
In Terminal 1:
docker compose up --build -d
docker compose ps # All 4 containers should be "Up"Open Terminal 2 and Terminal 3 (side by side):
# Terminal 2 β Watch consumer logs live
docker compose logs -f demo_consumer
# Terminal 3 β Watch Config Hub API logs live
docker compose logs -f config_apiπ‘ Initially, the following logs will appear in Terminal 2:
Database 'db_alpha' created on PostgreSQL server. Database 'db_alpha' initialized with seed data. Database 'db_beta' created on PostgreSQL server. Database 'db_beta' initialized with seed data. RabbitMQ Background Subscriber initialized and listening to routing key: SERVICE-ABoth databases are created by the
DatabaseInitializer.
π Browser Tab 1 β http://localhost:5173
- Health cards show the health status β PostgreSQL β , RabbitMQ β
- Registered Services card shows registered services β SERVICE-A (dev, staging, prod)
- You can switch between environments using the filter β dev / staging / prod
- You can examine records like
MainDatabase,IsMaintenanceModeEnabled,ExternalPaymentApiUrlin the configuration list.
π Browser Tab 2 β http://localhost:5174
- The page will automatically load products (5 Alpha products)
- "SYSTEM INFO" panel in the bottom right corner can be seen:
- Uptime β How long the application has been up (this value will keep increasing!)
- Environment β PROD (automatically from
ASPNETCORE_ENVIRONMENT) - Active DB β db_alpha
- Maintenance Mode β Disabled
- ENV: PROD and Active DB: db_alpha badges appear in the header
π‘ The page auto-refreshes every 3 seconds β no manual refresh required.
π Browser Tab 1 β Admin Panel
- Update the
IsMaintenanceModeEnabled(dev) value fromfalseβtrueand save
π Browser Tab 2 β Consumer Page (Auto-refreshes every 3 seconds)
-
The maintenance mode overlay will appear instantly:
- π§ Special maintenance mode window
- Environment: DEV Β· DB: db_alpha info
-
SYSTEM INFO panel continues to appear in maintenance mode:
- Maintenance Mode β ACTIVE (yellow)
- Uptime β Still increasing (application does not restart)
-
Return to Admin Panel and set
IsMaintenanceModeEnabledβfalse -
The consumer page instantly returns to the product catalog β¨
π Browser Tab 2 β Look at the products on the Consumer page and the panel on the bottom right:
- 5 Alpha products (Alpha Laptop Pro, Alpha Phone X, ...)
- Active DB: db_alpha
- Pay attention to the Uptime value
π Browser Tab 1 β Return to Admin Panel:
- Find the
MainDatabase(dev) configuration - Change its value:
Old: Host=postgres;Database=db_alpha;Username=postgres;Password=postgres New: Host=postgres;Database=db_beta;Username=postgres;Password=postgres - Save
π Browser Tab 2 β Return to Consumer page:
- Now 4 Beta products are visible! (Beta Smartwatch Ultra, Beta Tablet Air, ...)
- In the bottom right panel:
- Active DB β db_beta π£
- Uptime β Still increasing! Application did not close π’
- Maintenance Mode β Disabled
π Browser Tab 1 β Admin Panel
- Click the π (history) button of the
MainDatabaseconfiguration - Change history will be visible (INSERT β UPDATE β ...)
- Rollback to the first value (db_alpha) with the "Rollback to this point" button
- Alpha products will appear again on the Consumer page
- Uptime still continues π’
π Browser Tab 3 β http://localhost:15672 β Login: guest / guest
-
Examining Exchange Structure:
- Go to the Exchanges tab and click on the
config_updates_directexchange. - In the Bindings section, you will see the currently connected
demo_consumerqueue andSERVICE-Arouting key.
- Go to the Exchanges tab and click on the
-
"Sniffing" with a New Queue (Security Test):
- Since real consumers use
Exclusivequeues (which are deleted when the connection drops), they are hard to monitor externally. Go to the Queues and Streams tab for this. - Create a persistent queue named
debug-snifferfrom the Add a new queue section. - Enter this new queue and bind it to the
config_updates_directexchange with theSERVICE-Arouting key from the Bindings section.
- Since real consumers use
-
Capturing Message Live:
- Update a setting from the Admin Panel.
- You will see the message drop into the
debug-snifferqueue in RabbitMQ (Ready: 1). - Click the Get Messages button in the queue details.
- β¨ The payload of the signal (e.g.
{"SERVICE-A|prod"}) will appear as raw data.
Send a request with an incorrect API key:
curl -H "X-Api-Key: wrong-key" "http://localhost:5173/configurations?applicationName=SERVICE-A"
# β 401 UnauthorizedWith the correct API key:
curl -H "X-Api-Key: service-a-secret-key" "http://localhost:5173/configurations?applicationName=SERVICE-A"
# β 200 OK + JSON dataClick to See Log Commands
# All container logs (live)
docker compose logs -f
# Only Consumer logs
docker compose logs -f demo_consumer
# Only Config Hub API logs
docker compose logs -f config_api
# Only PostgreSQL logs
docker compose logs -f postgres
# Only RabbitMQ logs
docker compose logs -f rabbitmq
# Last 50 lines + live tracking
docker compose logs -f --tail=50 demo_consumer
# Container statuses
docker compose ps



