Skip to content

Commit

Permalink
Add .NET Aspire support ♥️
Browse files Browse the repository at this point in the history
  • Loading branch information
fredimachado committed May 16, 2024
1 parent 0a10bb8 commit a0ffa07
Show file tree
Hide file tree
Showing 19 changed files with 365 additions and 0 deletions.
10 changes: 10 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Aspire.Hosting" Version="8.0.0-preview.7.24251.11" />
<PackageVersion Include="Aspire.Hosting.AppHost" Version="8.0.0-preview.7.24251.11" />
<PackageVersion Include="Aspire.Hosting.RabbitMQ" Version="8.0.0-preview.7.24251.11" />
<PackageVersion Include="Microsoft.Extensions.Http.Resilience" Version="8.3.0" />
<PackageVersion Include="Microsoft.Extensions.ServiceDiscovery" Version="8.0.0-preview.7.24251.11" />
<PackageVersion Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.8.1" />
<PackageVersion Include="OpenTelemetry.Extensions.Hosting" Version="1.8.1" />
<PackageVersion Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.8.1" />
<PackageVersion Include="OpenTelemetry.Instrumentation.Http" Version="1.8.1" />
<PackageVersion Include="OpenTelemetry.Instrumentation.Runtime" Version="1.8.0" />
<PackageVersion Include="AntDesign" Version="0.15.5" />
<PackageVersion Include="Ardalis.GuardClauses" Version="4.5.0" />
<PackageVersion Include="coverlet.collector" Version="6.0.2" />
Expand Down
23 changes: 23 additions & 0 deletions NCafe.sln
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{49528FFA
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NCafe.Common.Tests", "src\Common\NCafe.Common.Tests\NCafe.Common.Tests.csproj", "{0203B146-09CB-4350-8375-9759F132BDC2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NCafe.AppHost", "src\Aspire\NCafe.AppHost\NCafe.AppHost.csproj", "{ED16805D-2DEA-4F88-8BCD-E3460AE89CAF}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NCafe.ServiceDefaults", "src\Aspire\NCafe.ServiceDefaults\NCafe.ServiceDefaults.csproj", "{A7B56906-8702-48CD-9CB0-2274802E54B2}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Aspire", "Aspire", "{81ABD551-6547-47EF-8F01-160466F018D2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspire.Hosting.EventStore", "src\Aspire\Aspire.Hosting.EventStore\Aspire.Hosting.EventStore.csproj", "{1F5E7F20-3972-4E73-86A9-83F20F6EE8C7}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -139,6 +147,18 @@ Global
{0203B146-09CB-4350-8375-9759F132BDC2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0203B146-09CB-4350-8375-9759F132BDC2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0203B146-09CB-4350-8375-9759F132BDC2}.Release|Any CPU.Build.0 = Release|Any CPU
{ED16805D-2DEA-4F88-8BCD-E3460AE89CAF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{ED16805D-2DEA-4F88-8BCD-E3460AE89CAF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ED16805D-2DEA-4F88-8BCD-E3460AE89CAF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ED16805D-2DEA-4F88-8BCD-E3460AE89CAF}.Release|Any CPU.Build.0 = Release|Any CPU
{A7B56906-8702-48CD-9CB0-2274802E54B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A7B56906-8702-48CD-9CB0-2274802E54B2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A7B56906-8702-48CD-9CB0-2274802E54B2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A7B56906-8702-48CD-9CB0-2274802E54B2}.Release|Any CPU.Build.0 = Release|Any CPU
{1F5E7F20-3972-4E73-86A9-83F20F6EE8C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1F5E7F20-3972-4E73-86A9-83F20F6EE8C7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1F5E7F20-3972-4E73-86A9-83F20F6EE8C7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1F5E7F20-3972-4E73-86A9-83F20F6EE8C7}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -164,6 +184,9 @@ Global
{98734B27-556B-40FB-8E1F-86FDCC4DA1A1} = {728C5CE5-C0F8-45B9-B9ED-E6AA7C219095}
{49528FFA-842E-4853-9B8F-77BCE9970D11} = {728C5CE5-C0F8-45B9-B9ED-E6AA7C219095}
{0203B146-09CB-4350-8375-9759F132BDC2} = {49528FFA-842E-4853-9B8F-77BCE9970D11}
{ED16805D-2DEA-4F88-8BCD-E3460AE89CAF} = {81ABD551-6547-47EF-8F01-160466F018D2}
{A7B56906-8702-48CD-9CB0-2274802E54B2} = {81ABD551-6547-47EF-8F01-160466F018D2}
{1F5E7F20-3972-4E73-86A9-83F20F6EE8C7} = {81ABD551-6547-47EF-8F01-160466F018D2}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {F0136BF1-D5F3-4E83-8D23-E3F43534FBB5}
Expand Down
1 change: 1 addition & 0 deletions src/Admin/NCafe.Admin.Api/NCafe.Admin.Api.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\Aspire\NCafe.ServiceDefaults\NCafe.ServiceDefaults.csproj" />
<ProjectReference Include="..\..\Common\NCafe.Infrastructure\NCafe.Infrastructure.csproj" />
<ProjectReference Include="..\NCafe.Admin.Domain\NCafe.Admin.Domain.csproj" />
</ItemGroup>
Expand Down
4 changes: 4 additions & 0 deletions src/Admin/NCafe.Admin.Api/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

var builder = WebApplication.CreateBuilder(args);

builder.AddServiceDefaults();

// Add services to the container.
builder.Services.AddEventStoreRepository(builder.Configuration)
.AddCommandHandlers<CreateProduct>()
Expand Down Expand Up @@ -35,6 +37,8 @@

var app = builder.Build();

app.MapDefaultEndpoints();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Description>EventStore support for .NET Aspire.</Description>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Aspire.Hosting" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.EventStore;

namespace Aspire.Hosting;

public static class EventStoreBuilderExtensions
{
/// <summary>
/// Adds an EventStore resource to the application model. A container is used for local development.
/// This package defaults to the 20.10.0-buster-slim tag of the eventstore container image.
/// </summary>
/// <param name="builder">The <see cref="IDistributedApplicationBuilder"/>.</param>
/// <param name="name">The name of the resource. This name will be used as the connection string name when referenced in a dependency.</param>
/// <param name="httpPort">The port on which the EventStore HTTP endpoint will be exposed.</param>
/// <param name="tcpPort">The port on which the EventStore TCP endpoint will be exposed.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<EventStoreResource> AddEventStore(
this IDistributedApplicationBuilder builder, string name, int? httpPort = null, int? tcpPort = null)
{
var eventStoreContainer = new EventStoreResource(name);

return builder
.AddResource(eventStoreContainer)
.WithEndpoint(port: tcpPort, targetPort: EventStoreResource.DefaultTcpPort, name: EventStoreResource.TcpEndpointName)
.WithHttpEndpoint(port: httpPort, targetPort: EventStoreResource.DefaultHttpPort, name: EventStoreResource.HttpEndpointName)
.WithImage(EventStoreContainerImageTags.Image, EventStoreContainerImageTags.Tag)
.WithImageRegistry(EventStoreContainerImageTags.Registry)
.WithEnvironment(context => ConfigureEventStoreContainer(context, eventStoreContainer));
}

private static void ConfigureEventStoreContainer(EnvironmentCallbackContext context, EventStoreResource resource)
{
context.EnvironmentVariables.Add("EVENTSTORE_CLUSTER_SIZE", "1");
context.EnvironmentVariables.Add("EVENTSTORE_RUN_PROJECTIONS", "All");
context.EnvironmentVariables.Add("EVENTSTORE_START_STANDARD_PROJECTIONS", "true");
context.EnvironmentVariables.Add("EVENTSTORE_EXT_TCP_PORT", EventStoreResource.DefaultTcpPort.ToString());
context.EnvironmentVariables.Add("EVENTSTORE_HTTP_PORT", EventStoreResource.DefaultHttpPort.ToString());
context.EnvironmentVariables.Add("EVENTSTORE_INSECURE", "true");
context.EnvironmentVariables.Add("EVENTSTORE_DEV", "true");
context.EnvironmentVariables.Add("EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP", "true");
context.EnvironmentVariables.Add("EVENTSTORE_ENABLE_EXTERNAL_TCP", "true");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Aspire.Hosting.EventStore;

internal static class EventStoreContainerImageTags
{
public const string Registry = "docker.io";
public const string Image = "eventstore/eventstore";
public const string Tag = "20.10.0-buster-slim";
}
27 changes: 27 additions & 0 deletions src/Aspire/Aspire.Hosting.EventStore/EventStoreResource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
namespace Aspire.Hosting.ApplicationModel;

/// <summary>
/// A resource that represents an EventStore container.
/// </summary>
/// <param name="name">The name of the resource.</param>
public class EventStoreResource(string name) : ContainerResource(name), IResourceWithConnectionString
{
internal const string HttpEndpointName = "http";
internal const string TcpEndpointName = "tcp";
internal const int DefaultHttpPort = 2113;
internal const int DefaultTcpPort = 1113;

private EndpointReference? _primaryEndpoint;

/// <summary>
/// Gets the primary endpoint for the EventStore server.
/// </summary>
public EndpointReference PrimaryEndpoint => _primaryEndpoint ??= new(this, HttpEndpointName);

/// <summary>
/// Gets the connection string for the EventStore server.
/// </summary>
public ReferenceExpression ConnectionStringExpression =>
ReferenceExpression.Create(
$"esdb://{PrimaryEndpoint.Property(EndpointProperty.Host)}:{PrimaryEndpoint.Property(EndpointProperty.Port)}?tls=false");
}
24 changes: 24 additions & 0 deletions src/Aspire/NCafe.AppHost/NCafe.AppHost.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsAspireHost>true</IsAspireHost>
<UserSecretsId>6e4e7f77-0985-41fb-8ac7-7a26000ea41d</UserSecretsId>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Aspire.Hosting.AppHost" />
<PackageReference Include="Aspire.Hosting.RabbitMQ" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\Admin\NCafe.Admin.Api\NCafe.Admin.Api.csproj" />
<ProjectReference Include="..\..\Barista\NCafe.Barista.Api\NCafe.Barista.Api.csproj" />
<ProjectReference Include="..\..\Cashier\NCafe.Cashier.Api\NCafe.Cashier.Api.csproj" />
<ProjectReference Include="..\..\UI\NCafe.Web\NCafe.Web.csproj" />
<ProjectReference Include="..\Aspire.Hosting.EventStore\Aspire.Hosting.EventStore.csproj" IsAspireProjectResource="false" />
</ItemGroup>

</Project>
20 changes: 20 additions & 0 deletions src/Aspire/NCafe.AppHost/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
var builder = DistributedApplication.CreateBuilder(args);

var eventStore = builder.AddEventStore("eventstore");
var rabbitMq = builder.AddRabbitMQ("rabbitmq");

var adminProject = builder.AddProject<Projects.NCafe_Admin_Api>("admin-api")
.WithReference(eventStore);

var baristaProject = builder.AddProject<Projects.NCafe_Barista_Api>("barista-api")
.WithReference(eventStore)
.WithReference(rabbitMq);

var cashierProject = builder.AddProject<Projects.NCafe_Cashier_Api>("cashier-api")
.WithReference(eventStore)
.WithReference(rabbitMq);

var webUiProject = builder.AddProject<Projects.NCafe_Web>("web-ui")
.WithExternalHttpEndpoints();

builder.Build().Run();
29 changes: 29 additions & 0 deletions src/Aspire/NCafe.AppHost/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:17129;http://localhost:15064",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"DOTNET_ENVIRONMENT": "Development",
"DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21182",
"DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22264"
}
},
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:15064",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"DOTNET_ENVIRONMENT": "Development",
"DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19224",
"DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20043"
}
}
}
}
8 changes: 8 additions & 0 deletions src/Aspire/NCafe.AppHost/appsettings.Development.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
9 changes: 9 additions & 0 deletions src/Aspire/NCafe.AppHost/appsettings.json
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"
}
}
}
115 changes: 115 additions & 0 deletions src/Aspire/NCafe.ServiceDefaults/Extensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.Extensions.Logging;
using OpenTelemetry;
using OpenTelemetry.Metrics;
using OpenTelemetry.Trace;

