Skip to content
bitzorcas
EN

Reference

Eventing building block

CAP-based event-driven architecture — RabbitMQ transport, SQL Server Outbox for atomic publishing, integration event contracts, and the webhook delivery subsystem.

Last updated

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 work
await unitOfWork.PublishAsync(new NoteCreatedIntegrationEvent(noteId));
await unitOfWork.PublishAsync(new NotificationRequestedEvent(userId, "Note created"));
await unitOfWork.CommitAsync(); // DB + Outbox committed atomically

Connection 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 envelopes

Event 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 consumer
public 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:

ComponentPurpose
WebhookSubscriptionServiceManage subscriptions (CRUD + secret rotation)
WebhookDeliveryServiceDeliver events to subscriber endpoints
WebhookRetryPolicyExponential backoff with configurable limits
IWebhookDeadLetterQueueFailed delivery tracking
IWebhookIpAllowlistPolicyIP restriction for subscriber callbacks
IWebhookRateLimitPolicyPer-subscriber delivery rate limiting
WebhookSignatureHMAC-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:00Z
X-Webhook-Event: ticket.created
X-Webhook-Subscription-Id: sub-789

API 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.

See also