Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Service Bus emulator support #6737

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions Aspire.sln
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DaprServiceC", "playground\
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Dashboard", "Dashboard", "{830F7CA9-8E51-4D62-832F-91F53F85B7AE}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AzureServiceBus", "AzureServiceBus", "{D2938171-1DBB-4E8D-AF16-97F75F1AE6DE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceBusWorker", "playground\AzureServiceBus\ServiceBusWorker\ServiceBusWorker.csproj", "{162F0B66-E88F-4735-8CE0-BE8950F74CC6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceBus.AppHost", "playground\AzureServiceBus\ServiceBus.AppHost\ServiceBus.AppHost.csproj", "{A7EC9111-F3CC-46E8-B95E-3768481D67B4}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -1653,6 +1659,14 @@ Global
{B26653B9-439E-4850-A7F8-43C6E5121952}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B26653B9-439E-4850-A7F8-43C6E5121952}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B26653B9-439E-4850-A7F8-43C6E5121952}.Release|Any CPU.Build.0 = Release|Any CPU
{162F0B66-E88F-4735-8CE0-BE8950F74CC6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{162F0B66-E88F-4735-8CE0-BE8950F74CC6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{162F0B66-E88F-4735-8CE0-BE8950F74CC6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{162F0B66-E88F-4735-8CE0-BE8950F74CC6}.Release|Any CPU.Build.0 = Release|Any CPU
{A7EC9111-F3CC-46E8-B95E-3768481D67B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A7EC9111-F3CC-46E8-B95E-3768481D67B4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A7EC9111-F3CC-46E8-B95E-3768481D67B4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A7EC9111-F3CC-46E8-B95E-3768481D67B4}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -1956,6 +1970,9 @@ Global
{042DD8C6-A26C-4B06-80A1-FE7F8659C5BC} = {B7345F72-712F-436C-AE18-CAF7CDD4A990}
{B26653B9-439E-4850-A7F8-43C6E5121952} = {57A42144-739E-49A7-BADB-BB8F5F20FA17}
{830F7CA9-8E51-4D62-832F-91F53F85B7AE} = {4981B3A5-4AFD-4191-BF7D-8692D9783D60}
{D2938171-1DBB-4E8D-AF16-97F75F1AE6DE} = {D173887B-AF42-4576-B9C1-96B9E9B3D9C0}
{162F0B66-E88F-4735-8CE0-BE8950F74CC6} = {D2938171-1DBB-4E8D-AF16-97F75F1AE6DE}
{A7EC9111-F3CC-46E8-B95E-3768481D67B4} = {D2938171-1DBB-4E8D-AF16-97F75F1AE6DE}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {6DCEDFEC-988E-4CB3-B45B-191EB5086E0C}
Expand Down
62 changes: 62 additions & 0 deletions playground/AzureServiceBus/ServiceBus.AppHost/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using System.Text.Json.Nodes;

var builder = DistributedApplication.CreateBuilder(args);

var serviceBus = builder.AddAzureServiceBus("sbemulator");

serviceBus
.AddQueue("myQueue", queue =>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to use a callback API pattern here? Why doesn't .AddQueue return the Queue, and then you can modify it as you want?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@eerhardt In this case we would need to return an object that combines behavior of IResourceBuilder<AzureServiceBusResource> with what you are describing so that we could either modify the specified queue or start to specify a different queue or topic?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(full disclosure, I'm not a super fan of fluent APIs)

If you want to start to specify a different queue, you would start a new code line.

var serviceBus = builder.AddAzureServiceBus("sbemulator");

var queue1 = serviceBus.AddQueue("queue1");
// set properties on queue1

var queue2 = serviceBus.AddQueue("queue2");
// set properties on queue2

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why doesn't .AddQueue return the Queue, and then you can modify it as you want?

This would be a breaking change. Is that something we'd want? But we could then reference the resources when building the model, or do topic.AddSubscription("sub.1")

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be a breaking change. Is that something we'd want?

Any way we could make it without a breaking change? A new method name? AddServiceBusQueue?

cc @mitchdenny @davidfowl - thoughts on using callbacks in the API this way?

{
queue.Name = "queue.1";
queue.DeadLetteringOnMessageExpiration = false;
queue.DefaultMessageTimeToLive = TimeSpan.FromHours(1);
queue.DuplicateDetectionHistoryTimeWindow = TimeSpan.FromSeconds(20);
queue.ForwardDeadLetteredMessagesTo = "";
queue.LockDuration = TimeSpan.FromMinutes(1);
queue.MaxDeliveryCount = 10;
queue.RequiresDuplicateDetection = false;
queue.RequiresSession = false;
})
//.AddTopic("myTopic", topic =>
//{
// topic.Name = "topic.1";
// topic.DefaultMessageTimeToLive = TimeSpan.FromHours(1);
// topic.DuplicateDetectionHistoryTimeWindow = TimeSpan.FromSeconds(20);
// topic.RequiresDuplicateDetection = false;
//})
//.AddSubscription("myTopic", "mySubscription", sub =>
//{
// sub.Name = "subscription.1";
// sub.DeadLetteringOnMessageExpiration = false;
// sub.DefaultMessageTimeToLive = TimeSpan.FromHours(1);
// sub.LockDuration = TimeSpan.FromMinutes(1);
// sub.MaxDeliveryCount = 10;
// sub.ForwardDeadLetteredMessagesTo = "";
// sub.RequiresSession = false;
//})
//.AddRule("myTopic", "mySubscription", "myRule", rule =>
//{
// rule.Name = "app-prop-filter-1";
// rule.CorrelationFilter = new()
// {
// ContentType = "application/text",
// CorrelationId = "id1",
// Subject = "subject1",
// MessageId = "msgid1",
// ReplyTo = "someQueue",
// ReplyToSessionId = "sessionId",
// SessionId = "session1",
// SendTo = "xyz"
// };
//})
;

serviceBus.RunAsEmulator(configure => configure.ConfigureJson(document =>
{
document["UserConfig"]!["Logging"] = new JsonObject { ["Type"] = "Console" };
}));

builder.AddProject<Projects.ServiceBusWorker>("worker")
.WithReference(serviceBus).WaitFor(serviceBus);

builder.Build().Run();
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",

"profiles": {
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:15887;http://localhost:15888",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"DOTNET_ENVIRONMENT": "Development",
"DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:16175",
"DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:17037",
"DOTNET_ASPIRE_SHOW_DASHBOARD_RESOURCES": "true"
}
},
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:15888",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"DOTNET_ENVIRONMENT": "Development",
"DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:16175",
"DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:17038",
"DOTNET_ASPIRE_SHOW_DASHBOARD_RESOURCES": "true",
"ASPIRE_ALLOW_UNSECURED_TRANSPORT": "true"
}
},
"generate-manifest": {
"commandName": "Project",
"launchBrowser": true,
"dotnetRunMessages": true,
"commandLineArgs": "--publisher manifest --output-path aspire-manifest.json",
"applicationUrl": "http://localhost:15888",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"DOTNET_ENVIRONMENT": "Development",
"DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:16175"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">

sebastienros marked this conversation as resolved.
Show resolved Hide resolved
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>$(DefaultTargetFramework)</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsAspireHost>true</IsAspireHost>
<UserSecretsId>c12f723f-2545-4f8f-8c3b-fb7bdeadbd55</UserSecretsId>
</PropertyGroup>

<ItemGroup>
<AspireProjectOrPackageReference Include="Aspire.Hosting.AppHost" />
<AspireProjectOrPackageReference Include="Aspire.Hosting.Azure.ServiceBus" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\ServiceBusWorker\ServiceBusWorker.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Aspire.Hosting.Dcp": "Warning"
}
}
}
44 changes: 44 additions & 0 deletions playground/AzureServiceBus/ServiceBusWorker/Consumer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using System.Text;
using Azure.Messaging.ServiceBus;

