BitzOrcas uses DotNetCore.CAP as the event bus with RabbitMQ as the transport layer and SQL Server as the Outbox storage. This provides exactly-once semantic delivery with transactional guarantees.
Architecture
┌─────────────────────────────────────────────────┐│ Application Layer ││ CommandHandler → UoW.Commit() ││ │ │ ││ ▼ ▼ ││ DB Write CAP PublishAsync() ││ │ │ ││ └────────────┬───────────────┘ ││ ▼ ││ SqlTransaction + CAP Outbox ││ (atomic commit) │└─────────────────────┬───────────────────────────┘ ▼┌─────────────────────────────────────────────────┐│ CAP Internal (Background) ││ Outbox Table → RabbitMQ → Consumer │└─────────────┬───────────────────────────────────┘ ▼┌─────────────────────────────────────────────────┐│ Event Consumers ││ CapSubscribe handler → Service method │└─────────────────────────────────────────────────┘CAP + SqlSugar integration
The core integration point is CapSqlSugarUnitOfWork:
// Publishing within a transactional unit of workawait unitOfWork.PublishAsync(new NoteCreatedIntegrationEvent(noteId));await unitOfWork.PublishAsync(new NotificationRequestedEvent(userId, "Note created"));await unitOfWork.CommitAsync(); // DB + Outbox committed atomicallyConnection configuration
{ "ConnectionStrings": { "Default": "Server=localhost;Database=BitzOrcas;..." }, "RabbitMq": { "Host": "localhost", "Port": 5672, "User": "guest", "Password": "guest" }}Both SqlSugar and EF Core paths use DotNetCore.CAP.SqlServer for the Outbox table and DotNetCore.CAP.RabbitMQ for message transport.
Integration event contracts
Integration events are defined in BitzOrcas.SaaS.Contracts — a separate assembly that both publishers and consumers reference:
BitzOrcas.SaaS.Contracts/├── Auditing/ → Audit-related events├── Catalog/ → Catalog events (plans, offerings)├── Chat/ → Chat message events├── Files/ → File upload/delete events├── Notifications/ → Notification request events├── PlatformBilling/→ Billing/subscription/invoice events├── Tickets/ → Ticket lifecycle events└── Webhooks/ → Webhook event envelopesEvent publishing
Events are published through the INotificationPublisher abstraction:
public interface INotificationPublisher{ Task PublishAsync<T>(T @event, CancellationToken ct = default) where T : notnull;}- Default:
NullNotificationPublisher(API Shell mode, no-op) - Production:
CapNotificationPublisher(publishes through CAP)
Event consumers
CAP consumers use the [CapSubscribe] attribute:
// Example: Webhook event consumerpublic class WebhookPlatformEventConsumer{ [CapSubscribe("bitzorcas.webhook.*")] public async Task HandleAsync(WebhookEventEnvelope envelope) { // Route event to webhook delivery service }}Webhook subsystem
The webhook delivery system is a full-featured event-to-HTTP bridge:
| Component | Purpose |
|---|---|
WebhookSubscriptionService | Manage subscriptions (CRUD + secret rotation) |
WebhookDeliveryService | Deliver events to subscriber endpoints |
WebhookRetryPolicy | Exponential backoff with configurable limits |
IWebhookDeadLetterQueue | Failed delivery tracking |
IWebhookIpAllowlistPolicy | IP restriction for subscriber callbacks |
IWebhookRateLimitPolicy | Per-subscriber delivery rate limiting |
WebhookSignature | HMAC-SHA256 request signing for verification |
Webhook request signing
Each webhook delivery includes signature headers for verification:
X-Webhook-Signature: sha256=abc123...X-Webhook-Timestamp: 2026-06-22T10:00:00ZX-Webhook-Event: ticket.createdX-Webhook-Subscription-Id: sub-789API Shell mode
When no database/RabbitMQ is configured, all eventing falls back to null implementations:
NullNotificationPublisher— silently drops published events- No CAP consumer registration — no background processing
This enables running the API Host in “Shell mode” for lightweight testing without infrastructure.