@polymorphism-tech/morph-spec 4.3.1 → 4.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/morph-spec.js +1 -1
- package/package.json +2 -1
- package/src/commands/project/doctor.js +24 -18
- package/src/commands/project/init.js +15 -5
- package/src/commands/project/update.js +7 -3
- package/src/commands/state/state.js +24 -2
- package/src/core/templates/template-registry.js +1 -1
- package/src/core/workflows/workflow-detector.js +2 -2
- package/stacks/blazor-azure/.claude/commands/morph-apply.md +221 -0
- package/stacks/blazor-azure/.claude/commands/morph-archive.md +79 -0
- package/stacks/blazor-azure/.claude/commands/morph-deploy.md +529 -0
- package/stacks/blazor-azure/.claude/commands/morph-infra.md +209 -0
- package/stacks/blazor-azure/.claude/commands/morph-preflight.md +227 -0
- package/stacks/blazor-azure/.claude/commands/morph-proposal.md +122 -0
- package/stacks/blazor-azure/.claude/commands/morph-status.md +86 -0
- package/stacks/blazor-azure/.claude/commands/morph-troubleshoot.md +122 -0
- package/stacks/blazor-azure/.morph/.morphversion +5 -5
- package/stacks/blazor-azure/.morph/config/config.json +9 -0
- package/stacks/blazor-azure/.morph/project/context/README.md +17 -0
- package/stacks/blazor-azure/.morph/standards/ai-agents/blazor-ui.md +364 -0
- package/stacks/blazor-azure/.morph/standards/ai-agents/production.md +415 -0
- package/stacks/blazor-azure/.morph/standards/ai-agents/setup.md +418 -0
- package/stacks/blazor-azure/.morph/standards/ai-agents/team-orchestration.md +479 -0
- package/stacks/blazor-azure/.morph/standards/ai-agents/workflows.md +354 -0
- package/stacks/blazor-azure/.morph/standards/architecture/ddd/aggregates.md +120 -0
- package/stacks/blazor-azure/.morph/standards/architecture/ddd/entities.md +99 -0
- package/stacks/blazor-azure/.morph/standards/architecture/ddd/value-objects.md +124 -0
- package/stacks/blazor-azure/.morph/standards/backend/api/minimal-api.md +494 -0
- package/stacks/blazor-azure/.morph/standards/backend/api/rest.md +492 -0
- package/stacks/blazor-azure/.morph/standards/backend/api/validation.md +88 -0
- package/stacks/blazor-azure/.morph/standards/backend/authentication/passkeys.md +428 -0
- package/stacks/blazor-azure/.morph/standards/backend/database/ef-core.md +199 -0
- package/stacks/blazor-azure/.morph/standards/backend/database/migrations.md +393 -0
- package/stacks/blazor-azure/.morph/standards/backend/database/postgresql/database.md +352 -0
- package/stacks/blazor-azure/.morph/standards/backend/database/repository-patterns.md +528 -0
- package/stacks/blazor-azure/.morph/standards/backend/database/vector-search-rag.md +541 -0
- package/stacks/blazor-azure/.morph/standards/backend/dotnet/async.md +366 -0
- package/stacks/blazor-azure/.morph/standards/backend/dotnet/core.md +117 -0
- package/stacks/blazor-azure/.morph/standards/backend/dotnet/di.md +439 -0
- package/stacks/blazor-azure/.morph/standards/backend/dotnet/program-cs-checklist.md +92 -0
- package/stacks/blazor-azure/.morph/standards/backend/integrations/asaas/asaas-api.md +216 -0
- package/stacks/blazor-azure/.morph/standards/backend/integrations/clerk/clerk-auth.md +290 -0
- package/stacks/blazor-azure/.morph/standards/backend/integrations/hangfire/hangfire-jobs.md +350 -0
- package/stacks/blazor-azure/.morph/standards/backend/integrations/resend/resend-email.md +385 -0
- package/stacks/blazor-azure/.morph/standards/context/analytics.md +96 -0
- package/stacks/blazor-azure/.morph/standards/context/bundles.md +110 -0
- package/stacks/blazor-azure/.morph/standards/context/priming.md +78 -0
- package/stacks/blazor-azure/.morph/standards/core/architecture.md +185 -0
- package/stacks/blazor-azure/.morph/standards/core/coding.md +214 -0
- package/stacks/blazor-azure/.morph/standards/core/git-branching-strategy.md +403 -0
- package/stacks/blazor-azure/.morph/standards/core/git.md +185 -0
- package/stacks/blazor-azure/.morph/standards/core/testing.md +295 -0
- package/stacks/blazor-azure/.morph/standards/data/nosql/blob-storage.md +102 -0
- package/stacks/blazor-azure/.morph/standards/data/nosql/cache/redis.md +97 -0
- package/stacks/blazor-azure/.morph/standards/data/nosql/cosmos-db.md +118 -0
- package/stacks/blazor-azure/.morph/standards/data/vector-search/azure-ai-search.md +121 -0
- package/stacks/blazor-azure/.morph/standards/data/vector-search/rag-chunking.md +104 -0
- package/stacks/blazor-azure/.morph/standards/frontend/blazor/design-checklist.md +222 -0
- package/stacks/blazor-azure/.morph/standards/frontend/blazor/fluent-ui-setup.md +595 -0
- package/stacks/blazor-azure/.morph/standards/frontend/blazor/fluent-ui.md +137 -0
- package/stacks/blazor-azure/.morph/standards/frontend/blazor/html-conversion.md +184 -0
- package/stacks/blazor-azure/.morph/standards/frontend/blazor/lifecycle.md +195 -0
- package/stacks/blazor-azure/.morph/standards/frontend/blazor/pitfalls.md +198 -0
- package/stacks/blazor-azure/.morph/standards/frontend/blazor/state.md +191 -0
- package/stacks/blazor-azure/.morph/standards/frontend/design-system/animations.md +151 -0
- package/stacks/blazor-azure/.morph/standards/frontend/design-system/naming.md +64 -0
- package/stacks/blazor-azure/.morph/standards/frontend/nextjs/nextjs-patterns.md +198 -0
- package/stacks/blazor-azure/.morph/standards/infrastructure/azure/azure.md +624 -0
- package/stacks/blazor-azure/.morph/standards/infrastructure/azure/bicep/bicep-patterns.md +422 -0
- package/stacks/blazor-azure/.morph/standards/infrastructure/azure/devops/azure-devops-setup.md +516 -0
- package/stacks/blazor-azure/.morph/standards/infrastructure/azure/devops/local-development.md +520 -0
- package/stacks/blazor-azure/.morph/standards/infrastructure/azure/services/functions.md +486 -0
- package/stacks/blazor-azure/.morph/standards/infrastructure/azure/services/service-bus.md +459 -0
- package/stacks/blazor-azure/.morph/standards/infrastructure/azure/services/storage.md +407 -0
- package/stacks/blazor-azure/.morph/standards/infrastructure/docker/easypanel-deploy.md +196 -0
- package/stacks/blazor-azure/.morph/standards/infrastructure/supabase/mcp-setup.md +252 -0
- package/stacks/blazor-azure/.morph/standards/infrastructure/supabase/supabase-auth.md +176 -0
- package/stacks/blazor-azure/.morph/standards/infrastructure/supabase/supabase-pgvector.md +169 -0
- package/stacks/blazor-azure/.morph/standards/infrastructure/supabase/supabase-rls.md +184 -0
- package/stacks/blazor-azure/.morph/standards/infrastructure/supabase/supabase-storage.md +153 -0
- package/stacks/blazor-azure/.morph/standards/integration/api/graphql.md +91 -0
- package/stacks/blazor-azure/.morph/standards/integration/api/grpc.md +114 -0
- package/stacks/blazor-azure/.morph/standards/integration/api/rest-design.md +95 -0
- package/stacks/blazor-azure/.morph/standards/integration/event-driven/cqrs.md +101 -0
- package/stacks/blazor-azure/.morph/standards/integration/event-driven/event-sourcing.md +124 -0
- package/stacks/blazor-azure/.morph/standards/integration/event-driven/service-bus.md +95 -0
- package/stacks/blazor-azure/.morph/standards/observability/logging.md +131 -0
- package/stacks/blazor-azure/.morph/standards/observability/metrics.md +121 -0
- package/stacks/blazor-azure/.morph/standards/observability/monitoring.md +114 -0
- package/stacks/blazor-azure/.morph/standards/observability/tracing.md +132 -0
- package/stacks/blazor-azure/.morph/standards/workflows/parallel-execution.md +112 -0
- package/stacks/blazor-azure/.morph/standards/workflows/thread-management.md +113 -0
- package/stacks/blazor-azure/CLAUDE.md +106 -101
- package/stacks/nextjs-supabase/.claude/commands/morph-apply.md +221 -0
- package/stacks/nextjs-supabase/.claude/commands/morph-archive.md +79 -0
- package/stacks/nextjs-supabase/.claude/commands/morph-deploy.md +529 -0
- package/stacks/nextjs-supabase/.claude/commands/morph-infra.md +209 -0
- package/stacks/nextjs-supabase/.claude/commands/morph-preflight.md +227 -0
- package/stacks/nextjs-supabase/.claude/commands/morph-proposal.md +122 -0
- package/stacks/nextjs-supabase/.claude/commands/morph-status.md +86 -0
- package/stacks/nextjs-supabase/.claude/commands/morph-troubleshoot.md +122 -0
- package/stacks/nextjs-supabase/.morph/.morphversion +5 -0
- package/stacks/nextjs-supabase/.morph/config/agents.json +345 -345
- package/stacks/nextjs-supabase/.morph/config/config.json +9 -0
- package/stacks/nextjs-supabase/.morph/project/context/README.md +17 -0
- package/stacks/nextjs-supabase/.morph/standards/ai-agents/blazor-ui.md +364 -0
- package/stacks/nextjs-supabase/.morph/standards/ai-agents/production.md +415 -0
- package/stacks/nextjs-supabase/.morph/standards/ai-agents/setup.md +418 -0
- package/stacks/nextjs-supabase/.morph/standards/ai-agents/team-orchestration.md +479 -0
- package/stacks/nextjs-supabase/.morph/standards/ai-agents/workflows.md +354 -0
- package/stacks/nextjs-supabase/.morph/standards/architecture/ddd/aggregates.md +120 -0
- package/stacks/nextjs-supabase/.morph/standards/architecture/ddd/entities.md +99 -0
- package/stacks/nextjs-supabase/.morph/standards/architecture/ddd/value-objects.md +124 -0
- package/stacks/nextjs-supabase/.morph/standards/backend/api/minimal-api.md +494 -0
- package/stacks/nextjs-supabase/.morph/standards/backend/api/rest.md +492 -0
- package/stacks/nextjs-supabase/.morph/standards/backend/api/validation.md +88 -0
- package/stacks/nextjs-supabase/.morph/standards/backend/authentication/passkeys.md +428 -0
- package/stacks/nextjs-supabase/.morph/standards/backend/database/ef-core.md +199 -0
- package/stacks/nextjs-supabase/.morph/standards/backend/database/migrations.md +393 -0
- package/stacks/nextjs-supabase/.morph/standards/backend/database/postgresql/database.md +352 -0
- package/stacks/nextjs-supabase/.morph/standards/backend/database/repository-patterns.md +528 -0
- package/stacks/nextjs-supabase/.morph/standards/backend/database/vector-search-rag.md +541 -0
- package/stacks/nextjs-supabase/.morph/standards/backend/dotnet/async.md +366 -0
- package/stacks/nextjs-supabase/.morph/standards/backend/dotnet/core.md +117 -0
- package/stacks/nextjs-supabase/.morph/standards/backend/dotnet/di.md +439 -0
- package/stacks/nextjs-supabase/.morph/standards/backend/dotnet/program-cs-checklist.md +92 -0
- package/stacks/nextjs-supabase/.morph/standards/backend/integrations/asaas/asaas-api.md +216 -0
- package/stacks/nextjs-supabase/.morph/standards/backend/integrations/clerk/clerk-auth.md +290 -0
- package/stacks/nextjs-supabase/.morph/standards/backend/integrations/hangfire/hangfire-jobs.md +350 -0
- package/stacks/nextjs-supabase/.morph/standards/backend/integrations/resend/resend-email.md +385 -0
- package/stacks/nextjs-supabase/.morph/standards/context/analytics.md +96 -0
- package/stacks/nextjs-supabase/.morph/standards/context/bundles.md +110 -0
- package/stacks/nextjs-supabase/.morph/standards/context/priming.md +78 -0
- package/stacks/nextjs-supabase/.morph/standards/core/architecture.md +185 -0
- package/stacks/nextjs-supabase/.morph/standards/core/coding.md +214 -0
- package/stacks/nextjs-supabase/.morph/standards/core/git-branching-strategy.md +403 -0
- package/stacks/nextjs-supabase/.morph/standards/core/git.md +185 -0
- package/stacks/nextjs-supabase/.morph/standards/core/testing.md +295 -0
- package/stacks/nextjs-supabase/.morph/standards/data/nosql/blob-storage.md +102 -0
- package/stacks/nextjs-supabase/.morph/standards/data/nosql/cache/redis.md +97 -0
- package/stacks/nextjs-supabase/.morph/standards/data/nosql/cosmos-db.md +118 -0
- package/stacks/nextjs-supabase/.morph/standards/data/vector-search/azure-ai-search.md +121 -0
- package/stacks/nextjs-supabase/.morph/standards/data/vector-search/rag-chunking.md +104 -0
- package/stacks/nextjs-supabase/.morph/standards/frontend/blazor/design-checklist.md +222 -0
- package/stacks/nextjs-supabase/.morph/standards/frontend/blazor/fluent-ui-setup.md +595 -0
- package/stacks/nextjs-supabase/.morph/standards/frontend/blazor/fluent-ui.md +137 -0
- package/stacks/nextjs-supabase/.morph/standards/frontend/blazor/html-conversion.md +184 -0
- package/stacks/nextjs-supabase/.morph/standards/frontend/blazor/lifecycle.md +195 -0
- package/stacks/nextjs-supabase/.morph/standards/frontend/blazor/pitfalls.md +198 -0
- package/stacks/nextjs-supabase/.morph/standards/frontend/blazor/state.md +191 -0
- package/stacks/nextjs-supabase/.morph/standards/frontend/design-system/animations.md +151 -0
- package/stacks/nextjs-supabase/.morph/standards/frontend/design-system/naming.md +64 -0
- package/stacks/nextjs-supabase/.morph/standards/frontend/nextjs/nextjs-patterns.md +198 -0
- package/stacks/nextjs-supabase/.morph/standards/infrastructure/azure/azure.md +624 -0
- package/stacks/nextjs-supabase/.morph/standards/infrastructure/azure/bicep/bicep-patterns.md +422 -0
- package/stacks/nextjs-supabase/.morph/standards/infrastructure/azure/devops/azure-devops-setup.md +516 -0
- package/stacks/nextjs-supabase/.morph/standards/infrastructure/azure/devops/local-development.md +520 -0
- package/stacks/nextjs-supabase/.morph/standards/infrastructure/azure/services/functions.md +486 -0
- package/stacks/nextjs-supabase/.morph/standards/infrastructure/azure/services/service-bus.md +459 -0
- package/stacks/nextjs-supabase/.morph/standards/infrastructure/azure/services/storage.md +407 -0
- package/stacks/nextjs-supabase/.morph/standards/infrastructure/docker/easypanel-deploy.md +196 -0
- package/stacks/nextjs-supabase/.morph/standards/infrastructure/supabase/mcp-setup.md +252 -0
- package/stacks/nextjs-supabase/.morph/standards/infrastructure/supabase/supabase-auth.md +176 -0
- package/stacks/nextjs-supabase/.morph/standards/infrastructure/supabase/supabase-pgvector.md +169 -0
- package/stacks/nextjs-supabase/.morph/standards/infrastructure/supabase/supabase-rls.md +184 -0
- package/stacks/nextjs-supabase/.morph/standards/infrastructure/supabase/supabase-storage.md +153 -0
- package/stacks/nextjs-supabase/.morph/standards/integration/api/graphql.md +91 -0
- package/stacks/nextjs-supabase/.morph/standards/integration/api/grpc.md +114 -0
- package/stacks/nextjs-supabase/.morph/standards/integration/api/rest-design.md +95 -0
- package/stacks/nextjs-supabase/.morph/standards/integration/event-driven/cqrs.md +101 -0
- package/stacks/nextjs-supabase/.morph/standards/integration/event-driven/event-sourcing.md +124 -0
- package/stacks/nextjs-supabase/.morph/standards/integration/event-driven/service-bus.md +95 -0
- package/stacks/nextjs-supabase/.morph/standards/observability/logging.md +131 -0
- package/stacks/nextjs-supabase/.morph/standards/observability/metrics.md +121 -0
- package/stacks/nextjs-supabase/.morph/standards/observability/monitoring.md +114 -0
- package/stacks/nextjs-supabase/.morph/standards/observability/tracing.md +132 -0
- package/stacks/nextjs-supabase/.morph/standards/workflows/parallel-execution.md +112 -0
- package/stacks/nextjs-supabase/.morph/standards/workflows/thread-management.md +113 -0
- package/stacks/nextjs-supabase/CLAUDE.md +69 -63
- package/stacks/blazor-azure/.morph/config/config.template.json +0 -122
- package/stacks/blazor-azure/.morph/hooks/pre-commit/tests-csharp.sh +0 -61
- package/stacks/blazor-azure/.morph/project.md +0 -160
- package/stacks/blazor-azure/.morph/state.json +0 -18
- package/stacks/blazor-azure/.morph/templates/.gitkeep +0 -0
- package/stacks/blazor-azure/.morph/templates/infrastructure/github/workflows/cd-prod.yml.hbs +0 -41
- package/stacks/blazor-azure/.morph/templates/infrastructure/github/workflows/cd-staging.yml.hbs +0 -24
- package/stacks/blazor-azure/.morph/templates/infrastructure/github/workflows/ci-build.yml.hbs +0 -23
- package/stacks/nextjs-supabase/.morph/config/config.template.json +0 -92
- package/stacks/nextjs-supabase/.morph/hooks/pre-commit/tests-typescript.sh +0 -61
- package/stacks/nextjs-supabase/.morph/project.md +0 -168
- package/stacks/nextjs-supabase/.morph/templates/.gitkeep +0 -0
- package/stacks/nextjs-supabase/.morph/templates/infrastructure/github/workflows/cd-prod.yml.hbs +0 -22
- package/stacks/nextjs-supabase/.morph/templates/infrastructure/github/workflows/cd-staging.yml.hbs +0 -22
- package/stacks/nextjs-supabase/.morph/templates/infrastructure/github/workflows/ci-build.yml.hbs +0 -35
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
# Azure Service Bus Standard
|
|
2
|
+
|
|
3
|
+
> **Scope:** blazor-azure
|
|
4
|
+
> **Layer:** 2 (on keyword)
|
|
5
|
+
> **Keywords:** service bus, messaging, queue, topic, subscription
|
|
6
|
+
> **Load When:** service bus or azure messaging keywords detected
|
|
7
|
+
|
|
8
|
+
Enterprise messaging patterns with Azure Service Bus.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Overview
|
|
13
|
+
|
|
14
|
+
Azure Service Bus provides:
|
|
15
|
+
- **Queues**: Point-to-point messaging with FIFO guarantee
|
|
16
|
+
- **Topics/Subscriptions**: Publish-subscribe patterns
|
|
17
|
+
- **Dead-letter queues**: Failed message handling
|
|
18
|
+
- **Message sessions**: Ordered processing
|
|
19
|
+
- **Scheduled messages**: Delayed delivery
|
|
20
|
+
|
|
21
|
+
**Use Cases:** Event-driven architectures, microservices communication, async workflows
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Core Principles
|
|
26
|
+
|
|
27
|
+
1. **At-Least-Once Delivery**: Messages delivered at least once
|
|
28
|
+
2. **Managed Identity**: Use Managed Identity over connection strings
|
|
29
|
+
3. **Idempotent Handlers**: Handle duplicate messages gracefully
|
|
30
|
+
4. **Dead-Letter Management**: Monitor and reprocess failed messages
|
|
31
|
+
5. **Sessions for Ordering**: Use sessions when message order matters
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
dotnet add package Azure.Messaging.ServiceBus
|
|
39
|
+
dotnet add package Azure.Identity
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Queue Patterns
|
|
45
|
+
|
|
46
|
+
### DI Configuration
|
|
47
|
+
|
|
48
|
+
```csharp
|
|
49
|
+
// Program.cs
|
|
50
|
+
using Azure.Identity;
|
|
51
|
+
using Azure.Messaging.ServiceBus;
|
|
52
|
+
|
|
53
|
+
builder.Services.AddSingleton(sp =>
|
|
54
|
+
{
|
|
55
|
+
var fullyQualifiedNamespace = builder.Configuration["ServiceBus:FullyQualifiedNamespace"]!;
|
|
56
|
+
return new ServiceBusClient(fullyQualifiedNamespace, new DefaultAzureCredential());
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Send Message
|
|
61
|
+
|
|
62
|
+
```csharp
|
|
63
|
+
public class OrderQueueService
|
|
64
|
+
{
|
|
65
|
+
private readonly ServiceBusClient _serviceBusClient;
|
|
66
|
+
private readonly string _queueName = "orders";
|
|
67
|
+
|
|
68
|
+
public OrderQueueService(ServiceBusClient serviceBusClient)
|
|
69
|
+
{
|
|
70
|
+
_serviceBusClient = serviceBusClient;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
public async Task SendOrderAsync(string orderId, string orderData)
|
|
74
|
+
{
|
|
75
|
+
await using var sender = _serviceBusClient.CreateSender(_queueName);
|
|
76
|
+
|
|
77
|
+
var message = new ServiceBusMessage(orderData)
|
|
78
|
+
{
|
|
79
|
+
MessageId = orderId,
|
|
80
|
+
ContentType = "application/json",
|
|
81
|
+
Subject = "OrderCreated"
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// Add custom properties
|
|
85
|
+
message.ApplicationProperties.Add("OrderId", orderId);
|
|
86
|
+
message.ApplicationProperties.Add("Priority", "High");
|
|
87
|
+
|
|
88
|
+
await sender.SendMessageAsync(message);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
public async Task ScheduleOrderAsync(string orderId, string orderData, DateTimeOffset scheduleTime)
|
|
92
|
+
{
|
|
93
|
+
await using var sender = _serviceBusClient.CreateSender(_queueName);
|
|
94
|
+
|
|
95
|
+
var message = new ServiceBusMessage(orderData)
|
|
96
|
+
{
|
|
97
|
+
MessageId = orderId
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
await sender.ScheduleMessageAsync(message, scheduleTime);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Receive Messages
|
|
106
|
+
|
|
107
|
+
```csharp
|
|
108
|
+
public class OrderProcessorService : BackgroundService
|
|
109
|
+
{
|
|
110
|
+
private readonly ServiceBusClient _serviceBusClient;
|
|
111
|
+
private readonly ILogger<OrderProcessorService> _logger;
|
|
112
|
+
private readonly string _queueName = "orders";
|
|
113
|
+
|
|
114
|
+
public OrderProcessorService(
|
|
115
|
+
ServiceBusClient serviceBusClient,
|
|
116
|
+
ILogger<OrderProcessorService> logger)
|
|
117
|
+
{
|
|
118
|
+
_serviceBusClient = serviceBusClient;
|
|
119
|
+
_logger = logger;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
|
123
|
+
{
|
|
124
|
+
await using var processor = _serviceBusClient.CreateProcessor(_queueName, new ServiceBusProcessorOptions
|
|
125
|
+
{
|
|
126
|
+
MaxConcurrentCalls = 10,
|
|
127
|
+
AutoCompleteMessages = false,
|
|
128
|
+
PrefetchCount = 20
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
processor.ProcessMessageAsync += ProcessMessageAsync;
|
|
132
|
+
processor.ProcessErrorAsync += ProcessErrorAsync;
|
|
133
|
+
|
|
134
|
+
await processor.StartProcessingAsync(stoppingToken);
|
|
135
|
+
|
|
136
|
+
// Wait until cancellation
|
|
137
|
+
await Task.Delay(Timeout.Infinite, stoppingToken);
|
|
138
|
+
|
|
139
|
+
await processor.StopProcessingAsync();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private async Task ProcessMessageAsync(ProcessMessageEventArgs args)
|
|
143
|
+
{
|
|
144
|
+
var body = args.Message.Body.ToString();
|
|
145
|
+
var orderId = args.Message.ApplicationProperties["OrderId"].ToString();
|
|
146
|
+
|
|
147
|
+
try
|
|
148
|
+
{
|
|
149
|
+
_logger.LogInformation("Processing order {OrderId}", orderId);
|
|
150
|
+
|
|
151
|
+
// Process order
|
|
152
|
+
await ProcessOrderAsync(body);
|
|
153
|
+
|
|
154
|
+
// Complete the message
|
|
155
|
+
await args.CompleteMessageAsync(args.Message);
|
|
156
|
+
}
|
|
157
|
+
catch (Exception ex)
|
|
158
|
+
{
|
|
159
|
+
_logger.LogError(ex, "Failed to process order {OrderId}", orderId);
|
|
160
|
+
|
|
161
|
+
// Dead-letter the message
|
|
162
|
+
await args.DeadLetterMessageAsync(args.Message, "ProcessingFailed", ex.Message);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private Task ProcessErrorAsync(ProcessErrorEventArgs args)
|
|
167
|
+
{
|
|
168
|
+
_logger.LogError(args.Exception, "Service Bus error: {ErrorSource}", args.ErrorSource);
|
|
169
|
+
return Task.CompletedTask;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private Task ProcessOrderAsync(string orderData)
|
|
173
|
+
{
|
|
174
|
+
// Implementation...
|
|
175
|
+
return Task.CompletedTask;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Topic/Subscription Patterns
|
|
183
|
+
|
|
184
|
+
### Send to Topic
|
|
185
|
+
|
|
186
|
+
```csharp
|
|
187
|
+
public class EventPublisherService
|
|
188
|
+
{
|
|
189
|
+
private readonly ServiceBusClient _serviceBusClient;
|
|
190
|
+
private readonly string _topicName = "order-events";
|
|
191
|
+
|
|
192
|
+
public EventPublisherService(ServiceBusClient serviceBusClient)
|
|
193
|
+
{
|
|
194
|
+
_serviceBusClient = serviceBusClient;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
public async Task PublishOrderCreatedAsync(string orderId)
|
|
198
|
+
{
|
|
199
|
+
await using var sender = _serviceBusClient.CreateSender(_topicName);
|
|
200
|
+
|
|
201
|
+
var message = new ServiceBusMessage(JsonSerializer.Serialize(new
|
|
202
|
+
{
|
|
203
|
+
OrderId = orderId,
|
|
204
|
+
Timestamp = DateTime.UtcNow
|
|
205
|
+
}))
|
|
206
|
+
{
|
|
207
|
+
Subject = "OrderCreated",
|
|
208
|
+
CorrelationId = Guid.NewGuid().ToString()
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
await sender.SendMessageAsync(message);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Subscribe to Topic
|
|
217
|
+
|
|
218
|
+
```csharp
|
|
219
|
+
public class NotificationSubscriberService : BackgroundService
|
|
220
|
+
{
|
|
221
|
+
private readonly ServiceBusClient _serviceBusClient;
|
|
222
|
+
private readonly string _topicName = "order-events";
|
|
223
|
+
private readonly string _subscriptionName = "notification-service";
|
|
224
|
+
|
|
225
|
+
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
|
226
|
+
{
|
|
227
|
+
await using var processor = _serviceBusClient.CreateProcessor(
|
|
228
|
+
_topicName,
|
|
229
|
+
_subscriptionName,
|
|
230
|
+
new ServiceBusProcessorOptions
|
|
231
|
+
{
|
|
232
|
+
MaxConcurrentCalls = 5,
|
|
233
|
+
AutoCompleteMessages = false
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
processor.ProcessMessageAsync += async args =>
|
|
237
|
+
{
|
|
238
|
+
if (args.Message.Subject == "OrderCreated")
|
|
239
|
+
{
|
|
240
|
+
await SendNotificationAsync(args.Message.Body.ToString());
|
|
241
|
+
await args.CompleteMessageAsync(args.Message);
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
processor.ProcessErrorAsync += args =>
|
|
246
|
+
{
|
|
247
|
+
_logger.LogError(args.Exception, "Notification subscriber error");
|
|
248
|
+
return Task.CompletedTask;
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
await processor.StartProcessingAsync(stoppingToken);
|
|
252
|
+
await Task.Delay(Timeout.Infinite, stoppingToken);
|
|
253
|
+
await processor.StopProcessingAsync();
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
private Task SendNotificationAsync(string orderData)
|
|
257
|
+
{
|
|
258
|
+
// Send notification...
|
|
259
|
+
return Task.CompletedTask;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
## Message Sessions (Ordered Processing)
|
|
267
|
+
|
|
268
|
+
```csharp
|
|
269
|
+
public async Task SendSessionMessageAsync(string sessionId, string message)
|
|
270
|
+
{
|
|
271
|
+
await using var sender = _serviceBusClient.CreateSender("orders");
|
|
272
|
+
|
|
273
|
+
var serviceBusMessage = new ServiceBusMessage(message)
|
|
274
|
+
{
|
|
275
|
+
SessionId = sessionId // All messages with same SessionId processed in order
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
await sender.SendMessageAsync(serviceBusMessage);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Process with sessions
|
|
282
|
+
var processor = _serviceBusClient.CreateSessionProcessor("orders", new ServiceBusSessionProcessorOptions
|
|
283
|
+
{
|
|
284
|
+
MaxConcurrentSessions = 5,
|
|
285
|
+
SessionIdleTimeout = TimeSpan.FromMinutes(1)
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
processor.ProcessMessageAsync += async args =>
|
|
289
|
+
{
|
|
290
|
+
_logger.LogInformation("Processing message in session {SessionId}", args.SessionId);
|
|
291
|
+
await ProcessMessageAsync(args.Message.Body.ToString());
|
|
292
|
+
await args.CompleteMessageAsync(args.Message);
|
|
293
|
+
};
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
## Dead-Letter Queue Processing
|
|
299
|
+
|
|
300
|
+
```csharp
|
|
301
|
+
public async Task ReprocessDeadLetterMessagesAsync()
|
|
302
|
+
{
|
|
303
|
+
var deadLetterQueuePath = $"orders/$DeadLetterQueue";
|
|
304
|
+
|
|
305
|
+
await using var receiver = _serviceBusClient.CreateReceiver(deadLetterQueuePath);
|
|
306
|
+
|
|
307
|
+
var messages = await receiver.ReceiveMessagesAsync(maxMessages: 100);
|
|
308
|
+
|
|
309
|
+
foreach (var message in messages)
|
|
310
|
+
{
|
|
311
|
+
try
|
|
312
|
+
{
|
|
313
|
+
// Attempt to reprocess
|
|
314
|
+
await ProcessMessageAsync(message.Body.ToString());
|
|
315
|
+
|
|
316
|
+
// Complete from dead-letter queue
|
|
317
|
+
await receiver.CompleteMessageAsync(message);
|
|
318
|
+
|
|
319
|
+
// Optionally, resend to main queue
|
|
320
|
+
await using var sender = _serviceBusClient.CreateSender("orders");
|
|
321
|
+
await sender.SendMessageAsync(new ServiceBusMessage(message.Body)
|
|
322
|
+
{
|
|
323
|
+
MessageId = message.MessageId
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
catch (Exception ex)
|
|
327
|
+
{
|
|
328
|
+
_logger.LogError(ex, "Failed to reprocess dead-letter message {MessageId}", message.MessageId);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
## Bicep Configuration
|
|
337
|
+
|
|
338
|
+
```bicep
|
|
339
|
+
resource serviceBusNamespace 'Microsoft.ServiceBus/namespaces@2022-10-01-preview' = {
|
|
340
|
+
name: serviceBusNamespaceName
|
|
341
|
+
location: location
|
|
342
|
+
sku: {
|
|
343
|
+
name: 'Standard'
|
|
344
|
+
tier: 'Standard'
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
resource queue 'Microsoft.ServiceBus/namespaces/queues@2022-10-01-preview' = {
|
|
349
|
+
parent: serviceBusNamespace
|
|
350
|
+
name: 'orders'
|
|
351
|
+
properties: {
|
|
352
|
+
maxDeliveryCount: 10
|
|
353
|
+
deadLetteringOnMessageExpiration: true
|
|
354
|
+
defaultMessageTimeToLive: 'P14D' // 14 days
|
|
355
|
+
lockDuration: 'PT5M' // 5 minutes
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
resource topic 'Microsoft.ServiceBus/namespaces/topics@2022-10-01-preview' = {
|
|
360
|
+
parent: serviceBusNamespace
|
|
361
|
+
name: 'order-events'
|
|
362
|
+
properties: {
|
|
363
|
+
defaultMessageTimeToLive: 'P7D'
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
resource subscription 'Microsoft.ServiceBus/namespaces/topics/subscriptions@2022-10-01-preview' = {
|
|
368
|
+
parent: topic
|
|
369
|
+
name: 'notification-service'
|
|
370
|
+
properties: {
|
|
371
|
+
deadLetteringOnMessageExpiration: true
|
|
372
|
+
maxDeliveryCount: 5
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Grant Container App send/receive access
|
|
377
|
+
resource serviceBusRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
|
|
378
|
+
scope: serviceBusNamespace
|
|
379
|
+
name: guid(serviceBusNamespace.id, principalId, 'Azure Service Bus Data Owner')
|
|
380
|
+
properties: {
|
|
381
|
+
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '090c5cfd-751d-490a-894a-3ce6f1109419')
|
|
382
|
+
principalId: principalId
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
---
|
|
388
|
+
|
|
389
|
+
## Best Practices
|
|
390
|
+
|
|
391
|
+
### Idempotent Message Handling
|
|
392
|
+
|
|
393
|
+
```csharp
|
|
394
|
+
private async Task<bool> IsMessageProcessedAsync(string messageId)
|
|
395
|
+
{
|
|
396
|
+
await using var db = await _dbFactory.CreateDbContextAsync();
|
|
397
|
+
return await db.ProcessedMessages.AnyAsync(m => m.MessageId == messageId);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
private async Task ProcessMessageAsync(ProcessMessageEventArgs args)
|
|
401
|
+
{
|
|
402
|
+
if (await IsMessageProcessedAsync(args.Message.MessageId))
|
|
403
|
+
{
|
|
404
|
+
// Already processed, complete and skip
|
|
405
|
+
await args.CompleteMessageAsync(args.Message);
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Process message...
|
|
410
|
+
|
|
411
|
+
// Mark as processed
|
|
412
|
+
await using var db = await _dbFactory.CreateDbContextAsync();
|
|
413
|
+
db.ProcessedMessages.Add(new ProcessedMessage
|
|
414
|
+
{
|
|
415
|
+
MessageId = args.Message.MessageId,
|
|
416
|
+
ProcessedAt = DateTime.UtcNow
|
|
417
|
+
});
|
|
418
|
+
await db.SaveChangesAsync();
|
|
419
|
+
|
|
420
|
+
await args.CompleteMessageAsync(args.Message);
|
|
421
|
+
}
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
### Exponential Backoff on Errors
|
|
425
|
+
|
|
426
|
+
```csharp
|
|
427
|
+
private async Task ProcessWithRetryAsync(ServiceBusReceivedMessage message)
|
|
428
|
+
{
|
|
429
|
+
int maxRetries = 3;
|
|
430
|
+
int attempt = 0;
|
|
431
|
+
|
|
432
|
+
while (attempt < maxRetries)
|
|
433
|
+
{
|
|
434
|
+
try
|
|
435
|
+
{
|
|
436
|
+
await ProcessMessageAsync(message.Body.ToString());
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
catch (Exception ex)
|
|
440
|
+
{
|
|
441
|
+
attempt++;
|
|
442
|
+
if (attempt >= maxRetries) throw;
|
|
443
|
+
|
|
444
|
+
await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt)));
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
---
|
|
451
|
+
|
|
452
|
+
## References
|
|
453
|
+
|
|
454
|
+
- [Azure Service Bus Documentation](https://learn.microsoft.com/azure/service-bus-messaging/)
|
|
455
|
+
- [Service Bus .NET SDK](https://learn.microsoft.com/dotnet/api/overview/azure/messaging.servicebus-readme)
|
|
456
|
+
|
|
457
|
+
---
|
|
458
|
+
|
|
459
|
+
*MORPH-SPEC by Polymorphism Tech*
|