namespace ServiceBusWorker;

internal sealed class Consumer(ServiceBusClient client, ILogger<Consumer> logger) : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{
var processor = client.CreateProcessor("queue.1", new ServiceBusProcessorOptions
{
AutoCompleteMessages = true,
MaxConcurrentCalls = 1, // Process one message at a time
});

// Whenever a message is available on the queue
processor.ProcessMessageAsync += MessageHandler;

processor.ProcessErrorAsync += ErrorHandler;

await processor.StartProcessingAsync(cancellationToken);
}

private static Task MessageHandler(ProcessMessageEventArgs args)
{
// Process the message
Console.WriteLine($"Received message: {Encoding.UTF8.GetString(args.Message.Body)}");

return Task.CompletedTask;
}

private Task ErrorHandler(ProcessErrorEventArgs args)
{
logger.LogError(args.Exception, "Error processing message");

return Task.CompletedTask;
}

public override Task StopAsync(CancellationToken cancellationToken)
{
logger.LogInformation("Stopping consumer...");
return Task.CompletedTask;
}
}
30 changes: 30 additions & 0 deletions playground/AzureServiceBus/ServiceBusWorker/Producer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using Azure.Messaging.ServiceBus;

namespace ServiceBusWorker;

internal sealed class Producer(ServiceBusClient client, ILogger<Producer> logger) : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{
logger.LogInformation("Starting producer...");

await using var sender = client.CreateSender("queue.1");

var periodicTimer = new PeriodicTimer(TimeSpan.FromSeconds(5));

while (!cancellationToken.IsCancellationRequested)
{
await periodicTimer.WaitForNextTickAsync(cancellationToken);

await sender.SendMessageAsync(new ServiceBusMessage($"Hello, World! It's {DateTime.Now} here."), cancellationToken);
}
}