namespace Microsoft.Extensions.Hosting;

public static class Extensions
{
public static IHostApplicationBuilder AddServiceDefaults(this IHostApplicationBuilder builder)
{
builder.ConfigureOpenTelemetry();

builder.AddDefaultHealthChecks();

builder.Services.AddServiceDiscovery();

builder.Services.ConfigureHttpClientDefaults(http =>
{
// Turn on resilience by default
http.AddStandardResilienceHandler();
// Turn on service discovery by default
http.AddServiceDiscovery();
});

return builder;
}

public static IHostApplicationBuilder ConfigureOpenTelemetry(this IHostApplicationBuilder builder)
{
builder.Logging.AddOpenTelemetry(logging =>
{
logging.IncludeFormattedMessage = true;
logging.IncludeScopes = true;
});

builder.Services.AddOpenTelemetry()
.WithMetrics(metrics =>
{
metrics.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddRuntimeInstrumentation();
})
.WithTracing(tracing =>
{
tracing.AddAspNetCoreInstrumentation()
// Uncomment the following line to enable gRPC instrumentation (requires the OpenTelemetry.Instrumentation.GrpcNetClient package)
//.AddGrpcClientInstrumentation()
.AddHttpClientInstrumentation();
});

builder.AddOpenTelemetryExporters();

return builder;
}

private static IHostApplicationBuilder AddOpenTelemetryExporters(this IHostApplicationBuilder builder)
{
var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]);

