Skip to content

Commit

Permalink
Upgrade to .NET 9 (#24)
Browse files Browse the repository at this point in the history
* try to upgrade to 9 #14 #13

* .net 9 with AI extensions

* use uuid v7

* add chatcompletion service

* fix pipeline

* fix AI switch for ollama, azure openai

* refactor code and update docs

* tidy docs
  • Loading branch information
thangchung authored Nov 20, 2024
1 parent de87ce0 commit 0fbbd5e
Show file tree
Hide file tree
Showing 49 changed files with 16,773 additions and 192 deletions.
5 changes: 1 addition & 4 deletions .github/workflows/dotnet-code-coverage.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: 8.0.x

- name: Install .NET Aspire workload
run: dotnet workload install aspire
dotnet-version: 9.0.x

- name: Restore dependencies
run: dotnet restore
Expand Down
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project>
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>preview</LangVersion>
Expand Down
59 changes: 37 additions & 22 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,28 @@
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
<AspnetVersion>8.0.6</AspnetVersion>
<MicrosoftExtensionsVersion>8.4.0</MicrosoftExtensionsVersion>
<EfVersion>8.0.8</EfVersion>
<AspireVersion>8.2.1</AspireVersion>
<AspnetVersion>9.0.0</AspnetVersion>
<EfVersion>9.0.0</EfVersion>
<AspireVersion>9.0.0</AspireVersion>
<OpenTelemetryVersion>1.9.0</OpenTelemetryVersion>
<AspirantVersion>0.0.4</AspirantVersion>
<HealthCheckVersion>8.0.1</HealthCheckVersion>
<HealthCheckVersion>8.0.2</HealthCheckVersion>
<AspVersioningVersion>8.1.0</AspVersioningVersion>
<MassTransitVersion>8.2.4</MassTransitVersion>
<MassTransitVersion>8.3.0</MassTransitVersion>
<MartenVersion>7.10.1</MartenVersion>
<AIExtensions>9.0.0-preview.9.24556.5</AIExtensions>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Microsoft.AspNet.WebApi.Core" Version="5.3.0" />
<PackageVersion Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="$(AspnetVersion)" />
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="$(AspnetVersion)" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="$(EfVersion)" PrivateAssets="all" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="$(EfVersion)" />
<PackageVersion Include="Microsoft.OpenApi" Version="1.6.10" />
<PackageVersion Include="Microsoft.Extensions.ApiDescription.Server" Version="$(AspnetVersion)" />
<PackageVersion Include="Microsoft.OpenApi" Version="1.6.17" />
</ItemGroup>
<ItemGroup>
<PackageVersion Include="MediatR" Version="12.4.0" />
<PackageVersion Include="MediatR" Version="12.4.1" />
<PackageVersion Include="FluentValidation.AspNetCore" Version="11.3.0" />
<PackageVersion Include="FluentValidation" Version="11.9.2" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="6.5.0" />
<PackageVersion Include="FluentValidation" Version="11.10.0" />
<PackageVersion Include="Riok.Mapperly" Version="3.5.1" ExcludeAssets="runtime" PrivateAssets="all" />
<PackageVersion Include="MassTransit" Version="$(MassTransitVersion)" />
<PackageVersion Include="MassTransit.RabbitMQ" Version="$(MassTransitVersion)" />
Expand All @@ -47,12 +44,12 @@
<PackageVersion Include="Aspire.Hosting.PostgreSQL" Version="$(AspireVersion)" />
<PackageVersion Include="Aspire.Hosting.RabbitMQ" Version="$(AspireVersion)" />
<PackageVersion Include="Aspire.Hosting.Redis" Version="$(AspireVersion)" />
<PackageVersion Include="Aspirant.Hosting" Version="$(AspirantVersion)" />
<PackageVersion Include="Aspirant.Hosting.Yarp" Version="$(AspirantVersion)" />
<PackageVersion Include="Aspirant.Hosting.Testing" Version="$(AspirantVersion)" />
<PackageVersion Include="WireMock.Net.Aspire" Version="0.0.1-preview-05" />
<PackageVersion Include="Aspire.Hosting.Testing" Version="$(AspireVersion)" />
<PackageVersion Include="Aspire.Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.0-rc.2.24551.3" />
<PackageVersion Include="Aspire.Azure.AI.OpenAI" Version="9.0.0-preview.5.24551.3" />
<PackageVersion Include="WireMock.Net.Aspire" Version="1.6.7" />
<!-- Aspire Custom Extensions -->
<PackageVersion Include="Microsoft.Extensions.Http.Resilience" Version="8.7.0" />
<PackageVersion Include="Microsoft.Extensions.Http.Resilience" Version="$(AspireVersion)" />
<PackageVersion Include="Microsoft.Extensions.ServiceDiscovery" Version="$(AspireVersion)" />
<PackageVersion Include="Microsoft.Extensions.ServiceDiscovery.Abstractions" Version="$(AspireVersion)" />
<PackageVersion Include="Microsoft.Extensions.ServiceDiscovery.Yarp" Version="$(AspireVersion)" />
Expand All @@ -62,15 +59,13 @@
<PackageVersion Include="OpenTelemetry.Instrumentation.AspNetCore" Version="$(OpenTelemetryVersion)" />
<PackageVersion Include="OpenTelemetry.Instrumentation.Http" Version="$(OpenTelemetryVersion)" />
<PackageVersion Include="OpenTelemetry.Instrumentation.Runtime" Version="$(OpenTelemetryVersion)" />
<!--<PackageVersion Include="Azure.Monitor.OpenTelemetry.Exporter" Version="1.3.0-beta.2" />
<PackageVersion Include="Azure.Monitor.OpenTelemetry.AspNetCore" Version="1.2.0-beta.4" />-->
</ItemGroup>
<ItemGroup>
<!-- Healthchecks -->
<PackageVersion Include="AspNetCore.HealthChecks.Uris" Version="$(HealthCheckVersion)" />
<PackageVersion Include="AspNetCore.HealthChecks.Uris" Version="8.0.1" />
<PackageVersion Include="AspNetCore.HealthChecks.NpgSql" Version="$(HealthCheckVersion)" />
<PackageVersion Include="AspNetCore.HealthChecks.Rabbitmq" Version="$(HealthCheckVersion)" />
<PackageVersion Include="AspNetCore.HealthChecks.Redis" Version="$(HealthCheckVersion)" />
<PackageVersion Include="AspNetCore.HealthChecks.Redis" Version="8.0.1" />
</ItemGroup>
<ItemGroup>
<!-- Testing -->
Expand All @@ -81,4 +76,24 @@
<PackageVersion Include="MSTest" Version="3.2.0" />
<PackageVersion Include="ReportGenerator" Version="5.3.6" />
</ItemGroup>
<ItemGroup Label="EF">
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="$(EfVersion)" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="$(EfVersion)" PrivateAssets="all" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="$(EfVersion)" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="$(EfVersion)" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="$(EfVersion)" />
<PackageVersion Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="$(EfVersion)" />
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.1" />
<PackageVersion Include="EFCore.NamingConventions" Version="8.0.3" />
<PackageVersion Include="Pgvector" Version="0.3.0" />
<PackageVersion Include="Pgvector.EntityFrameworkCore" Version="0.2.1" />
</ItemGroup>
<ItemGroup Label="AI">
<PackageVersion Include="Microsoft.Extensions.AI" Version="$(AIExtensions)" />
<PackageVersion Include="Microsoft.Extensions.AI.Abstractions" Version="$(AIExtensions)" />
<PackageVersion Include="Microsoft.Extensions.AI.Ollama" Version="$(AIExtensions)" />
<PackageVersion Include="Microsoft.Extensions.AI.OpenAI" Version="$(AIExtensions)" />
<PackageVersion Include="CommunityToolkit.Aspire.Hosting.Ollama" Version="9.0.0-beta.66" />
<PackageVersion Include="Azure.AI.OpenAI" Version="2.1.0-beta.2" />
</ItemGroup>
</Project>
26 changes: 22 additions & 4 deletions NuGet.config
Original file line number Diff line number Diff line change
@@ -1,7 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="nuget" value="https://api.nuget.org/v3/index.json" />
</packageSources>
<packageSources>
<clear />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
<!--<add key="dotnet8" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet8/nuget/v3/index.json" />-->
<add key="azure-sdk-devfeed" value="https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-net/nuget/v3/index.json" />
</packageSources>
<solution>
<add key="disableSourceControlIntegration" value="true" />
</solution>
<packageSourceMapping>
<clear />
<packageSource key="nuget.org">
<package pattern="*" />
</packageSource>
<!--<packageSource key="dotnet8">
<package pattern="Aspire.*" />
<package pattern="Microsoft.Extensions.ServiceDiscovery*" />
</packageSource>-->
<packageSource key="azure-sdk-devfeed">
<package pattern="Azure.*" />
</packageSource>
</packageSourceMapping>
</configuration>
76 changes: 26 additions & 50 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,24 @@ The coffeeshop apps on .NET Aspire

![Counter API-Code Coverage](https://img.shields.io/badge/Code%20Coverage-73%25-yellow?style=flat)

## Get starting

In `Visual Studio` with the project opening, press `F5`!!!

or

```sh
> dotnet build coffeeshop-aspire.sln
> dotnet run --project app-host/app-host.csproj
# http://localhost:5019
```

## Introduction

> Notice: This is just a demo of how can we build and deploy the microservices approach. In the reality, the boundary should be defined by bounded-context concepts of Domaim-driven Design, and totally based on the business domain, and might not be so fine-grained services like this demo, so you use it with care.
> Notice: This is just a demo of how can we build and deploy the microservices approach. In the reality, the boundary should be defined by bounded-context concepts of Domain-driven Design, and totally based on the business domain, and might not be so fine-grained services like this demo, so you use it with care.
- [x] Built on .NET 8.0 LTS
- [x] Built on .NET 9.0 STS
- [x] .NET Aspire 9
- [x] Microservices architectural style
- [x] Follows Vertical Sliding principles
- [x] Domain Driven Design building blocks
Expand All @@ -22,9 +35,12 @@ The coffeeshop apps on .NET Aspire
- [x] API Versioning
- [x] Integration test with .NET Aspire and Wiremock.NET
- [x] Run it on GitHub Actions and output code coverage
- [x] UUID v7
- [Extend System.Guid with a new creation API for v7](https://github.com/dotnet/runtime/issues/103658)
- [Npgsql.EntityFrameworkCore.PostgreSQL-UUIDv7 GUIDs are generated by default](https://www.npgsql.org/efcore/release-notes/9.0.html#uuidv7-guids-are-generated-by-default)
- [x] Microsoft.Extensions.AI (Ollama for local dev and Azure OpenAI service)
- [ ] Response Caching - Distributed Caching with Redis
- [ ] Dapr integration
- [ ] JWT & Authentication with Keycloak
- [ ] JWT & Authentication with ASP.NET Identity

## System Context diagram - C4 Model

Expand Down Expand Up @@ -89,54 +105,14 @@ C4Container
Rel_Back(order_summary, message_broker, "Subscribes", "TCP")
```

## Prerequisites
## CoffeeShop App Infused with AI - Intelligent Apps Development

If you run on `Windows 11`:
### Seeding data - chat completion

```bash
> cargo install just
# https://cheatography.com/linux-china/cheat-sheets/justfile/
```
![](assets/genai_seed_data.png)

## Get starting
### Semantic Searching - vector embeddings

```sh
> dotnet build coffeeshop-aspire.sln
> dotnet run --project app-host/app-host.csproj
# http://localhost:5019
```
![](assets/genai_semantic_searching.png)

## Generate manifest file (powershell)

```sh
dotnet run --project app-host\CoffeeShop.AppHost.csproj `
--publisher manifest `
--output-path ../aspire-manifest.json
```

## Deploy to Kubernetes

```sh
dotnet tool install -g aspirate --prerelease
```

## Run with Justfile (cross-platform)

```sh
> just run
```

On Windows 11 - WSL2 Ubuntu 22 integrated, we can use `Podman Desktop` to replace `Docker for Desktop`, and run `.NET Aspire` normally. Check this blog post -> https://dev.to/thangchung/net-8-integration-tests-on-podman-desktop-windows-11-wsl2-ubuntu-23-4hpo

## Run with Makefile (Ubuntu)

```sh
> touch .env
> make run
# http://localhost:5019
```

```sh
dotnet publish "/workspaces/coffeeshop-aspire/app-host/../product-api/CoffeeShop.ProductApi.csproj" -p:PublishProfile="DefaultContainer" -p:PublishSingleFile="true"
-p:PublishTrimmed="false" --self-contained "true" --verbosity "quiet" --nologo -r "linux-x64" -p:ContainerRegistry="k3d-myregistry.localhost:12345" -p:ContainerRepository="product-api" -p:ContainerImageTag="latest"
```
The detail blog of how to implement it at https://dev.to/thangchung/coffeeshop-app-infused-with-ai-intelligent-apps-development-202k
7 changes: 3 additions & 4 deletions app-host/CoffeeShop.AppHost.csproj
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">

<Sdk Name="Aspire.AppHost.Sdk" Version="$(AspireVersion)" />

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsAspireHost>true</IsAspireHost>
</PropertyGroup>

Expand All @@ -13,7 +12,7 @@
<PackageReference Include="Aspire.Hosting.PostgreSQL" />
<PackageReference Include="Aspire.Hosting.Redis" />
<PackageReference Include="Aspire.Hosting.RabbitMQ" />
<PackageReference Include="Aspirant.Hosting" />
<PackageReference Include="CommunityToolkit.Aspire.Hosting.Ollama" />
<PackageReference Include="Yarp.ReverseProxy" />
</ItemGroup>

Expand Down
29 changes: 26 additions & 3 deletions app-host/HealthCheckExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
using Aspirant.Hosting;

using HealthChecks.NpgSql;
using HealthChecks.NpgSql;
using HealthChecks.RabbitMQ;
using HealthChecks.Redis;
using HealthChecks.Uris;

using Microsoft.Extensions.Diagnostics.HealthChecks;

namespace CoffeeShop.AppHost;

/// <summary>
Expand Down Expand Up @@ -62,4 +62,27 @@ public static IResourceBuilder<T> WithHealthCheck<T>(
return new UriHealthCheck(options, () => client);
}));
}
}

public class HealthCheckAnnotation(Func<IResource, CancellationToken, Task<IHealthCheck?>> healthCheckFactory) : IResourceAnnotation
{
public Func<IResource, CancellationToken, Task<IHealthCheck?>> HealthCheckFactory { get; } = healthCheckFactory;

public static HealthCheckAnnotation Create(Func<string, IHealthCheck> connectionStringFactory)
{
return new(async (resource, token) =>
{
if (resource is not IResourceWithConnectionString c)
{
return null;
}
if (await c.GetConnectionStringAsync(token) is not string cs)
{
return null;
}
return connectionStringFactory(cs);
});
}
}
Loading

0 comments on commit 0fbbd5e

Please sign in to comment.