public override Task StopAsync(CancellationToken cancellationToken)
{
logger.LogInformation("Stopping producer...");
return Task.CompletedTask;
}
}
19 changes: 19 additions & 0 deletions playground/AzureServiceBus/ServiceBusWorker/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using ServiceBusWorker;

var builder = Host.CreateApplicationBuilder(args);

builder.AddServiceDefaults();

builder.AddAzureServiceBusClient("sbemulator");

Console.WriteLine("ServiceBus producer/consumer test");

builder.Services.AddHostedService<Consumer>();
Console.WriteLine("Starting Service Bus consumer...");

builder.Services.AddHostedService<Producer>();
Console.WriteLine("Starting Service Bus producer...");

var host = builder.Build();

await host.RunAsync();
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"profiles": {
"WorkerService1": {
"commandName": "Project",
"dotnetRunMessages": true,
"environmentVariables": {
"DOTNET_ENVIRONMENT": "Development"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk.Worker">

<PropertyGroup>
<TargetFramework>$(DefaultTargetFramework)</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>9c946452-f682-49a4-9209-29ed63174447</UserSecretsId>
</PropertyGroup>

<ItemGroup>
<AspireProjectOrPackageReference Include="Aspire.Azure.Messaging.ServiceBus" />
<ProjectReference Include="..\..\Playground.ServiceDefaults\Playground.ServiceDefaults.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}
8 changes: 8 additions & 0 deletions playground/AzureServiceBus/ServiceBusWorker/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ namespace Aspire.Hosting.Azure;
/// <param name="innerResource">The inner resource used to store annotations.</param>
public class AzureEventHubsEmulatorResource(AzureEventHubsResource innerResource) : ContainerResource(innerResource.Name), IResource
{
// The path to the emulator configuration file in the container.
internal const string EmulatorConfigJsonPath = "/Eventhubs_Emulator/ConfigFiles/Config.json";

private readonly AzureEventHubsResource _innerResource = innerResource;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,6 @@ public static IResourceBuilder<AzureEventHubsResource> AddAzureEventHubs(
/// <summary>
/// Adds an Azure Event Hubs hub resource to the application model. This resource requires an <see cref="AzureEventHubsResource"/> to be added to the application model.
/// </summary>
/// <remarks>
/// This version of the package defaults to the <inheritdoc cref="EventHubsEmulatorContainerImageTags.Tag"/> tag of the <inheritdoc cref="EventHubsEmulatorContainerImageTags.Registry"/>/<inheritdoc cref="EventHubsEmulatorContainerImageTags.Image"/> container image.
/// </remarks>
/// <param name="builder">The Azure Event Hubs resource builder.</param>
/// <param name="name">The name of the Event Hub.</param>
public static IResourceBuilder<AzureEventHubsResource> AddEventHub(this IResourceBuilder<AzureEventHubsResource> builder, [ResourceName] string name)
Expand All @@ -91,6 +88,9 @@ public static IResourceBuilder<AzureEventHubsResource> AddEventHub(this IResourc
/// <summary>
/// Configures an Azure Event Hubs resource to be emulated. This resource requires an <see cref="AzureEventHubsResource"/> to be added to the application model.
/// </summary>
/// <remarks>
/// This version of the package defaults to the <inheritdoc cref="EventHubsEmulatorContainerImageTags.Tag"/> tag of the <inheritdoc cref="EventHubsEmulatorContainerImageTags.Registry"/>/<inheritdoc cref="EventHubsEmulatorContainerImageTags.Image"/> container image.
/// </remarks>
/// <param name="builder">The Azure Event Hubs resource builder.</param>
/// <param name="configureContainer">Callback that exposes underlying container used for emulation to allow for customization.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns>
Expand Down
Loading
Loading