if (useOtlpExporter)
{
builder.Services.AddOpenTelemetry().UseOtlpExporter();
}

// Uncomment the following lines to enable the Prometheus exporter (requires the OpenTelemetry.Exporter.Prometheus.AspNetCore package)
// builder.Services.AddOpenTelemetry()
// .WithMetrics(metrics => metrics.AddPrometheusExporter());

// Uncomment the following lines to enable the Azure Monitor exporter (requires the Azure.Monitor.OpenTelemetry.AspNetCore package)
//if (!string.IsNullOrEmpty(builder.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"]))
//{
// builder.Services.AddOpenTelemetry()
// .UseAzureMonitor();
//}

return builder;
}

public static IHostApplicationBuilder AddDefaultHealthChecks(this IHostApplicationBuilder builder)
{
builder.Services.AddHealthChecks()
// Add a default liveness check to ensure app is responsive
.AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]);

return builder;
}

public static WebApplication MapDefaultEndpoints(this WebApplication app)
{
// Adding health checks endpoints to applications in non-development environments has security implications.
// See https://aka.ms/dotnet/aspire/healthchecks for details before enabling these endpoints in non-development environments.
if (app.Environment.IsDevelopment())
{
// All health checks must pass for app to be considered ready to accept traffic after starting
app.MapHealthChecks("/health");

// Only health checks tagged with the "live" tag must pass for app to be considered alive
app.MapHealthChecks("/alive", new HealthCheckOptions
{
Predicate = r => r.Tags.Contains("live")
});

// Uncomment the following line to enable the Prometheus endpoint (requires the OpenTelemetry.Exporter.Prometheus.AspNetCore package)
// app.MapPrometheusScrapingEndpoint();
}

return app;
}
}
Loading

0 comments on commit a0ffa07

Please sign in to comment.