Skip to content

Commit d36ef3a

Browse files
authored
Add NServiceBus 10 Sample: CosmosDB/Transactions (#7606)
* Refactor InputLoopService into Program.cs for CosmosDB transactions sample - Removed InputLoopService and moved logic back to Program.cs Main method - Updated to use StartAsync/StopAsync pattern with IMessageSession extraction - Maintains compatibility with hosting model while enabling proper console interaction - Removed Console.ReadKey() from Server for containerization compatibility * Add containerization support for CosmosDB transactions sample - Created Dockerfile with multi-stage build for .NET 9 - Added run_sample.sh with expect automation for complete testing - Updated Server to support COSMOS_CONNECTION_STRING environment variable - Fixed Client logging dependency injection for OrderCompletedHandler - Validates all sample scenarios including saga processing and CosmosDB persistence - Tested successfully with real Azure CosmosDB instance * Create CosmosDB_4 version for NServiceBus v10 - Updated to target .NET 10 with preview language version - Upgraded to latest alpha packages: • NServiceBus 10.0.0-alpha.2 • NServiceBus.Extensions.Hosting 4.0.0-alpha.2 • NServiceBus.Persistence.CosmosDB 4.0.0-alpha.2 - Added prerelease.txt marker file - Updated Dockerfile for .NET 10 with multi-stage build - Set DOTNET_SYSTEM_NET_SECURITY_NOREVOCATIONCHECKBYDEFAULT environment variable for .NET 10 preview compatibility - Validated complete sample functionality with containerized testing and real Azure CosmosDB * Apply modern C# language features to CosmosDB_4 sample - Use raw string literals for connection strings - Apply target-typed new expressions for variable declarations - Use collection expressions for Task.WhenAll() calls - All modern C# 12+ features compile and execute properly
1 parent 8558999 commit d36ef3a

27 files changed

+821
-63
lines changed

samples/cosmosdb/transactions/CosmosDB_3/Client/Client.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
<LangVersion>12.0</LangVersion>
66
</PropertyGroup>
77
<ItemGroup>
8+
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.2" />
89
<PackageReference Include="NServiceBus.Extensions.Hosting" Version="3.0.1" />
910
</ItemGroup>
1011
<ItemGroup>

samples/cosmosdb/transactions/CosmosDB_3/Client/InputLoopService.cs

Lines changed: 0 additions & 38 deletions
This file was deleted.
Lines changed: 43 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,57 @@
11
using System;
22
using System.Threading.Tasks;
3-
using Client;
4-
using Microsoft.Extensions.DependencyInjection;
53
using Microsoft.Extensions.Hosting;
4+
using Microsoft.Extensions.DependencyInjection;
5+
using Microsoft.Extensions.Logging;
66
using NServiceBus;
77

88
class Program
99
{
10-
public static async Task Main(string[] args)
10+
static async Task Main()
1111
{
12-
await CreateHostBuilder(args).Build().RunAsync();
13-
}
12+
Console.Title = "Client";
1413

15-
public static IHostBuilder CreateHostBuilder(string[] args) =>
16-
Host.CreateDefaultBuilder(args)
17-
.ConfigureServices((hostContext, services) =>
18-
{
19-
Console.Title = "Server";
20-
services.AddHostedService<InputLoopService>();
14+
var hostBuilder = Host.CreateDefaultBuilder()
15+
.ConfigureServices(services =>
16+
{
17+
services.AddLogging(logging => logging.AddConsole());
18+
})
19+
.UseNServiceBus(context =>
20+
{
21+
var endpointConfiguration = new EndpointConfiguration("Samples.CosmosDB.Transactions.Client");
22+
endpointConfiguration.UsePersistence<LearningPersistence>();
23+
endpointConfiguration.UseTransport(new LearningTransport());
24+
endpointConfiguration.UseSerialization<SystemJsonSerializer>();
25+
return endpointConfiguration;
26+
});
2127

22-
}).UseNServiceBus(x =>
23-
{
24-
Console.Title = "Client";
25-
var endpointConfiguration = new EndpointConfiguration("Samples.CosmosDB.Transactions.Client");
26-
endpointConfiguration.UsePersistence<LearningPersistence>();
27-
endpointConfiguration.UseTransport(new LearningTransport());
28-
endpointConfiguration.UseSerialization<SystemJsonSerializer>();
28+
var host = hostBuilder.Build();
29+
await host.StartAsync();
2930

30-
Console.WriteLine("Press 'S' to send a StartOrder message to the server endpoint");
31+
var messageSession = host.Services.GetRequiredService<IMessageSession>();
3132

32-
Console.WriteLine("Press any other key to exit");
33-
return endpointConfiguration;
34-
});
33+
Console.WriteLine("Press 'S' to send a StartOrder message to the server endpoint");
34+
Console.WriteLine("Press any other key to exit");
3535

36+
while (true)
37+
{
38+
var key = Console.ReadKey();
39+
Console.WriteLine();
40+
41+
var orderId = Guid.NewGuid();
42+
var startOrder = new StartOrder
43+
{
44+
OrderId = orderId
45+
};
46+
if (key.Key == ConsoleKey.S)
47+
{
48+
await messageSession.Send("Samples.CosmosDB.Transactions.Server", startOrder);
49+
Console.WriteLine($"StartOrder Message sent to Server with OrderId {orderId}");
50+
continue;
51+
}
52+
break;
53+
}
54+
55+
await host.StopAsync();
56+
}
3657
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Build this image from the repository root directory:
2+
# podman build -f samples/cosmosdb/transactions/CosmosDB_3/Dockerfile -t cosmosdb-transactions-sample .
3+
#
4+
# Run with your CosmosDB connection string:
5+
# podman run --rm -e COSMOS_CONNECTION_STRING="$COSMOS_CONNECTION_STRING" cosmosdb-transactions-sample
6+
#
7+
# Docker commands (same as above but replace 'podman' with 'docker'):
8+
# docker build -f samples/cosmosdb/transactions/CosmosDB_3/Dockerfile -t cosmosdb-transactions-sample .
9+
# docker run --rm -e COSMOS_CONNECTION_STRING="$COSMOS_CONNECTION_STRING" cosmosdb-transactions-sample
10+
11+
# Build stage
12+
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
13+
WORKDIR /app
14+
15+
# Install expect for automated testing
16+
RUN apt-get update && apt-get install -y expect && rm -rf /var/lib/apt/lists/*
17+
18+
# Copy build configuration files
19+
COPY nuget.config Directory.Build.props BannedSymbols.txt ./
20+
21+
# Copy solution and project files
22+
COPY samples/cosmosdb/transactions/CosmosDB_3/ ./samples/cosmosdb/transactions/CosmosDB_3/
23+
24+
# Restore packages
25+
WORKDIR /app/samples/cosmosdb/transactions/CosmosDB_3
26+
RUN dotnet restore
27+
28+
# Build all projects
29+
RUN dotnet build --no-restore SharedMessages/SharedMessages.csproj
30+
RUN dotnet build --no-restore Client/Client.csproj
31+
RUN dotnet build --no-restore Server/Server.csproj
32+
33+
# Runtime stage
34+
FROM mcr.microsoft.com/dotnet/runtime:9.0 AS runtime
35+
WORKDIR /app
36+
37+
# Install expect and process management tools
38+
RUN apt-get update && apt-get install -y expect procps && rm -rf /var/lib/apt/lists/*
39+
40+
# Create .learningtransport folder for LearningTransport
41+
RUN mkdir -p .learningtransport
42+
43+
# Copy built applications
44+
COPY --from=build /app/samples/cosmosdb/transactions/CosmosDB_3/Client/bin/Debug/net9.0/ ./Client/
45+
COPY --from=build /app/samples/cosmosdb/transactions/CosmosDB_3/Server/bin/Debug/net9.0/ ./Server/
46+
47+
# Copy the startup script
48+
COPY samples/cosmosdb/transactions/CosmosDB_3/run_sample.sh ./
49+
RUN chmod +x run_sample.sh
50+
51+
CMD ["./run_sample.sh"]

samples/cosmosdb/transactions/CosmosDB_3/Server/Program.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ public static IHostBuilder CreateHostBuilder(string[] args) =>
2626
endpointConfiguration.EnableOutbox();
2727

2828
var persistence = endpointConfiguration.UsePersistence<CosmosPersistence>();
29-
var connection = @"AccountEndpoint = https://localhost:8081/;AccountKey=C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";
29+
var connection = Environment.GetEnvironmentVariable("COSMOS_CONNECTION_STRING") ??
30+
@"AccountEndpoint = https://localhost:8081/;AccountKey=C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";
3031
persistence.DatabaseName("Samples.CosmosDB.Transactions");
3132
persistence.CosmosClient(new CosmosClient(connection));
3233
persistence.DefaultContainer("Server", "/OrderId");
@@ -61,8 +62,6 @@ public static IHostBuilder CreateHostBuilder(string[] args) =>
6162

6263
endpointConfiguration.EnableInstallers();
6364

64-
Console.ReadKey();
65-
6665
return endpointConfiguration;
6766
});
6867

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
#!/bin/bash
2+
set -e
3+
4+
echo "Starting CosmosDB Transactions Sample Test..."
5+
6+
# Check for CosmosDB connection string
7+
if [ -z "$COSMOS_CONNECTION_STRING" ]; then
8+
echo "WARNING: COSMOS_CONNECTION_STRING environment variable not set. Using emulator connection string."
9+
fi
10+
11+
# Start server in background
12+
echo "Starting Server..."
13+
dotnet Server/Server.dll > server.log 2>&1 &
14+
SERVER_PID=$!
15+
16+
# Wait for server to initialize
17+
echo "Waiting for server to initialize..."
18+
sleep 5
19+
20+
# Check if server is still running
21+
if ! kill -0 $SERVER_PID 2>/dev/null; then
22+
echo "ERROR: Server failed to start"
23+
cat server.log
24+
exit 1
25+
fi
26+
27+
echo "Server started successfully (PID: $SERVER_PID)"
28+
29+
# Start client and send messages using expect
30+
echo "Starting Client and sending StartOrder messages..."
31+
32+
# Create expect script for client interaction
33+
cat > client_script.exp << 'EOF'
34+
#!/usr/bin/expect -f
35+
set timeout 30
36+
37+
spawn dotnet Client/Client.dll
38+
expect "Press 'S' to send a StartOrder message to the server endpoint"
39+
expect "Press any other key to exit"
40+
41+
# Send first StartOrder message
42+
send "S"
43+
expect "StartOrder Message sent to Server with OrderId"
44+
puts "First StartOrder message sent successfully"
45+
46+
# Wait a moment
47+
sleep 2
48+
49+
# Send second StartOrder message
50+
send "S"
51+
expect "StartOrder Message sent to Server with OrderId"
52+
puts "Second StartOrder message sent successfully"
53+
54+
# Wait for message processing
55+
sleep 3
56+
57+
# Send third StartOrder message
58+
send "S"
59+
expect "StartOrder Message sent to Server with OrderId"
60+
puts "Third StartOrder message sent successfully"
61+
62+
# Wait for processing then exit
63+
sleep 5
64+
send "q"
65+
expect eof
66+
EOF
67+
68+
chmod +x client_script.exp
69+
70+
# Run the client expect script
71+
./client_script.exp > client.log 2>&1 &
72+
CLIENT_PID=$!
73+
74+
# Wait for client to complete
75+
wait $CLIENT_PID
76+
CLIENT_EXIT_CODE=$?
77+
78+
# Allow additional time for saga processing and message completion
79+
echo "Waiting for saga processing to complete..."
80+
sleep 10
81+
82+
# Stop the server
83+
echo "Stopping Server..."
84+
kill $SERVER_PID 2>/dev/null || true
85+
wait $SERVER_PID 2>/dev/null || true
86+
87+
# Analyze results
88+
echo "=== ANALYSIS ==="
89+
90+
echo "=== Server Log ==="
91+
cat server.log
92+
93+
echo "=== Client Log ==="
94+
cat client.log
95+
96+
# Check for expected patterns in logs
97+
SUCCESS=true
98+
99+
echo "=== Validation ==="
100+
101+
# Check that StartOrder messages were received
102+
if grep -q "StartOrder Message sent to Server" client.log; then
103+
echo "✓ StartOrder messages sent successfully"
104+
else
105+
echo "✗ No StartOrder messages found in client log"
106+
SUCCESS=false
107+
fi
108+
109+
# Check that OrderSaga was created and processed
110+
if grep -q "OrderSaga" server.log; then
111+
echo "✓ OrderSaga processing detected"
112+
else
113+
echo "✗ No OrderSaga processing found in server log"
114+
SUCCESS=false
115+
fi
116+
117+
# Check for CosmosDB persistence activity
118+
if grep -q "partition key" server.log; then
119+
echo "✓ CosmosDB partition key extraction working"
120+
else
121+
echo "✗ No CosmosDB partition key activity found"
122+
SUCCESS=false
123+
fi
124+
125+
# Check for timeout/completion events
126+
if grep -q "Saga.*completed\|OrderCompleted" server.log || grep -q "OrderCompleted" client.log; then
127+
echo "✓ Order completion events detected"
128+
else
129+
echo "✗ No order completion events found"
130+
SUCCESS=false
131+
fi
132+
133+
# Final result
134+
if [ "$SUCCESS" = true ]; then
135+
echo "=== SUCCESS: CosmosDB Transactions sample validation passed ==="
136+
exit 0
137+
else
138+
echo "=== FAILURE: CosmosDB Transactions sample validation failed ==="
139+
exit 1
140+
fi
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<TargetFramework>net10.0</TargetFramework>
4+
<OutputType>Exe</OutputType>
5+
<LangVersion>preview</LangVersion>
6+
</PropertyGroup>
7+
<ItemGroup>
8+
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.0-preview.6.25358.103" />
9+
<PackageReference Include="NServiceBus.Extensions.Hosting" Version="4.0.0-alpha.2" />
10+
</ItemGroup>
11+
<ItemGroup>
12+
<ProjectReference Include="..\SharedMessages\SharedMessages.csproj" />
13+
</ItemGroup>
14+
</Project>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using System.Threading.Tasks;
2+
using NServiceBus;
3+
using Microsoft.Extensions.Logging;
4+
5+
public class OrderCompletedHandler (ILogger<OrderCompletedHandler> logger):
6+
IHandleMessages<OrderCompleted>
7+
{
8+
public Task Handle(OrderCompleted message, IMessageHandlerContext context)
9+
{
10+
logger.LogInformation($"Received OrderCompleted for OrderId {message.OrderId}");
11+
return Task.CompletedTask;
12+
}
13+
}

0 commit comments

Comments
 (0)