mirror of
https://github.com/meysamhadeli/booking-microservices.git
synced 2026-04-12 11:32:10 +08:00
Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d99d1a9a9a | ||
|
|
6e1fe61a65 | ||
|
|
a882b3cfe2 | ||
|
|
14cc9e7c96 | ||
|
|
0f66c14299 | ||
|
|
7f9cf8b922 | ||
|
|
23d4babd52 | ||
|
|
8d4819624e | ||
|
|
043c20002c | ||
|
|
94a22dfc23 | ||
|
|
3ce312891a | ||
|
|
a62177a6c4 | ||
|
|
b67d000580 | ||
|
|
2a5909bdbd | ||
|
|
786fbb121f | ||
|
|
b9b3c26edc | ||
|
|
9dcd6625b2 | ||
|
|
43666a7dd3 | ||
|
|
23b14eabe7 | ||
|
|
9164c770b5 | ||
|
|
3bdcc6341f | ||
|
|
20e49770b2 | ||
|
|
e259b64476 | ||
|
|
7476502e50 | ||
|
|
64dfee4224 | ||
|
|
bd94742d18 | ||
|
|
d5e9f75dfc | ||
|
|
eef8aead7e | ||
|
|
38c339c1aa | ||
|
|
20bd2ac0bc | ||
|
|
3b86cf7917 | ||
|
|
d1dbe6209c | ||
|
|
475fa90e1c | ||
|
|
0dd7941136 | ||
|
|
29a1e47a2d | ||
|
|
68f768d687 | ||
|
|
e2fbd27222 | ||
|
|
d6e313a560 | ||
|
|
57000c5802 | ||
|
|
b3ce2889b2 | ||
|
|
a3c6f670e1 | ||
|
|
3bd8cb1db3 |
3
.aspire/settings.json
Normal file
3
.aspire/settings.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"appHostPath": "../src/Aspire/src/AppHost/AppHost.csproj"
|
||||
}
|
||||
@ -3,18 +3,28 @@
|
||||
"isRoot": true,
|
||||
"tools": {
|
||||
"dotnet-outdated-tool": {
|
||||
"version": "4.6.4",
|
||||
"version": "4.6.9",
|
||||
"commands": [
|
||||
"dotnet-outdated"
|
||||
],
|
||||
"rollForward": false
|
||||
]
|
||||
},
|
||||
"dotnet-ef": {
|
||||
"version": "9.0.0",
|
||||
"version": "10.0.3",
|
||||
"commands": [
|
||||
"dotnet-ef"
|
||||
],
|
||||
"rollForward": false
|
||||
]
|
||||
},
|
||||
"aspire.cli": {
|
||||
"version": "13.1.1",
|
||||
"commands": [
|
||||
"aspire"
|
||||
]
|
||||
},
|
||||
"csharpier": {
|
||||
"version": "0.30.6",
|
||||
"commands": [
|
||||
"dotnet-csharpier"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -455,6 +455,8 @@ dotnet_diagnostic.CA1707.severity = None
|
||||
dotnet_diagnostic.CA1716.severity = Suggestion
|
||||
# CA1032: Implement standard exception constructors
|
||||
dotnet_diagnostic.CA1032.severity = Suggestion
|
||||
# AV1500: A method should not exceed a predefined number (60-100 lines) of lines
|
||||
dotnet_diagnostic.AV1500.severity = none
|
||||
|
||||
##################################################################################
|
||||
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/
|
||||
|
||||
2
.github/actions/build/action.yml
vendored
2
.github/actions/build/action.yml
vendored
@ -34,7 +34,7 @@ runs:
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: '9.x.x'
|
||||
dotnet-version: '10.x.x'
|
||||
|
||||
# https://learn.microsoft.com/en-us/dotnet/core/tools/global-tools
|
||||
- name: Restore .NET Tools
|
||||
|
||||
1
.gitpod.Dockerfile
vendored
1
.gitpod.Dockerfile
vendored
@ -1 +0,0 @@
|
||||
FROM gitpod/workspace-dotnet:latest
|
||||
37
.gitpod.yml
37
.gitpod.yml
@ -1,37 +0,0 @@
|
||||
# https://github.com/gitpod-samples/template-dotnet-core-cli-csharp
|
||||
# https://www.gitpod.io/docs/introduction/languages/dotnet
|
||||
# https://github.com/gitpod-samples/template-docker-compose
|
||||
# https://www.gitpod.io/docs/references/gitpod-yml
|
||||
# https://www.gitpod.io/docs/configure
|
||||
# https://www.gitpod.io/docs/configure/workspaces/ports
|
||||
|
||||
image:
|
||||
file: .gitpod.Dockerfile
|
||||
|
||||
vscode:
|
||||
extensions:
|
||||
- muhammad-sammy.csharp
|
||||
- editorconfig.editorconfig
|
||||
- vivaxy.vscode-conventional-commits
|
||||
- humao.rest-client
|
||||
- ms-azuretools.vscode-docker
|
||||
- donjayamanne.githistory
|
||||
- pkief.material-icon-theme
|
||||
- emmanuelbeziat.vscode-great-icons
|
||||
|
||||
# https://www.gitpod.io/docs/configure/workspaces/tasks#execution-order
|
||||
# https://www.gitpod.io/docs/configure/projects/prebuilds
|
||||
tasks:
|
||||
- name: Init Docker-Compose
|
||||
# https://www.gitpod.io/docs/configure/projects/prebuilds
|
||||
# We load docker on pre-build for increasing speed
|
||||
init: |
|
||||
docker-compose pull
|
||||
docker-compose -f ./deployments/docker-compose/infrastracture.yaml up -d
|
||||
- name: Setup kubectl
|
||||
command: bash $GITPOD_REPO_ROOT/scripts/setup_kubectl_gitpod.sh
|
||||
- name: Restore & Build
|
||||
init: |
|
||||
dotnet dev-certs https
|
||||
dotnet restore
|
||||
dotnet build
|
||||
@ -1,34 +1,35 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<RepositoryRoot>$(MSBuildThisFileDirectory)</RepositoryRoot>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="StyleCop.Analyzers" PrivateAssets="all" Version="1.1.118">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Meziantou.Analyzer" PrivateAssets="all" Version="2.0.182">
|
||||
<PackageReference Include="Meziantou.Analyzer" PrivateAssets="all" Version="2.0.299">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Roslynator.Analyzers" PrivateAssets="all" Version="4.12.9">
|
||||
<PackageReference Include="Roslynator.Analyzers" PrivateAssets="all" Version="4.15.0">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Roslynator.CodeAnalysis.Analyzers" PrivateAssets="all" Version="4.12.9">
|
||||
<PackageReference Include="Roslynator.CodeAnalysis.Analyzers" PrivateAssets="all" Version="4.15.0">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Roslynator.Formatting.Analyzers" PrivateAssets="all" Version="4.12.9">
|
||||
<PackageReference Include="Roslynator.Formatting.Analyzers" PrivateAssets="all" Version="4.15.0">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" PrivateAssets="all" Version="17.12.19">
|
||||
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" PrivateAssets="all" Version="17.14.15">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="AsyncAwaitBestPractices" PrivateAssets="all" Version="9.0.0">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="SerilogAnalyzer" PrivateAssets="all" Version="0.15.0">
|
||||
<PackageReference Include="AsyncAwaitBestPractices" PrivateAssets="all" Version="10.0.0">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="CSharpGuidelinesAnalyzer" PrivateAssets="all" Version="3.8.5">
|
||||
|
||||
15
README.md
15
README.md
@ -6,15 +6,12 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
> 🚀 **A practical microservices with the latest technologies and architectures like Vertical Slice Architecture, Event Sourcing, CQRS, DDD, gRpc, MongoDB, RabbitMq, Masstransit, and Aspire in .Net 9.**
|
||||
> 🚀 **A practical microservices with the latest technologies and architectures like Vertical Slice Architecture, Event Sourcing, CQRS, DDD, gRpc, MongoDB, RabbitMq, Masstransit, and Aspire in .Net 10.**
|
||||
|
||||
## You can find other version of this project here:
|
||||
- [Booking with Modular Monolith Architecture](https://github.com/meysamhadeli/booking-modular-monolith)
|
||||
- [Booking with Monolith Architecture](https://github.com/meysamhadeli/booking-monolith)
|
||||
|
||||
<div>
|
||||
<a href="https://gitpod.io/#https://github.com/meysamhadeli/booking-microservices"><img alt="Open in Gitpod" src="https://gitpod.io/button/open-in-gitpod.svg"/></a>
|
||||
</div>
|
||||
<div>
|
||||
<a href='https://codespaces.new/meysamhadeli/booking-microservices?quickstart=1'><img alt='Open in GitHub Codespaces' src='https://github.com/codespaces/badge.svg'></a>
|
||||
</div>
|
||||
@ -77,10 +74,10 @@
|
||||
|
||||
## Technologies - Libraries
|
||||
|
||||
- ✔️ **[`.NET 9`](https://github.com/dotnet/aspnetcore)** - .NET Framework and .NET Core, including ASP.NET and ASP.NET Core.
|
||||
- ✔️ **[`.NET 10`](https://github.com/dotnet/aspnetcore)** - .NET Framework and .NET Core, including ASP.NET and ASP.NET Core.
|
||||
- ✔️ **[`MVC Versioning API`](https://github.com/microsoft/aspnet-api-versioning)** - Set of libraries which add service API versioning to ASP.NET Web API, OData with ASP.NET Web API, and ASP.NET Core.
|
||||
- ✔️ **[`EF Core`](https://github.com/dotnet/efcore)** - Modern object-database mapper for .NET. It supports LINQ queries, change tracking, updates, and schema migrations.
|
||||
- ✔️ **[`AspNetCore OpenApi`](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/openapi/aspnetcore-openapi?view=aspnetcore-9.0&tabs=visual-studio#configure-openapi-document-generation)** - Provides built-in support for OpenAPI document generation in ASP.NET Core.
|
||||
- ✔️ **[`AspNetCore OpenApi`](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/openapi/aspnetcore-openapi)** - Provides built-in support for OpenAPI document generation in ASP.NET Core.
|
||||
- ✔️ **[`Masstransit`](https://github.com/MassTransit/MassTransit)** - Distributed Application Framework for .NET.
|
||||
- ✔️ **[`MediatR`](https://github.com/jbogard/MediatR)** - Simple, unambitious mediator implementation in .NET.
|
||||
- ✔️ **[`FluentValidation`](https://github.com/FluentValidation/FluentValidation)** - Popular .NET validation library for building strongly-typed validation rules.
|
||||
@ -217,13 +214,13 @@ dotnet dev-certs https --trust
|
||||
|
||||
### Aspire
|
||||
|
||||
To run the application using the `ASPIRE App Host`, execute the following command from the solution root:
|
||||
To run the application using the `Aspire App Host`, execute the following command from the solution root:
|
||||
|
||||
```bash
|
||||
dotnet run --project ./src/Aspire/src/AppHost
|
||||
aspire run
|
||||
```
|
||||
|
||||
> Note:The `ASPIRE dashboard` will be available at `http://localhost:18888`
|
||||
> Note:The `Aspire dashboard` will be available at `http://localhost:18888`
|
||||
|
||||
> ### Docker Compose
|
||||
|
||||
|
||||
@ -75,6 +75,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{C4287034-683
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AppHost", "src\Aspire\src\AppHost\AppHost.csproj", "{490BCB11-314C-473C-9B85-A32164783507}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceDefaults", "src\Aspire\src\ServiceDefaults\ServiceDefaults.csproj", "{5B7BF918-E47F-4932-B5C5-E8C2C35890E4}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@ -289,6 +291,18 @@ Global
|
||||
{490BCB11-314C-473C-9B85-A32164783507}.Release|x64.Build.0 = Release|Any CPU
|
||||
{490BCB11-314C-473C-9B85-A32164783507}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{490BCB11-314C-473C-9B85-A32164783507}.Release|x86.Build.0 = Release|Any CPU
|
||||
{5B7BF918-E47F-4932-B5C5-E8C2C35890E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{5B7BF918-E47F-4932-B5C5-E8C2C35890E4}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{5B7BF918-E47F-4932-B5C5-E8C2C35890E4}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{5B7BF918-E47F-4932-B5C5-E8C2C35890E4}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{5B7BF918-E47F-4932-B5C5-E8C2C35890E4}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{5B7BF918-E47F-4932-B5C5-E8C2C35890E4}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{5B7BF918-E47F-4932-B5C5-E8C2C35890E4}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{5B7BF918-E47F-4932-B5C5-E8C2C35890E4}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{5B7BF918-E47F-4932-B5C5-E8C2C35890E4}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{5B7BF918-E47F-4932-B5C5-E8C2C35890E4}.Release|x64.Build.0 = Release|Any CPU
|
||||
{5B7BF918-E47F-4932-B5C5-E8C2C35890E4}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{5B7BF918-E47F-4932-B5C5-E8C2C35890E4}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@ -329,5 +343,6 @@ Global
|
||||
{D1B6353A-63F5-4DD9-90E6-42B2CFDF1DEA} = {CD4A4407-C3B0-422D-BB8C-2A810CED9938}
|
||||
{C4287034-6833-4505-A6EB-704A86392ECB} = {D1B6353A-63F5-4DD9-90E6-42B2CFDF1DEA}
|
||||
{490BCB11-314C-473C-9B85-A32164783507} = {C4287034-6833-4505-A6EB-704A86392ECB}
|
||||
{5B7BF918-E47F-4932-B5C5-E8C2C35890E4} = {C4287034-6833-4505-A6EB-704A86392ECB}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
@ -355,7 +355,7 @@ services:
|
||||
args:
|
||||
Version: "1"
|
||||
context: ../../
|
||||
dockerfile: src/ApiGateway/dev.Dockerfile
|
||||
dockerfile: src/ApiGateway/Dockerfile
|
||||
container_name: api-gateway
|
||||
ports:
|
||||
- "5001:80"
|
||||
@ -382,7 +382,7 @@ services:
|
||||
args:
|
||||
Version: "1"
|
||||
context: ../../
|
||||
dockerfile: src/Services/Flight/dev.Dockerfile
|
||||
dockerfile: src/Services/Flight/Dockerfile
|
||||
container_name: flight
|
||||
ports:
|
||||
- 5004:80
|
||||
@ -408,7 +408,7 @@ services:
|
||||
args:
|
||||
Version: "1"
|
||||
context: ../../
|
||||
dockerfile: src/Services/Identity/dev.Dockerfile
|
||||
dockerfile: src/Services/Identity/Dockerfile
|
||||
container_name: identity
|
||||
ports:
|
||||
- 6005:80
|
||||
@ -435,7 +435,7 @@ services:
|
||||
args:
|
||||
Version: "1"
|
||||
context: ../../
|
||||
dockerfile: src/Services/Passenger/dev.Dockerfile
|
||||
dockerfile: src/Services/Passenger/Dockerfile
|
||||
container_name: passenger
|
||||
ports:
|
||||
- 6012:80
|
||||
@ -462,7 +462,7 @@ services:
|
||||
args:
|
||||
Version: "1"
|
||||
context: ../../
|
||||
dockerfile: src/Services/Booking/dev.Dockerfile
|
||||
dockerfile: src/Services/Booking/Dockerfile
|
||||
container_name: booking
|
||||
ports:
|
||||
- 6010:80
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "9.0.100",
|
||||
"version": "10.0.103",
|
||||
"rollForward": "latestFeature"
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,8 +7,8 @@
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"prepare": "husky && dotnet tool restore",
|
||||
"format": "dotnet format booking-microservices.sln --severity error --verbosity detailed",
|
||||
"ci-format": "dotnet format booking-microservices.sln --verify-no-changes --severity error --verbosity detailed",
|
||||
"format": "dotnet tool run dotnet-csharpier booking-microservices.sln",
|
||||
"ci-format": "dotnet tool run dotnet-csharpier booking-microservices.sln --check",
|
||||
"upgrade-packages": "dotnet outdated --upgrade"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@ -1,45 +1,38 @@
|
||||
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS builder
|
||||
WORKDIR /
|
||||
# ---------- Build Stage ----------
|
||||
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
|
||||
WORKDIR /src
|
||||
|
||||
COPY ./.editorconfig ./
|
||||
COPY ./global.json ./
|
||||
COPY ./Directory.Build.props ./
|
||||
# Copy solution-level files
|
||||
COPY .editorconfig .
|
||||
COPY global.json .
|
||||
COPY Directory.Build.props .
|
||||
|
||||
# Setup working directory for the project
|
||||
COPY ./src/BuildingBlocks/BuildingBlocks.csproj ./building-blocks/
|
||||
COPY ./src/ApiGateway/src/ApiGateway.csproj ./src/ApiGateway/src/
|
||||
# Copy project files first (better Docker caching)
|
||||
COPY src/BuildingBlocks/BuildingBlocks.csproj src/BuildingBlocks/
|
||||
COPY src/ApiGateway/src/ApiGateway.csproj src/ApiGateway/src/
|
||||
COPY src/Aspire/src/ServiceDefaults/ServiceDefaults.csproj src/Aspire/src/ServiceDefaults/
|
||||
|
||||
# Restore dependencies
|
||||
RUN dotnet restore src/ApiGateway/src/ApiGateway.csproj
|
||||
|
||||
# Restore nuget packages
|
||||
RUN dotnet restore ./src/ApiGateway/src/ApiGateway.csproj
|
||||
# Copy the rest of the source code
|
||||
COPY src ./src
|
||||
|
||||
# Copy project files
|
||||
COPY ./src/BuildingBlocks ./src/BuildingBlocks/
|
||||
COPY ./src/ApiGateway/src ./src/ApiGateway/src/
|
||||
# Publish (build included)
|
||||
RUN dotnet publish src/ApiGateway/src/ApiGateway.csproj \
|
||||
-c Release \
|
||||
-o /app/publish \
|
||||
--no-restore
|
||||
|
||||
# Build project with Release configuration
|
||||
# and no restore, as we did it already
|
||||
# ---------- Runtime Stage ----------
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime
|
||||
WORKDIR /app
|
||||
|
||||
RUN ls
|
||||
RUN dotnet build -c Release --no-restore ./src/ApiGateway/src/ApiGateway.csproj
|
||||
COPY --from=build /app/publish .
|
||||
|
||||
WORKDIR /src/ApiGateway/src
|
||||
|
||||
# Publish project to output folder
|
||||
# and no build, as we did it already
|
||||
RUN dotnet publish -c Release --no-build -o out
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:9.0
|
||||
|
||||
# Setup working directory for the project
|
||||
WORKDIR /
|
||||
COPY --from=builder /src/ApiGateway/src/out .
|
||||
|
||||
ENV ASPNETCORE_URLS https://*:443, http://*:80
|
||||
ENV ASPNETCORE_ENVIRONMENT docker
|
||||
ENV ASPNETCORE_URLS=http://+:80
|
||||
ENV ASPNETCORE_ENVIRONMENT=docker
|
||||
|
||||
EXPOSE 80
|
||||
EXPOSE 443
|
||||
|
||||
ENTRYPOINT ["dotnet", "ApiGateway.dll"]
|
||||
|
||||
ENTRYPOINT ["dotnet", "ApiGateway.dll"]
|
||||
@ -1,48 +0,0 @@
|
||||
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS builder
|
||||
WORKDIR /
|
||||
|
||||
COPY ./.editorconfig ./
|
||||
COPY ./global.json ./
|
||||
COPY ./Directory.Build.props ./
|
||||
|
||||
# Setup working directory for the project
|
||||
COPY ./src/BuildingBlocks/BuildingBlocks.csproj ./building-blocks/
|
||||
COPY ./src/ApiGateway/src/ApiGateway.csproj ./src/ApiGateway/src/
|
||||
|
||||
|
||||
# Restore nuget packages
|
||||
RUN --mount=type=cache,id=gateway_nuget,target=/root/.nuget/packages \
|
||||
dotnet restore ./src/ApiGateway/src/ApiGateway.csproj
|
||||
|
||||
# Copy project files
|
||||
COPY ./src/BuildingBlocks ./src/BuildingBlocks/
|
||||
COPY ./src/ApiGateway/src ./src/ApiGateway/src/
|
||||
|
||||
# Build project with Release configuration
|
||||
# and no restore, as we did it already
|
||||
|
||||
RUN ls
|
||||
RUN --mount=type=cache,id=gateway_nuget,target=/root/.nuget/packages \
|
||||
dotnet build -c Release --no-restore ./src/ApiGateway/src/ApiGateway.csproj
|
||||
|
||||
WORKDIR /src/ApiGateway/src
|
||||
|
||||
# Publish project to output folder
|
||||
# and no build, as we did it already
|
||||
RUN --mount=type=cache,id=gateway_nuget,target=/root/.nuget/packages \
|
||||
dotnet publish -c Release --no-build -o out
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:9.0
|
||||
|
||||
# Setup working directory for the project
|
||||
WORKDIR /
|
||||
COPY --from=builder /src/ApiGateway/src/out .
|
||||
|
||||
ENV ASPNETCORE_URLS https://*:443, http://*:80
|
||||
ENV ASPNETCORE_ENVIRONMENT docker
|
||||
|
||||
EXPOSE 80
|
||||
EXPOSE 443
|
||||
|
||||
ENTRYPOINT ["dotnet", "ApiGateway.dll"]
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using BuildingBlocks.Web;
|
||||
using Figgle;
|
||||
using Figgle.Fonts;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
var env = builder.Environment;
|
||||
@ -7,7 +8,6 @@ var appOptions = builder.Services.GetOptions<AppOptions>("AppOptions");
|
||||
Console.WriteLine(FiggleFonts.Standard.Render(appOptions.Name));
|
||||
|
||||
|
||||
|
||||
builder.Services.AddControllers();
|
||||
builder.Services.AddHttpContextAccessor();
|
||||
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<Sdk Name="Aspire.AppHost.SDK" Version="9.3.1"/>
|
||||
<Sdk Name="Aspire.AppHost.SDK" Version="13.1.1"/>
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsAspireHost>true</IsAspireHost>
|
||||
@ -12,12 +12,13 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Aspire.Hosting.AppHost" Version="9.3.1" />
|
||||
<PackageReference Include="Aspire.Hosting.MongoDB" Version="9.3.1" />
|
||||
<PackageReference Include="Aspire.Hosting.PostgreSQL" Version="9.3.1" />
|
||||
<PackageReference Include="Aspire.Hosting.RabbitMQ" Version="9.3.1" />
|
||||
<PackageReference Include="Aspire.Hosting.Redis" Version="9.3.1" />
|
||||
<PackageReference Include="CommunityToolkit.Aspire.Hosting.EventStore" Version="9.6.0" />
|
||||
<PackageReference Include="Aspire.Hosting.AppHost" Version="13.1.1" />
|
||||
<PackageReference Include="Aspire.Hosting.Docker" Version="13.1.1-preview.1.26105.8" />
|
||||
<PackageReference Include="Aspire.Hosting.MongoDB" Version="13.1.1" />
|
||||
<PackageReference Include="Aspire.Hosting.PostgreSQL" Version="13.1.1" />
|
||||
<PackageReference Include="Aspire.Hosting.RabbitMQ" Version="13.1.1" />
|
||||
<PackageReference Include="Aspire.Hosting.Redis" Version="13.1.1" />
|
||||
<PackageReference Include="CommunityToolkit.Aspire.Hosting.EventStore" Version="9.9.0" />
|
||||
<PackageReference Include="Elastic.Aspire.Hosting.Elasticsearch" Version="9.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@ -2,39 +2,82 @@ using System.Net.Sockets;
|
||||
|
||||
var builder = DistributedApplication.CreateBuilder(args);
|
||||
|
||||
// 1. Database Services
|
||||
var username = builder.AddParameter("username", "postgres", secret: true);
|
||||
var password = builder.AddParameter("password", "postgres", secret: true);
|
||||
builder.AddDockerComposeEnvironment("docker-compose");
|
||||
|
||||
var postgres = builder.AddPostgres("postgres", username, password)
|
||||
// 1. Database Services
|
||||
var pgUsername = builder.AddParameter("pg-username", "postgres", secret: true);
|
||||
var pgPassword = builder.AddParameter("pg-password", "postgres", secret: true);
|
||||
|
||||
var postgres = builder.AddPostgres("postgres", pgUsername, pgPassword)
|
||||
.WithImage("postgres:latest")
|
||||
.WithEndpoint(port: 5432, targetPort: 5432, name: "postgres")
|
||||
.WithEndpoint(
|
||||
"tcp",
|
||||
e =>
|
||||
{
|
||||
e.Port = 5432;
|
||||
e.TargetPort = 5432;
|
||||
e.IsProxied = true;
|
||||
e.IsExternal = false;
|
||||
})
|
||||
.WithArgs(
|
||||
"-c", "wal_level=logical",
|
||||
"-c", "max_prepared_transactions=10"
|
||||
)
|
||||
.WithDataVolume("postgres-data")
|
||||
.WithLifetime(ContainerLifetime.Persistent);
|
||||
"-c",
|
||||
"wal_level=logical",
|
||||
"-c",
|
||||
"max_prepared_transactions=10");
|
||||
|
||||
if (builder.ExecutionContext.IsPublishMode)
|
||||
{
|
||||
postgres.WithDataVolume("postgres-data")
|
||||
.WithLifetime(ContainerLifetime.Persistent);
|
||||
}
|
||||
|
||||
|
||||
var flightDb = postgres.AddDatabase("flight");
|
||||
var passengerDb = postgres.AddDatabase("passenger");
|
||||
var identityDb = postgres.AddDatabase("identity");
|
||||
var persistMessageDb = postgres.AddDatabase("persist-message");
|
||||
|
||||
var mongoUsername = builder.AddParameter("mongo-username", "root");
|
||||
var mongoUsername = builder.AddParameter("mongo-username", "root", secret: true);
|
||||
var mongoPassword = builder.AddParameter("mongo-password", "secret", secret: true);
|
||||
|
||||
var mongo = builder.AddMongoDB("mongo", userName: mongoUsername, password: mongoPassword)
|
||||
.WithImage("mongo:latest")
|
||||
.WithEndpoint(port: 27017, targetPort: 27017, name: "mongo")
|
||||
.WithDataVolume("mongo-data")
|
||||
.WithLifetime(ContainerLifetime.Persistent);
|
||||
.WithImage("mongo")
|
||||
.WithImageTag("latest")
|
||||
.WithEndpoint(
|
||||
"tcp",
|
||||
e =>
|
||||
{
|
||||
e.Port = 27017;
|
||||
e.TargetPort = 27017;
|
||||
e.IsProxied = true;
|
||||
e.IsExternal = false;
|
||||
});
|
||||
|
||||
if (builder.ExecutionContext.IsPublishMode)
|
||||
{
|
||||
mongo.WithDataVolume("mongo-data")
|
||||
.WithLifetime(ContainerLifetime.Persistent);
|
||||
}
|
||||
|
||||
|
||||
var redis = builder.AddRedis("redis")
|
||||
.WithImage("redis:latest")
|
||||
.WithEndpoint(port: 6379, targetPort: 6379, name: "redis")
|
||||
.WithDataVolume("redis-data")
|
||||
.WithLifetime(ContainerLifetime.Persistent);
|
||||
.WithEndpoint(
|
||||
"tcp",
|
||||
e =>
|
||||
{
|
||||
e.Port = 6379;
|
||||
e.TargetPort = 6379;
|
||||
e.IsProxied = true;
|
||||
e.IsExternal = false;
|
||||
});
|
||||
|
||||
if (builder.ExecutionContext.IsPublishMode)
|
||||
{
|
||||
redis.WithDataVolume("redis-data")
|
||||
.WithLifetime(ContainerLifetime.Persistent);
|
||||
}
|
||||
|
||||
|
||||
var eventstore = builder.AddEventStore("eventstore")
|
||||
.WithImage("eventstore/eventstore")
|
||||
@ -43,41 +86,103 @@ var eventstore = builder.AddEventStore("eventstore")
|
||||
.WithEnvironment("EVENTSTORE_START_STANDARD_PROJECTIONS", "True")
|
||||
.WithEnvironment("EVENTSTORE_INSECURE", "True")
|
||||
.WithEnvironment("EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP", "True")
|
||||
.WithHttpEndpoint(port: 2113, targetPort: 2113, name: "eventstore-http")
|
||||
.WithDataVolume("eventstore-data")
|
||||
.WithLifetime(ContainerLifetime.Persistent);
|
||||
.WithEndpoint(
|
||||
"http",
|
||||
e =>
|
||||
{
|
||||
e.TargetPort = 2113;
|
||||
e.Port = 2113;
|
||||
e.IsProxied = true;
|
||||
e.IsExternal = true;
|
||||
})
|
||||
.WithEndpoint(
|
||||
port: 1113,
|
||||
targetPort: 1113,
|
||||
name: "tcp",
|
||||
isProxied: true,
|
||||
isExternal: false);
|
||||
|
||||
if (builder.ExecutionContext.IsPublishMode)
|
||||
{
|
||||
eventstore.WithDataVolume("eventstore-data")
|
||||
.WithLifetime(ContainerLifetime.Persistent);
|
||||
}
|
||||
|
||||
// 2. Messaging Services
|
||||
var rabbitmq = builder.AddRabbitMQ("rabbitmq")
|
||||
.WithImage("rabbitmq:management")
|
||||
.WithEndpoint(port: 5672, targetPort: 5672, name: "rabbitmq-amqp")
|
||||
.WithEndpoint(port: 15672, targetPort: 15672, name: "rabbitmq-management")
|
||||
.WithLifetime(ContainerLifetime.Persistent);
|
||||
var rabbitmqUsername = builder.AddParameter("rabbitmq-username", "guest", secret: true);
|
||||
var rabbitmqPassword = builder.AddParameter("rabbitmq-password", "guest", secret: true);
|
||||
|
||||
// 3. Observability Services
|
||||
var rabbitmq = builder.AddRabbitMQ("rabbitmq", rabbitmqUsername, rabbitmqPassword)
|
||||
.WithManagementPlugin()
|
||||
.WithEndpoint(
|
||||
"tcp",
|
||||
e =>
|
||||
{
|
||||
e.TargetPort = 5672;
|
||||
e.Port = 5672;
|
||||
e.IsProxied = true;
|
||||
e.IsExternal = false;
|
||||
})
|
||||
.WithEndpoint(
|
||||
"management",
|
||||
e =>
|
||||
{
|
||||
e.TargetPort = 15672;
|
||||
e.Port = 15672;
|
||||
e.IsProxied = true;
|
||||
e.IsExternal = true;
|
||||
});
|
||||
|
||||
if (builder.ExecutionContext.IsPublishMode)
|
||||
{
|
||||
rabbitmq.WithLifetime(ContainerLifetime.Persistent);
|
||||
}
|
||||
|
||||
// // 3. Observability Services
|
||||
var jaeger = builder.AddContainer("jaeger-all-in-one", "jaegertracing/all-in-one")
|
||||
.WithEndpoint(port: 6831, targetPort: 6831, name: "jaeger-udp", protocol: ProtocolType.Udp)
|
||||
.WithEndpoint(port: 16686, targetPort: 16686, name: "jaeger-ui")
|
||||
.WithEndpoint(port: 14268, targetPort: 14268, name: "jaeger-api")
|
||||
.WithEndpoint(port: 14317, targetPort: 4317, name: "jaeger-otlp-grpc")
|
||||
.WithEndpoint(port: 14318, targetPort: 4318, name: "jaeger-otlp-http")
|
||||
.WithLifetime(ContainerLifetime.Persistent);
|
||||
.WithEndpoint(
|
||||
port: 6831,
|
||||
targetPort: 6831,
|
||||
name: "agent",
|
||||
protocol: ProtocolType.Udp,
|
||||
isProxied: true,
|
||||
isExternal: false)
|
||||
.WithEndpoint(port: 16686, targetPort: 16686, name: "http", isProxied: true, isExternal: true)
|
||||
.WithEndpoint(port: 14268, targetPort: 14268, name: "collector", isProxied: true, isExternal: false)
|
||||
.WithEndpoint(port: 14317, targetPort: 4317, name: "otlp-grpc", isProxied: true, isExternal: false)
|
||||
.WithEndpoint(port: 14318, targetPort: 4318, name: "otlp-http", isProxied: true, isExternal: false);
|
||||
|
||||
if (builder.ExecutionContext.IsPublishMode)
|
||||
{
|
||||
jaeger.WithLifetime(ContainerLifetime.Persistent);
|
||||
}
|
||||
|
||||
var zipkin = builder.AddContainer("zipkin-all-in-one", "openzipkin/zipkin")
|
||||
.WithEndpoint(port: 9411, targetPort: 9411, name: "zipkin-api")
|
||||
.WithLifetime(ContainerLifetime.Persistent);
|
||||
.WithEndpoint(port: 9411, targetPort: 9411, name: "http", isProxied: true, isExternal: true);
|
||||
|
||||
if (builder.ExecutionContext.IsPublishMode)
|
||||
{
|
||||
zipkin.WithLifetime(ContainerLifetime.Persistent);
|
||||
}
|
||||
|
||||
var otelCollector = builder.AddContainer("otel-collector", "otel/opentelemetry-collector-contrib")
|
||||
.WithBindMount("../../../../deployments/configs/otel-collector-config.yaml", "/etc/otelcol-contrib/config.yaml", isReadOnly: true)
|
||||
.WithBindMount(
|
||||
"../../../../deployments/configs/otel-collector-config.yaml",
|
||||
"/etc/otelcol-contrib/config.yaml",
|
||||
isReadOnly: true)
|
||||
.WithArgs("--config=/etc/otelcol-contrib/config.yaml")
|
||||
.WithEndpoint(port: 11888, targetPort: 1888, name: "otel-pprof")
|
||||
.WithEndpoint(port: 8888, targetPort: 8888, name: "otel-metrics")
|
||||
.WithEndpoint(port: 8889, targetPort: 8889, name: "otel-exporter-metrics")
|
||||
.WithEndpoint(port: 13133, targetPort: 13133, name: "otel-health")
|
||||
.WithEndpoint(port: 4317, targetPort: 4317, name: "otel-grpc")
|
||||
.WithEndpoint(port: 4318, targetPort: 4318, name: "otel-http")
|
||||
.WithEndpoint(port: 55679, targetPort: 55679, name: "otel-zpages")
|
||||
.WithLifetime(ContainerLifetime.Persistent);
|
||||
.WithEndpoint(port: 11888, targetPort: 1888, name: "otel-pprof", isProxied: true, isExternal: true)
|
||||
.WithEndpoint(port: 8888, targetPort: 8888, name: "otel-metrics", isProxied: true, isExternal: true)
|
||||
.WithEndpoint(port: 8889, targetPort: 8889, name: "otel-exporter-metrics", isProxied: true, isExternal: true)
|
||||
.WithEndpoint(port: 13133, targetPort: 13133, name: "otel-health", isProxied: true, isExternal: true)
|
||||
.WithEndpoint(port: 4317, targetPort: 4317, name: "otel-grpc", isProxied: true, isExternal: true)
|
||||
.WithEndpoint(port: 4318, targetPort: 4318, name: "otel-http", isProxied: true, isExternal: true)
|
||||
.WithEndpoint(port: 55679, targetPort: 55679, name: "otel-zpages", isProxied: true, isExternal: true);
|
||||
|
||||
if (builder.ExecutionContext.IsPublishMode)
|
||||
{
|
||||
otelCollector.WithLifetime(ContainerLifetime.Persistent);
|
||||
}
|
||||
|
||||
var prometheus = builder.AddContainer("prometheus", "prom/prometheus")
|
||||
.WithBindMount("../../../../deployments/configs/prometheus.yaml", "/etc/prometheus/prometheus.yml")
|
||||
@ -87,8 +192,12 @@ var prometheus = builder.AddContainer("prometheus", "prom/prometheus")
|
||||
"--web.console.libraries=/usr/share/prometheus/console_libraries",
|
||||
"--web.console.templates=/usr/share/prometheus/consoles",
|
||||
"--web.enable-remote-write-receiver")
|
||||
.WithEndpoint(port: 9090, targetPort: 9090, name: "prometheus-web")
|
||||
.WithLifetime(ContainerLifetime.Persistent);
|
||||
.WithEndpoint(port: 9090, targetPort: 9090, name: "http", isProxied: true, isExternal: true);
|
||||
|
||||
if (builder.ExecutionContext.IsPublishMode)
|
||||
{
|
||||
prometheus.WithLifetime(ContainerLifetime.Persistent);
|
||||
}
|
||||
|
||||
var grafana = builder.AddContainer("grafana", "grafana/grafana")
|
||||
.WithEnvironment("GF_INSTALL_PLUGINS", "grafana-clock-panel,grafana-simple-json-datasource")
|
||||
@ -97,8 +206,12 @@ var grafana = builder.AddContainer("grafana", "grafana/grafana")
|
||||
.WithEnvironment("GF_FEATURE_TOGGLES_ENABLE", "traceqlEditor")
|
||||
.WithBindMount("../../../../deployments/configs/grafana/provisioning", "/etc/grafana/provisioning")
|
||||
.WithBindMount("../../../../deployments/configs/grafana/dashboards", "/var/lib/grafana/dashboards")
|
||||
.WithEndpoint(port: 3000, targetPort: 3000, name: "grafana-web")
|
||||
.WithLifetime(ContainerLifetime.Persistent);
|
||||
.WithEndpoint(port: 3000, targetPort: 3000, name: "http", isProxied: true, isExternal: true);
|
||||
|
||||
if (builder.ExecutionContext.IsPublishMode)
|
||||
{
|
||||
grafana.WithLifetime(ContainerLifetime.Persistent);
|
||||
}
|
||||
|
||||
var nodeExporter = builder.AddContainer("node-exporter", "prom/node-exporter")
|
||||
.WithBindMount("/proc", "/host/proc", isReadOnly: true)
|
||||
@ -108,22 +221,36 @@ var nodeExporter = builder.AddContainer("node-exporter", "prom/node-exporter")
|
||||
"--path.procfs=/host/proc",
|
||||
"--path.rootfs=/rootfs",
|
||||
"--path.sysfs=/host/sys")
|
||||
.WithEndpoint(port: 9101, targetPort: 9100, name: "node-exporter")
|
||||
.WithLifetime(ContainerLifetime.Persistent);
|
||||
.WithEndpoint(port: 9101, targetPort: 9100, name: "http", isProxied: true, isExternal: true);
|
||||
|
||||
if (builder.ExecutionContext.IsPublishMode)
|
||||
{
|
||||
nodeExporter.WithLifetime(ContainerLifetime.Persistent);
|
||||
}
|
||||
|
||||
var tempo = builder.AddContainer("tempo", "grafana/tempo")
|
||||
.WithBindMount("../../../../deployments/configs/tempo.yaml", "/etc/tempo.yaml", isReadOnly: true)
|
||||
.WithArgs("--config.file=/etc/tempo.yaml")
|
||||
.WithEndpoint(port: 3200, targetPort: 3200, name: "tempo")
|
||||
.WithEndpoint(port: 24317, targetPort: 4317, name: "tempo-otlp-grpc")
|
||||
.WithEndpoint(port: 24318, targetPort: 4318, name: "tempo-otlp-http")
|
||||
.WithLifetime(ContainerLifetime.Persistent);
|
||||
.WithEndpoint(port: 3200, targetPort: 3200, name: "http", isProxied: true, isExternal: false)
|
||||
.WithEndpoint(port: 9095, targetPort: 9095, name: "grpc", isProxied: true, isExternal: false)
|
||||
.WithEndpoint(port: 4317, targetPort: 4317, name: "otlp-grpc", isProxied: true, isExternal: false)
|
||||
.WithEndpoint(port: 4318, targetPort: 4318, name: "otlp-http", isProxied: true, isExternal: false);
|
||||
|
||||
if (builder.ExecutionContext.IsPublishMode)
|
||||
{
|
||||
tempo.WithLifetime(ContainerLifetime.Persistent);
|
||||
}
|
||||
|
||||
var loki = builder.AddContainer("loki", "grafana/loki")
|
||||
.WithBindMount("../../../../deployments/configs/loki-config.yaml", "/etc/loki/local-config.yaml", isReadOnly: true)
|
||||
.WithArgs("-config.file=/etc/loki/local-config.yaml")
|
||||
.WithEndpoint(port: 3100, targetPort: 3100, name: "loki")
|
||||
.WithLifetime(ContainerLifetime.Persistent);
|
||||
.WithEndpoint(port: 3100, targetPort: 3100, name: "http", isProxied: true, isExternal: false)
|
||||
.WithEndpoint(port: 9096, targetPort: 9096, name: "grpc", isProxied: true, isExternal: false);
|
||||
|
||||
if (builder.ExecutionContext.IsPublishMode)
|
||||
{
|
||||
loki.WithLifetime(ContainerLifetime.Persistent);
|
||||
}
|
||||
|
||||
var elasticsearch = builder.AddElasticsearch("elasticsearch")
|
||||
.WithImage("docker.elastic.co/elasticsearch/elasticsearch:8.17.0")
|
||||
@ -139,18 +266,41 @@ var elasticsearch = builder.AddElasticsearch("elasticsearch")
|
||||
.WithEnvironment("transport.host", "localhost")
|
||||
.WithEnvironment("bootstrap.memory_lock", "true")
|
||||
.WithEnvironment("cluster.routing.allocation.disk.threshold_enabled", "false")
|
||||
.WithEndpoint(port: 9200, targetPort: 9200, name: "elasticsearch-http")
|
||||
.WithEndpoint(port: 9300, targetPort: 9300, name: "elasticsearch-transport")
|
||||
.WithDataVolume("elastic-data")
|
||||
.WithLifetime(ContainerLifetime.Persistent);
|
||||
.WithEndpoint(
|
||||
"http",
|
||||
e =>
|
||||
{
|
||||
e.TargetPort = 9200;
|
||||
e.Port = 9200;
|
||||
e.IsProxied = true;
|
||||
e.IsExternal = false;
|
||||
})
|
||||
.WithEndpoint(
|
||||
"internal",
|
||||
e =>
|
||||
{
|
||||
e.TargetPort = 9300;
|
||||
e.Port = 9300;
|
||||
e.IsProxied = true;
|
||||
e.IsExternal = false;
|
||||
})
|
||||
.WithDataVolume("elastic-data");
|
||||
|
||||
if (builder.ExecutionContext.IsPublishMode)
|
||||
{
|
||||
elasticsearch.WithLifetime(ContainerLifetime.Persistent);
|
||||
}
|
||||
|
||||
var kibana = builder.AddContainer("kibana", "docker.elastic.co/kibana/kibana:8.17.0")
|
||||
.WithEnvironment("ELASTICSEARCH_HOSTS", "http://elasticsearch:9200")
|
||||
.WithEndpoint(port: 5601, targetPort: 5601, name: "kibana")
|
||||
.WithEndpoint(port: 5601, targetPort: 5601, name: "http", isProxied: true, isExternal: true)
|
||||
.WithReference(elasticsearch)
|
||||
.WaitFor(elasticsearch)
|
||||
.WithLifetime(ContainerLifetime.Persistent);
|
||||
.WaitFor(elasticsearch);
|
||||
|
||||
if (builder.ExecutionContext.IsPublishMode)
|
||||
{
|
||||
kibana.WithLifetime(ContainerLifetime.Persistent);
|
||||
}
|
||||
|
||||
// 5. Application Services
|
||||
var identity = builder.AddProject<Projects.Identity_Api>("identity-service")
|
||||
|
||||
41
src/Aspire/src/ServiceDefaults/Extensions.cs
Normal file
41
src/Aspire/src/ServiceDefaults/Extensions.cs
Normal file
@ -0,0 +1,41 @@
|
||||
using BuildingBlocks.HealthCheck;
|
||||
using BuildingBlocks.OpenTelemetryCollector;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace ServiceDefaults;
|
||||
|
||||
public static class Extensions
|
||||
{
|
||||
public static IHostApplicationBuilder AddServiceDefaults(this WebApplicationBuilder builder)
|
||||
{
|
||||
builder.Services.AddCustomHealthCheck();
|
||||
builder.AddCustomObservability();
|
||||
builder.Services.AddServiceDiscovery();
|
||||
|
||||
builder.Services.ConfigureHttpClientDefaults(http =>
|
||||
{
|
||||
http.AddStandardResilienceHandler(options =>
|
||||
{
|
||||
var timeSpan = TimeSpan.FromMinutes(1);
|
||||
options.CircuitBreaker.SamplingDuration = timeSpan * 2;
|
||||
options.TotalRequestTimeout.Timeout = timeSpan * 3;
|
||||
options.Retry.MaxRetryAttempts = 3;
|
||||
});
|
||||
|
||||
// Turn on service discovery by default
|
||||
http.AddServiceDiscovery();
|
||||
});
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static WebApplication UseServiceDefaults(this WebApplication app)
|
||||
{
|
||||
app.UseCustomHealthCheck();
|
||||
app.UseCustomObservability();
|
||||
|
||||
return app;
|
||||
}
|
||||
}
|
||||
13
src/Aspire/src/ServiceDefaults/ServiceDefaults.csproj
Normal file
13
src/Aspire/src/ServiceDefaults/ServiceDefaults.csproj
Normal file
@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\BuildingBlocks\BuildingBlocks.csproj" IsAspireProjectResource="false" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@ -3,69 +3,69 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Ardalis.GuardClauses" Version="5.0.0" />
|
||||
<PackageReference Include="Asp.Versioning.Abstractions" Version="8.1.0" />
|
||||
<PackageReference Include="Asp.Versioning.Http" Version="8.1.0" />
|
||||
<PackageReference Include="Asp.Versioning.Mvc" Version="8.1.0" />
|
||||
<PackageReference Include="Asp.Versioning.Mvc.ApiExplorer" Version="8.1.0" />
|
||||
<PackageReference Include="Asp.Versioning.Http" Version="8.1.1" />
|
||||
<PackageReference Include="Asp.Versioning.Mvc" Version="8.1.1" />
|
||||
<PackageReference Include="Asp.Versioning.Mvc.ApiExplorer" Version="8.1.1" />
|
||||
<PackageReference Include="Figgle.Fonts" Version="0.6.5" />
|
||||
<PackageReference Include="Grpc.Core.Testing" Version="2.46.6" />
|
||||
<PackageReference Include="EasyCaching.Core" Version="1.9.2" />
|
||||
<PackageReference Include="EasyCaching.InMemory" Version="1.9.2" />
|
||||
<PackageReference Include="EasyNetQ.Management.Client" Version="3.0.0" />
|
||||
<PackageReference Include="EFCore.NamingConventions" Version="9.0.0" />
|
||||
<PackageReference Include="Figgle" Version="0.5.1" />
|
||||
<PackageReference Include="FluentValidation" Version="11.11.0" />
|
||||
<PackageReference Include="FluentValidation.AspNetCore" Version="11.3.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.ServiceDiscovery" Version="9.3.1" />
|
||||
<PackageReference Include="Npgsql" Version="9.0.1" />
|
||||
<PackageReference Include="EasyNetQ.Management.Client" Version="3.0.1" />
|
||||
<PackageReference Include="EFCore.NamingConventions" Version="10.0.1" />
|
||||
<PackageReference Include="Figgle" Version="0.6.5" />
|
||||
<PackageReference Include="FluentValidation" Version="12.1.1" />
|
||||
<PackageReference Include="FluentValidation.AspNetCore" Version="11.3.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.3" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="10.0.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="10.0.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="10.3.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.ServiceDiscovery" Version="10.3.0" />
|
||||
<PackageReference Include="Npgsql" Version="10.0.1" />
|
||||
<PackageReference Include="NSubstitute" Version="5.3.0" />
|
||||
<PackageReference Include="Polly" Version="8.5.0" />
|
||||
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
|
||||
<PackageReference Include="Polly" Version="8.6.5" />
|
||||
<PackageReference Include="Humanizer.Core" Version="3.0.1" />
|
||||
<PackageReference Include="IdGen" Version="3.0.7" />
|
||||
<PackageReference Include="Mapster" Version="7.4.0" />
|
||||
<PackageReference Include="Mapster.DependencyInjection" Version="1.0.1" />
|
||||
<PackageReference Include="MediatR" Version="12.4.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.0" />
|
||||
<PackageReference Include="MongoDB.Driver" Version="3.1.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="Scalar.AspNetCore" Version="1.2.64" />
|
||||
<PackageReference Include="Scrutor" Version="5.0.2" />
|
||||
<PackageReference Include="MediatR" Version="14.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.3" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.3" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="10.0.3" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="10.0.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.3" />
|
||||
<PackageReference Include="MongoDB.Driver" Version="3.6.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
||||
<PackageReference Include="Scalar.AspNetCore" Version="2.12.38" />
|
||||
<PackageReference Include="Scrutor" Version="7.0.0" />
|
||||
<PackageReference Include="Sieve" Version="2.5.5" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="7.1.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="7.1.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="7.1.0" />
|
||||
<PackageReference Include="MassTransit" Version="8.3.6" />
|
||||
<PackageReference Include="MassTransit.RabbitMQ" Version="8.3.6" />
|
||||
<PackageReference Include="Duende.IdentityServer" Version="7.0.8" />
|
||||
<PackageReference Include="Duende.IdentityServer.AspNetIdentity" Version="7.0.8" />
|
||||
<PackageReference Include="Duende.IdentityServer.EntityFramework" Version="7.0.8" />
|
||||
<PackageReference Include="Duende.IdentityServer.EntityFramework.Storage" Version="7.0.8" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.0" />
|
||||
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
|
||||
<PackageReference Include="System.Linq.Async.Queryable" Version="6.0.1" />
|
||||
<PackageReference Include="Testcontainers" Version="4.0.0" />
|
||||
<PackageReference Include="Testcontainers.EventStoreDb" Version="4.0.0" />
|
||||
<PackageReference Include="Testcontainers.MongoDb" Version="4.0.0" />
|
||||
<PackageReference Include="Testcontainers.PostgreSql" Version="4.0.0" />
|
||||
<PackageReference Include="Testcontainers.RabbitMq" Version="4.0.0" />
|
||||
<PackageReference Include="Unchase.Swashbuckle.AspNetCore.Extensions" Version="2.7.1" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.1.2" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="10.1.2" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="10.1.2" />
|
||||
<PackageReference Include="MassTransit" Version="8.5.8" />
|
||||
<PackageReference Include="MassTransit.RabbitMQ" Version="8.5.8" />
|
||||
<PackageReference Include="Duende.IdentityServer" Version="7.4.5" />
|
||||
<PackageReference Include="Duende.IdentityServer.AspNetIdentity" Version="7.4.5" />
|
||||
<PackageReference Include="Duende.IdentityServer.EntityFramework" Version="7.4.5" />
|
||||
<PackageReference Include="Duende.IdentityServer.EntityFramework.Storage" Version="7.4.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="10.0.3" />
|
||||
<PackageReference Include="System.Linq.Async" Version="7.0.0" />
|
||||
<PackageReference Include="System.Linq.Async.Queryable" Version="7.0.0" />
|
||||
<PackageReference Include="Testcontainers" Version="4.9.0" />
|
||||
<PackageReference Include="Testcontainers.EventStoreDb" Version="4.9.0" />
|
||||
<PackageReference Include="Testcontainers.MongoDb" Version="4.9.0" />
|
||||
<PackageReference Include="Testcontainers.PostgreSql" Version="4.9.0" />
|
||||
<PackageReference Include="Testcontainers.RabbitMq" Version="4.9.0" />
|
||||
<PackageReference Include="xunit.abstractions" Version="2.0.3" />
|
||||
<PackageReference Include="xunit.extensibility.core" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.extensibility.core" Version="2.9.3" />
|
||||
<PackageReference Include="Xunit.Extensions.Logging" Version="1.1.0" />
|
||||
<PackageReference Include="Yarp.ReverseProxy" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="9.0.0" />
|
||||
<PackageReference Include="Yarp.ReverseProxy" Version="2.3.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="10.0.3" />
|
||||
<PackageReference Include="AspNetCore.HealthChecks.UI" Version="9.0.0" />
|
||||
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="9.0.0" />
|
||||
<PackageReference Include="AspNetCore.HealthChecks.UI.InMemory.Storage" Version="9.0.0" />
|
||||
@ -74,34 +74,34 @@
|
||||
<PackageReference Include="AspNetCore.HealthChecks.Rabbitmq" Version="9.0.0" />
|
||||
<PackageReference Include="AspNetCore.HealthChecks.MongoDb" Version="9.0.0" />
|
||||
|
||||
<PackageReference Include="Npgsql.OpenTelemetry" Version="9.0.1" />
|
||||
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.11.1" />
|
||||
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.11.1"/>
|
||||
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.11.0"/>
|
||||
<PackageReference Include="OpenTelemetry.Instrumentation.GrpcNetClient" Version="1.11.0-beta.1"/>
|
||||
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.11.0"/>
|
||||
<PackageReference Include="OpenTelemetry.Instrumentation.Process" Version="1.11.0-beta.1"/>
|
||||
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.11.0"/>
|
||||
<PackageReference Include="OpenTelemetry.Exporter.Prometheus.AspNetCore" Version="1.11.0-beta.1"/>
|
||||
<PackageReference Include="Grafana.OpenTelemetry" Version="1.2.0"/>
|
||||
<PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.11.1"/>
|
||||
<PackageReference Include="OpenTelemetry.Exporter.Zipkin" Version="1.11.1"/>
|
||||
<PackageReference Include="Npgsql.OpenTelemetry" Version="10.0.1" />
|
||||
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.15.0" />
|
||||
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.15.0" />
|
||||
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.15.0" />
|
||||
<PackageReference Include="OpenTelemetry.Instrumentation.GrpcNetClient" Version="1.15.0-beta.1" />
|
||||
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.15.0" />
|
||||
<PackageReference Include="OpenTelemetry.Instrumentation.Process" Version="1.15.0-beta.1" />
|
||||
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.15.0" />
|
||||
<PackageReference Include="OpenTelemetry.Exporter.Prometheus.AspNetCore" Version="1.15.0-beta.1" />
|
||||
<PackageReference Include="Grafana.OpenTelemetry" Version="1.5.2" />
|
||||
<PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.15.0" />
|
||||
<PackageReference Include="OpenTelemetry.Exporter.Zipkin" Version="1.15.0" />
|
||||
|
||||
<PackageReference Include="EventStore.Client.Grpc.Streams" Version="23.3.7" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.1" />
|
||||
<PackageReference Include="EventStore.Client.Grpc.Streams" Version="23.3.9" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.0" />
|
||||
|
||||
<PackageReference Include="AutoBogus" Version="2.13.1" />
|
||||
<PackageReference Include="Bogus" Version="35.6.1" />
|
||||
<PackageReference Include="FluentAssertions" Version="7.0.0" />
|
||||
<PackageReference Include="Respawn" Version="6.2.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.0" />
|
||||
<PackageReference Include="WebMotions.Fake.Authentication.JwtBearer" Version="8.0.1" />
|
||||
<PackageReference Include="Bogus" Version="35.6.5" />
|
||||
<PackageReference Include="FluentAssertions" Version="8.8.0" />
|
||||
<PackageReference Include="Respawn" Version="7.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="10.0.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.3" />
|
||||
<PackageReference Include="WebMotions.Fake.Authentication.JwtBearer" Version="10.0.0" />
|
||||
|
||||
<PackageReference Include="Google.Protobuf" Version="3.29.1" />
|
||||
<PackageReference Include="Grpc.Net.ClientFactory" Version="2.67.0" />
|
||||
<PackageReference Include="Google.Protobuf" Version="3.33.5" />
|
||||
<PackageReference Include="Grpc.Net.ClientFactory" Version="2.76.0" />
|
||||
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.0">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.3">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
using Microsoft.AspNetCore.OpenApi;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Microsoft.OpenApi;
|
||||
|
||||
public class SecuritySchemeDocumentTransformer : IOpenApiDocumentTransformer
|
||||
{
|
||||
@ -9,12 +9,14 @@ public class SecuritySchemeDocumentTransformer : IOpenApiDocumentTransformer
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
document.Components ??= new();
|
||||
document.Components ??= new OpenApiComponents();
|
||||
|
||||
// Bearer token scheme
|
||||
document.Components.SecuritySchemes.Add(
|
||||
"Bearer",
|
||||
new OpenApiSecurityScheme
|
||||
// Initialize with the correct interface type
|
||||
document.Components.SecuritySchemes ??= new Dictionary<string, IOpenApiSecurityScheme>();
|
||||
|
||||
var securitySchemes = new Dictionary<string, IOpenApiSecurityScheme>
|
||||
{
|
||||
["Bearer"] = new OpenApiSecurityScheme
|
||||
{
|
||||
Name = "Authorization",
|
||||
Type = SecuritySchemeType.Http,
|
||||
@ -23,21 +25,23 @@ public class SecuritySchemeDocumentTransformer : IOpenApiDocumentTransformer
|
||||
In = ParameterLocation.Header,
|
||||
Description =
|
||||
"Enter 'Bearer' [space] and your token in the text input below.\n\nExample: 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'",
|
||||
}
|
||||
);
|
||||
|
||||
// API Key scheme
|
||||
document.Components.SecuritySchemes.Add(
|
||||
"ApiKey",
|
||||
new OpenApiSecurityScheme
|
||||
},
|
||||
["ApiKey"] = new OpenApiSecurityScheme
|
||||
{
|
||||
Name = "X-API-KEY",
|
||||
Type = SecuritySchemeType.ApiKey,
|
||||
In = ParameterLocation.Header,
|
||||
Description =
|
||||
"Enter your API key in the text input below.\n\nExample: '12345-abcdef'",
|
||||
Description = "Enter your API key in the text input below.\n\nExample: '12345-abcdef'",
|
||||
},
|
||||
};
|
||||
|
||||
foreach (var (key, scheme) in securitySchemes)
|
||||
{
|
||||
if (!document.Components.SecuritySchemes.ContainsKey(key))
|
||||
{
|
||||
document.Components.SecuritySchemes.Add(key, scheme);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
@ -162,10 +162,7 @@ public static class Extensions
|
||||
{
|
||||
instrumentationOptions.RecordException = true;
|
||||
})
|
||||
.AddEntityFrameworkCoreInstrumentation(instrumentationOptions =>
|
||||
{
|
||||
instrumentationOptions.SetDbStatementForText = true;
|
||||
})
|
||||
.AddEntityFrameworkCoreInstrumentation()
|
||||
.AddSource(DiagnosticHeaders.DefaultListenerName)
|
||||
.AddNpgsql()
|
||||
// `AddSource` for adding custom activity sources
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
using System.Net;
|
||||
using System.Security.Claims;
|
||||
using Ardalis.GuardClauses;
|
||||
using BuildingBlocks.Core.Event;
|
||||
using BuildingBlocks.Core.Model;
|
||||
using BuildingBlocks.EFCore;
|
||||
@ -13,7 +12,6 @@ using Grpc.Net.Client;
|
||||
using MassTransit;
|
||||
using MassTransit.Testing;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.Testing;
|
||||
@ -38,11 +36,11 @@ using Testcontainers.PostgreSql;
|
||||
using Testcontainers.RabbitMq;
|
||||
|
||||
public class TestFixture<TEntryPoint> : IAsyncLifetime
|
||||
where TEntryPoint : class
|
||||
where TEntryPoint : class
|
||||
{
|
||||
private readonly WebApplicationFactory<TEntryPoint> _factory;
|
||||
private int Timeout => 120; // Second
|
||||
private ITestHarness TestHarness => ServiceProvider?.GetTestHarness();
|
||||
public ITestHarness TestHarness => ServiceProvider?.GetTestHarness();
|
||||
private Action<IServiceCollection> TestRegistrationServices { get; set; }
|
||||
private PostgreSqlContainer PostgresTestcontainer;
|
||||
private PostgreSqlContainer PostgresPersistTestContainer;
|
||||
@ -59,11 +57,11 @@ where TEntryPoint : class
|
||||
get
|
||||
{
|
||||
var claims = new Dictionary<string, object>
|
||||
{
|
||||
{ ClaimTypes.Name, "test@sample.com" },
|
||||
{ ClaimTypes.Role, "admin" },
|
||||
{ "scope", "flight-api" }
|
||||
};
|
||||
{
|
||||
{ ClaimTypes.Name, "test@sample.com" },
|
||||
{ ClaimTypes.Role, "admin" },
|
||||
{ "scope", "flight-api" },
|
||||
};
|
||||
|
||||
var httpClient = _factory.CreateClient();
|
||||
httpClient.SetFakeBearerToken(claims); // Uses FakeJwtBearer
|
||||
@ -72,9 +70,7 @@ where TEntryPoint : class
|
||||
}
|
||||
|
||||
public GrpcChannel Channel =>
|
||||
GrpcChannel.ForAddress(
|
||||
HttpClient.BaseAddress!,
|
||||
new GrpcChannelOptions { HttpClient = HttpClient });
|
||||
GrpcChannel.ForAddress(HttpClient.BaseAddress!, new GrpcChannelOptions { HttpClient = HttpClient });
|
||||
|
||||
public IServiceProvider ServiceProvider => _factory?.Services;
|
||||
public IConfiguration Configuration => _factory?.Services.GetRequiredService<IConfiguration>();
|
||||
@ -82,54 +78,55 @@ where TEntryPoint : class
|
||||
|
||||
protected TestFixture()
|
||||
{
|
||||
_factory = new WebApplicationFactory<TEntryPoint>()
|
||||
.WithWebHostBuilder(
|
||||
builder =>
|
||||
_factory = new WebApplicationFactory<TEntryPoint>().WithWebHostBuilder(builder =>
|
||||
{
|
||||
builder.ConfigureAppConfiguration(AddCustomAppSettings);
|
||||
|
||||
builder.UseEnvironment("test");
|
||||
|
||||
builder.ConfigureServices(services =>
|
||||
{
|
||||
TestRegistrationServices?.Invoke(services);
|
||||
services.ReplaceSingleton(AddHttpContextAccessorMock);
|
||||
|
||||
services.AddSingleton<PersistMessageBackgroundService>();
|
||||
services.RemoveHostedService<PersistMessageBackgroundService>();
|
||||
|
||||
// Register all ITestDataSeeder implementations dynamically
|
||||
services.Scan(scan =>
|
||||
scan.FromApplicationDependencies() // Scan the current app and its dependencies
|
||||
.AddClasses(classes => classes.AssignableTo<ITestDataSeeder>()) // Find classes that implement ITestDataSeeder
|
||||
.AsImplementedInterfaces()
|
||||
.WithScopedLifetime()
|
||||
);
|
||||
|
||||
// Add Fake JWT Authentication - we can use SetAdminUser method to set authenticate user to existing HttContextAccessor
|
||||
// https://github.com/webmotions/fake-authentication-jwtbearer
|
||||
// https://github.com/webmotions/fake-authentication-jwtbearer/issues/14
|
||||
services
|
||||
.AddAuthentication(options =>
|
||||
{
|
||||
options.DefaultAuthenticateScheme = FakeJwtBearerDefaults.AuthenticationScheme;
|
||||
|
||||
options.DefaultChallengeScheme = FakeJwtBearerDefaults.AuthenticationScheme;
|
||||
})
|
||||
.AddFakeJwtBearer();
|
||||
|
||||
// Mock Authorization Policies
|
||||
services.AddAuthorization(options =>
|
||||
{
|
||||
builder.ConfigureAppConfiguration(AddCustomAppSettings);
|
||||
|
||||
builder.UseEnvironment("test");
|
||||
|
||||
builder.ConfigureServices(
|
||||
services =>
|
||||
options.AddPolicy(
|
||||
nameof(ApiScope),
|
||||
policy =>
|
||||
{
|
||||
TestRegistrationServices?.Invoke(services);
|
||||
services.ReplaceSingleton(AddHttpContextAccessorMock);
|
||||
|
||||
services.AddSingleton<PersistMessageBackgroundService>();
|
||||
services.RemoveHostedService<PersistMessageBackgroundService>();
|
||||
|
||||
// Register all ITestDataSeeder implementations dynamically
|
||||
services.Scan(scan => scan
|
||||
.FromApplicationDependencies() // Scan the current app and its dependencies
|
||||
.AddClasses(classes => classes.AssignableTo<ITestDataSeeder>()) // Find classes that implement ITestDataSeeder
|
||||
.AsImplementedInterfaces()
|
||||
.WithScopedLifetime());
|
||||
|
||||
// Add Fake JWT Authentication - we can use SetAdminUser method to set authenticate user to existing HttContextAccessor
|
||||
// https://github.com/webmotions/fake-authentication-jwtbearer
|
||||
// https://github.com/webmotions/fake-authentication-jwtbearer/issues/14
|
||||
services.AddAuthentication(
|
||||
options =>
|
||||
{
|
||||
options.DefaultAuthenticateScheme = FakeJwtBearerDefaults.AuthenticationScheme;
|
||||
|
||||
options.DefaultChallengeScheme = FakeJwtBearerDefaults.AuthenticationScheme;
|
||||
})
|
||||
.AddFakeJwtBearer();
|
||||
|
||||
// Mock Authorization Policies
|
||||
services.AddAuthorization(options =>
|
||||
{
|
||||
options.AddPolicy(nameof(ApiScope), policy =>
|
||||
{
|
||||
policy.AddAuthenticationSchemes(FakeJwtBearerDefaults.AuthenticationScheme);
|
||||
policy.RequireAuthenticatedUser();
|
||||
policy.RequireClaim("scope", "flight-api"); // Test-specific scope
|
||||
});
|
||||
});
|
||||
});
|
||||
policy.AddAuthenticationSchemes(FakeJwtBearerDefaults.AuthenticationScheme);
|
||||
policy.RequireAuthenticatedUser();
|
||||
policy.RequireClaim("scope", "flight-api"); // Test-specific scope
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public async Task InitializeAsync()
|
||||
@ -157,10 +154,10 @@ where TEntryPoint : class
|
||||
return null;
|
||||
|
||||
var loggerFactory = LoggerFactory.Create(builder =>
|
||||
{
|
||||
builder.AddXunit(output);
|
||||
builder.SetMinimumLevel(LogLevel.Debug);
|
||||
});
|
||||
{
|
||||
builder.AddXunit(output);
|
||||
builder.SetMinimumLevel(LogLevel.Debug);
|
||||
});
|
||||
return loggerFactory.CreateLogger("TestLogger");
|
||||
}
|
||||
|
||||
@ -179,132 +176,93 @@ where TEntryPoint : class
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public Task<TResponse> SendAsync<TResponse>(IRequest<TResponse> request)
|
||||
{
|
||||
return ExecuteScopeAsync(
|
||||
sp =>
|
||||
{
|
||||
var mediator = sp.GetRequiredService<IMediator>();
|
||||
return ExecuteScopeAsync(sp =>
|
||||
{
|
||||
var mediator = sp.GetRequiredService<IMediator>();
|
||||
|
||||
return mediator.Send(request);
|
||||
});
|
||||
return mediator.Send(request);
|
||||
});
|
||||
}
|
||||
|
||||
public Task SendAsync(IRequest request)
|
||||
{
|
||||
return ExecuteScopeAsync(
|
||||
sp =>
|
||||
{
|
||||
var mediator = sp.GetRequiredService<IMediator>();
|
||||
return mediator.Send(request);
|
||||
});
|
||||
return ExecuteScopeAsync(sp =>
|
||||
{
|
||||
var mediator = sp.GetRequiredService<IMediator>();
|
||||
return mediator.Send(request);
|
||||
});
|
||||
}
|
||||
|
||||
public async Task Publish<TMessage>(
|
||||
TMessage message,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
where TMessage : class, IEvent
|
||||
public async Task Publish<TMessage>(TMessage message, CancellationToken cancellationToken = default)
|
||||
where TMessage : class, IEvent
|
||||
{
|
||||
// Use harness bus to ensure publish happens only after the bus is started.
|
||||
await TestHarness.Bus.Publish(message, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<bool> WaitForPublishing<TMessage>(
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
where TMessage : class, IEvent
|
||||
public async Task<bool> WaitForPublishing<TMessage>(CancellationToken cancellationToken = default)
|
||||
where TMessage : class, IEvent
|
||||
{
|
||||
var result = await WaitUntilConditionMet(
|
||||
async () =>
|
||||
{
|
||||
var published =
|
||||
await TestHarness.Published.Any<TMessage>(cancellationToken);
|
||||
async () =>
|
||||
{
|
||||
var published = await TestHarness.Published.Any<TMessage>(cancellationToken);
|
||||
|
||||
return published;
|
||||
});
|
||||
return published;
|
||||
},
|
||||
cancellationToken: cancellationToken
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<bool> WaitForConsuming<TMessage>(
|
||||
public Task<bool> WaitUntilAsync(
|
||||
Func<Task<bool>> condition,
|
||||
TimeSpan? timeout = null,
|
||||
TimeSpan? pollInterval = null,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
where TMessage : class, IEvent
|
||||
{
|
||||
var result = await WaitUntilConditionMet(
|
||||
async () =>
|
||||
{
|
||||
var consumed =
|
||||
await TestHarness.Consumed.Any<TMessage>(cancellationToken);
|
||||
var effectiveTimeout = timeout ?? TimeSpan.FromSeconds(Timeout);
|
||||
var effectivePollInterval = pollInterval ?? TimeSpan.FromMilliseconds(200);
|
||||
|
||||
return consumed;
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<bool> ShouldProcessedPersistInternalCommand<TInternalCommand>(
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
where TInternalCommand : class, IInternalCommand
|
||||
{
|
||||
var result = await WaitUntilConditionMet(
|
||||
async () =>
|
||||
{
|
||||
return await ExecuteScopeAsync(
|
||||
async sp =>
|
||||
{
|
||||
var persistMessageProcessor =
|
||||
sp.GetService<IPersistMessageProcessor>();
|
||||
|
||||
Guard.Against.Null(
|
||||
persistMessageProcessor,
|
||||
nameof(persistMessageProcessor));
|
||||
|
||||
var filter =
|
||||
await persistMessageProcessor.GetByFilterAsync(
|
||||
x =>
|
||||
x.DeliveryType ==
|
||||
MessageDeliveryType.Internal &&
|
||||
typeof(TInternalCommand).ToString() ==
|
||||
x.DataType);
|
||||
|
||||
var res = filter.Any(
|
||||
x => x.MessageStatus == MessageStatus.Processed);
|
||||
|
||||
return res;
|
||||
});
|
||||
});
|
||||
|
||||
return result;
|
||||
return WaitUntilConditionMet(
|
||||
conditionToMet: async () =>
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return await condition();
|
||||
},
|
||||
timeoutSecond: (int)Math.Ceiling(effectiveTimeout.TotalSeconds),
|
||||
pollInterval: effectivePollInterval,
|
||||
cancellationToken: cancellationToken
|
||||
);
|
||||
}
|
||||
|
||||
// Ref: https://tech.energyhelpline.com/in-memory-testing-with-masstransit/
|
||||
private async Task<bool> WaitUntilConditionMet(
|
||||
Func<Task<bool>> conditionToMet,
|
||||
int? timeoutSecond = null
|
||||
int? timeoutSecond = null,
|
||||
TimeSpan? pollInterval = null,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
var time = timeoutSecond ?? Timeout;
|
||||
var delay = pollInterval ?? TimeSpan.FromMilliseconds(100);
|
||||
|
||||
var startTime = DateTime.Now;
|
||||
var timeoutExpired = false;
|
||||
var meet = await conditionToMet.Invoke();
|
||||
|
||||
while (!meet)
|
||||
var startTime = DateTime.UtcNow;
|
||||
while (DateTime.UtcNow - startTime <= TimeSpan.FromSeconds(time))
|
||||
{
|
||||
if (timeoutExpired)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
await Task.Delay(100);
|
||||
meet = await conditionToMet.Invoke();
|
||||
timeoutExpired = DateTime.Now - startTime > TimeSpan.FromSeconds(time);
|
||||
if (await conditionToMet.Invoke())
|
||||
return true;
|
||||
|
||||
await Task.Delay(delay, cancellationToken);
|
||||
}
|
||||
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
private async Task StartTestContainerAsync()
|
||||
@ -337,39 +295,25 @@ where TEntryPoint : class
|
||||
configuration.AddInMemoryCollection(
|
||||
new KeyValuePair<string, string>[]
|
||||
{
|
||||
new(
|
||||
"PostgresOptions:ConnectionString",
|
||||
PostgresTestcontainer.GetConnectionString()),
|
||||
new(
|
||||
"PostgresOptions:ConnectionString:Flight",
|
||||
PostgresTestcontainer.GetConnectionString()),
|
||||
new(
|
||||
"PostgresOptions:ConnectionString:Identity",
|
||||
PostgresTestcontainer.GetConnectionString()),
|
||||
new(
|
||||
"PostgresOptions:ConnectionString:Passenger",
|
||||
PostgresTestcontainer.GetConnectionString()),
|
||||
new(
|
||||
"PersistMessageOptions:ConnectionString",
|
||||
PostgresPersistTestContainer.GetConnectionString()),
|
||||
new("RabbitMqOptions:HostName", RabbitMqTestContainer.Hostname),
|
||||
new(
|
||||
"RabbitMqOptions:UserName",
|
||||
TestContainers.RabbitMqContainerConfiguration.UserName),
|
||||
new(
|
||||
"RabbitMqOptions:Password",
|
||||
TestContainers.RabbitMqContainerConfiguration.Password),
|
||||
new("PostgresOptions:ConnectionString", PostgresTestcontainer.GetConnectionString()),
|
||||
new("PostgresOptions:ConnectionString:Flight", PostgresTestcontainer.GetConnectionString()),
|
||||
new("PostgresOptions:ConnectionString:Identity", PostgresTestcontainer.GetConnectionString()),
|
||||
new("PostgresOptions:ConnectionString:Passenger", PostgresTestcontainer.GetConnectionString()),
|
||||
new("PersistMessageOptions:ConnectionString", PostgresPersistTestContainer.GetConnectionString()),
|
||||
new("RabbitMqOptions:HostName", "127.0.0.1"),
|
||||
new("RabbitMqOptions:UserName", TestContainers.RabbitMqContainerConfiguration.UserName),
|
||||
new("RabbitMqOptions:Password", TestContainers.RabbitMqContainerConfiguration.Password),
|
||||
new(
|
||||
"RabbitMqOptions:Port",
|
||||
RabbitMqTestContainer.GetMappedPublicPort(
|
||||
TestContainers.RabbitMqContainerConfiguration.Port)
|
||||
.ToString(NumberFormatInfo.InvariantInfo)),
|
||||
RabbitMqTestContainer
|
||||
.GetMappedPublicPort(TestContainers.RabbitMqContainerConfiguration.Port)
|
||||
.ToString(NumberFormatInfo.InvariantInfo)
|
||||
),
|
||||
new("MongoOptions:ConnectionString", MongoDbTestContainer.GetConnectionString()),
|
||||
new("MongoOptions:DatabaseName", TestContainers.MongoContainerConfiguration.Name),
|
||||
new(
|
||||
"EventStoreOptions:ConnectionString",
|
||||
EventStoreDbTestContainer.GetConnectionString())
|
||||
});
|
||||
new("EventStoreOptions:ConnectionString", EventStoreDbTestContainer.GetConnectionString()),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private IHttpContextAccessor AddHttpContextAccessorMock(IServiceProvider serviceProvider)
|
||||
@ -377,8 +321,7 @@ where TEntryPoint : class
|
||||
var httpContextAccessorMock = Substitute.For<IHttpContextAccessor>();
|
||||
using var scope = serviceProvider.CreateScope();
|
||||
|
||||
httpContextAccessorMock.HttpContext = new DefaultHttpContext
|
||||
{ RequestServices = scope.ServiceProvider };
|
||||
httpContextAccessorMock.HttpContext = new DefaultHttpContext { RequestServices = scope.ServiceProvider };
|
||||
|
||||
httpContextAccessorMock.HttpContext.Request.Host = new HostString("localhost", 6012);
|
||||
httpContextAccessorMock.HttpContext.Request.Scheme = "http";
|
||||
@ -388,100 +331,90 @@ where TEntryPoint : class
|
||||
}
|
||||
|
||||
public class TestWriteFixture<TEntryPoint, TWContext> : TestFixture<TEntryPoint>
|
||||
where TEntryPoint : class
|
||||
where TWContext : DbContext
|
||||
where TEntryPoint : class
|
||||
where TWContext : DbContext
|
||||
{
|
||||
public Task ExecuteDbContextAsync(Func<TWContext, Task> action)
|
||||
{
|
||||
return ExecuteScopeAsync(sp => action(sp.GetService<TWContext>()));
|
||||
return ExecuteScopeAsync(sp => action(sp.GetRequiredService<TWContext>()));
|
||||
}
|
||||
|
||||
public Task ExecuteDbContextAsync(Func<TWContext, ValueTask> action)
|
||||
{
|
||||
return ExecuteScopeAsync(sp => action(sp.GetService<TWContext>()).AsTask());
|
||||
return ExecuteScopeAsync(sp => action(sp.GetRequiredService<TWContext>()).AsTask());
|
||||
}
|
||||
|
||||
public Task ExecuteDbContextAsync(Func<TWContext, IMediator, Task> action)
|
||||
{
|
||||
return ExecuteScopeAsync(
|
||||
sp => action(sp.GetService<TWContext>(), sp.GetService<IMediator>()));
|
||||
return ExecuteScopeAsync(sp => action(sp.GetRequiredService<TWContext>(), sp.GetRequiredService<IMediator>()));
|
||||
}
|
||||
|
||||
public Task<T> ExecuteDbContextAsync<T>(Func<TWContext, Task<T>> action)
|
||||
{
|
||||
return ExecuteScopeAsync(sp => action(sp.GetService<TWContext>()));
|
||||
return ExecuteScopeAsync(sp => action(sp.GetRequiredService<TWContext>()));
|
||||
}
|
||||
|
||||
public Task<T> ExecuteDbContextAsync<T>(Func<TWContext, ValueTask<T>> action)
|
||||
{
|
||||
return ExecuteScopeAsync(sp => action(sp.GetService<TWContext>()).AsTask());
|
||||
return ExecuteScopeAsync(sp => action(sp.GetRequiredService<TWContext>()).AsTask());
|
||||
}
|
||||
|
||||
public Task<T> ExecuteDbContextAsync<T>(Func<TWContext, IMediator, Task<T>> action)
|
||||
{
|
||||
return ExecuteScopeAsync(
|
||||
sp => action(sp.GetService<TWContext>(), sp.GetService<IMediator>()));
|
||||
return ExecuteScopeAsync(sp => action(sp.GetRequiredService<TWContext>(), sp.GetRequiredService<IMediator>()));
|
||||
}
|
||||
|
||||
public Task InsertAsync<T>(params T[] entities)
|
||||
where T : class
|
||||
where T : class
|
||||
{
|
||||
return ExecuteDbContextAsync(
|
||||
db =>
|
||||
return ExecuteDbContextAsync(db =>
|
||||
{
|
||||
foreach (var entity in entities)
|
||||
{
|
||||
foreach (var entity in entities)
|
||||
{
|
||||
db.Set<T>().Add(entity);
|
||||
}
|
||||
db.Set<T>().Add(entity);
|
||||
}
|
||||
|
||||
return db.SaveChangesAsync();
|
||||
});
|
||||
return db.SaveChangesAsync();
|
||||
});
|
||||
}
|
||||
|
||||
public async Task InsertAsync<TEntity>(TEntity entity)
|
||||
where TEntity : class
|
||||
where TEntity : class
|
||||
{
|
||||
await ExecuteDbContextAsync(
|
||||
db =>
|
||||
{
|
||||
db.Set<TEntity>().Add(entity);
|
||||
await ExecuteDbContextAsync(db =>
|
||||
{
|
||||
db.Set<TEntity>().Add(entity);
|
||||
|
||||
return db.SaveChangesAsync();
|
||||
});
|
||||
return db.SaveChangesAsync();
|
||||
});
|
||||
}
|
||||
|
||||
public Task InsertAsync<TEntity, TEntity2>(TEntity entity, TEntity2 entity2)
|
||||
where TEntity : class
|
||||
where TEntity2 : class
|
||||
where TEntity : class
|
||||
where TEntity2 : class
|
||||
{
|
||||
return ExecuteDbContextAsync(
|
||||
db =>
|
||||
{
|
||||
db.Set<TEntity>().Add(entity);
|
||||
db.Set<TEntity2>().Add(entity2);
|
||||
return ExecuteDbContextAsync(db =>
|
||||
{
|
||||
db.Set<TEntity>().Add(entity);
|
||||
db.Set<TEntity2>().Add(entity2);
|
||||
|
||||
return db.SaveChangesAsync();
|
||||
});
|
||||
return db.SaveChangesAsync();
|
||||
});
|
||||
}
|
||||
|
||||
public Task InsertAsync<TEntity, TEntity2, TEntity3>(
|
||||
TEntity entity,
|
||||
TEntity2 entity2,
|
||||
TEntity3 entity3
|
||||
)
|
||||
where TEntity : class
|
||||
where TEntity2 : class
|
||||
where TEntity3 : class
|
||||
public Task InsertAsync<TEntity, TEntity2, TEntity3>(TEntity entity, TEntity2 entity2, TEntity3 entity3)
|
||||
where TEntity : class
|
||||
where TEntity2 : class
|
||||
where TEntity3 : class
|
||||
{
|
||||
return ExecuteDbContextAsync(
|
||||
db =>
|
||||
{
|
||||
db.Set<TEntity>().Add(entity);
|
||||
db.Set<TEntity2>().Add(entity2);
|
||||
db.Set<TEntity3>().Add(entity3);
|
||||
return ExecuteDbContextAsync(db =>
|
||||
{
|
||||
db.Set<TEntity>().Add(entity);
|
||||
db.Set<TEntity2>().Add(entity2);
|
||||
db.Set<TEntity3>().Add(entity3);
|
||||
|
||||
return db.SaveChangesAsync();
|
||||
});
|
||||
return db.SaveChangesAsync();
|
||||
});
|
||||
}
|
||||
|
||||
public Task InsertAsync<TEntity, TEntity2, TEntity3, TEntity4>(
|
||||
@ -490,39 +423,38 @@ where TWContext : DbContext
|
||||
TEntity3 entity3,
|
||||
TEntity4 entity4
|
||||
)
|
||||
where TEntity : class
|
||||
where TEntity2 : class
|
||||
where TEntity3 : class
|
||||
where TEntity4 : class
|
||||
where TEntity : class
|
||||
where TEntity2 : class
|
||||
where TEntity3 : class
|
||||
where TEntity4 : class
|
||||
{
|
||||
return ExecuteDbContextAsync(
|
||||
db =>
|
||||
{
|
||||
db.Set<TEntity>().Add(entity);
|
||||
db.Set<TEntity2>().Add(entity2);
|
||||
db.Set<TEntity3>().Add(entity3);
|
||||
db.Set<TEntity4>().Add(entity4);
|
||||
return ExecuteDbContextAsync(db =>
|
||||
{
|
||||
db.Set<TEntity>().Add(entity);
|
||||
db.Set<TEntity2>().Add(entity2);
|
||||
db.Set<TEntity3>().Add(entity3);
|
||||
db.Set<TEntity4>().Add(entity4);
|
||||
|
||||
return db.SaveChangesAsync();
|
||||
});
|
||||
return db.SaveChangesAsync();
|
||||
});
|
||||
}
|
||||
|
||||
public Task<T> FindAsync<T, TKey>(TKey id)
|
||||
where T : class, IEntity
|
||||
where T : class, IEntity
|
||||
{
|
||||
return ExecuteDbContextAsync(db => db.Set<T>().FindAsync(id).AsTask());
|
||||
}
|
||||
|
||||
public Task<T> FirstOrDefaultAsync<T>()
|
||||
where T : class, IEntity
|
||||
where T : class, IEntity
|
||||
{
|
||||
return ExecuteDbContextAsync(db => db.Set<T>().FirstOrDefaultAsync());
|
||||
}
|
||||
}
|
||||
|
||||
public class TestReadFixture<TEntryPoint, TRContext> : TestFixture<TEntryPoint>
|
||||
where TEntryPoint : class
|
||||
where TRContext : MongoDbContext
|
||||
where TEntryPoint : class
|
||||
where TRContext : MongoDbContext
|
||||
{
|
||||
public Task ExecuteReadContextAsync(Func<TRContext, Task> action)
|
||||
{
|
||||
@ -535,21 +467,19 @@ where TRContext : MongoDbContext
|
||||
}
|
||||
|
||||
public async Task InsertMongoDbContextAsync<T>(string collectionName, params T[] entities)
|
||||
where T : class
|
||||
where T : class
|
||||
{
|
||||
await ExecuteReadContextAsync(
|
||||
async db =>
|
||||
{
|
||||
await db.GetCollection<T>(collectionName).InsertManyAsync(entities.ToList());
|
||||
});
|
||||
await ExecuteReadContextAsync(async db =>
|
||||
{
|
||||
await db.GetCollection<T>(collectionName).InsertManyAsync(entities.ToList());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public class TestFixture<TEntryPoint, TWContext, TRContext>
|
||||
: TestWriteFixture<TEntryPoint, TWContext>
|
||||
where TEntryPoint : class
|
||||
where TWContext : DbContext
|
||||
where TRContext : MongoDbContext
|
||||
public class TestFixture<TEntryPoint, TWContext, TRContext> : TestWriteFixture<TEntryPoint, TWContext>
|
||||
where TEntryPoint : class
|
||||
where TWContext : DbContext
|
||||
where TRContext : MongoDbContext
|
||||
{
|
||||
public Task ExecuteReadContextAsync(Func<TRContext, Task> action)
|
||||
{
|
||||
@ -562,38 +492,38 @@ where TRContext : MongoDbContext
|
||||
}
|
||||
|
||||
public async Task InsertMongoDbContextAsync<T>(string collectionName, params T[] entities)
|
||||
where T : class
|
||||
where T : class
|
||||
{
|
||||
await ExecuteReadContextAsync(
|
||||
async db =>
|
||||
{
|
||||
await db.GetCollection<T>(collectionName).InsertManyAsync(entities.ToList());
|
||||
});
|
||||
await ExecuteReadContextAsync(async db =>
|
||||
{
|
||||
await db.GetCollection<T>(collectionName).InsertManyAsync(entities.ToList());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public class TestFixtureCore<TEntryPoint> : IAsyncLifetime
|
||||
where TEntryPoint : class
|
||||
where TEntryPoint : class
|
||||
{
|
||||
private Respawner _reSpawnerDefaultDb;
|
||||
private Respawner _reSpawnerPersistDb;
|
||||
private NpgsqlConnection DefaultDbConnection { get; set; }
|
||||
private NpgsqlConnection PersistDbConnection { get; set; }
|
||||
|
||||
private Type _dbContextType;
|
||||
|
||||
public TestFixtureCore(
|
||||
TestFixture<TEntryPoint> integrationTestFixture,
|
||||
ITestOutputHelper outputHelper
|
||||
ITestOutputHelper outputHelper,
|
||||
Type dbContextType = null
|
||||
)
|
||||
{
|
||||
Fixture = integrationTestFixture;
|
||||
integrationTestFixture.RegisterServices(RegisterTestsServices);
|
||||
integrationTestFixture.Logger = integrationTestFixture.CreateLogger(outputHelper);
|
||||
_dbContextType = dbContextType;
|
||||
}
|
||||
|
||||
public TestFixture<TEntryPoint> Fixture { get; }
|
||||
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
await InitPostgresAsync();
|
||||
@ -613,25 +543,37 @@ where TEntryPoint : class
|
||||
|
||||
if (!string.IsNullOrEmpty(persistOptions?.ConnectionString))
|
||||
{
|
||||
await Fixture.PersistMessageBackgroundService.StartAsync(
|
||||
Fixture.CancellationTokenSource.Token);
|
||||
|
||||
PersistDbConnection = new NpgsqlConnection(persistOptions.ConnectionString);
|
||||
await PersistDbConnection.OpenAsync();
|
||||
|
||||
using var scope = Fixture.ServiceProvider.CreateScope();
|
||||
var dbContext = scope.ServiceProvider.GetRequiredService<PersistMessageDbContext>();
|
||||
await dbContext.Database.EnsureCreatedAsync();
|
||||
|
||||
await Fixture.PersistMessageBackgroundService.StartAsync(Fixture.CancellationTokenSource.Token);
|
||||
|
||||
_reSpawnerPersistDb = await Respawner.CreateAsync(
|
||||
PersistDbConnection,
|
||||
new RespawnerOptions { DbAdapter = DbAdapter.Postgres });
|
||||
PersistDbConnection,
|
||||
new RespawnerOptions { DbAdapter = DbAdapter.Postgres }
|
||||
);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(postgresOptions?.ConnectionString))
|
||||
if (!string.IsNullOrEmpty(postgresOptions?.ConnectionString) && _dbContextType != null)
|
||||
{
|
||||
DefaultDbConnection = new NpgsqlConnection(postgresOptions.ConnectionString);
|
||||
await DefaultDbConnection.OpenAsync();
|
||||
|
||||
using var scope = Fixture.ServiceProvider.CreateScope();
|
||||
|
||||
if (scope.ServiceProvider.GetRequiredService(_dbContextType) is DbContext dbContext)
|
||||
{
|
||||
await dbContext.Database.EnsureCreatedAsync();
|
||||
}
|
||||
|
||||
_reSpawnerDefaultDb = await Respawner.CreateAsync(
|
||||
DefaultDbConnection,
|
||||
new RespawnerOptions { DbAdapter = DbAdapter.Postgres });
|
||||
DefaultDbConnection,
|
||||
new RespawnerOptions { DbAdapter = DbAdapter.Postgres, TablesToIgnore = ["__EFMigrationsHistory"] }
|
||||
);
|
||||
|
||||
await SeedDataAsync();
|
||||
}
|
||||
@ -643,8 +585,7 @@ where TEntryPoint : class
|
||||
{
|
||||
await _reSpawnerPersistDb.ResetAsync(PersistDbConnection);
|
||||
|
||||
await Fixture.PersistMessageBackgroundService.StopAsync(
|
||||
Fixture.CancellationTokenSource.Token);
|
||||
await Fixture.PersistMessageBackgroundService.StopAsync(Fixture.CancellationTokenSource.Token);
|
||||
}
|
||||
|
||||
if (DefaultDbConnection is not null)
|
||||
@ -659,31 +600,33 @@ where TEntryPoint : class
|
||||
var dbClient = new MongoClient(Fixture.MongoDbTestContainer?.GetConnectionString());
|
||||
|
||||
var collections = await dbClient
|
||||
.GetDatabase(TestContainers.MongoContainerConfiguration.Name)
|
||||
.ListCollectionsAsync(cancellationToken: cancellationToken);
|
||||
.GetDatabase(TestContainers.MongoContainerConfiguration.Name)
|
||||
.ListCollectionsAsync(cancellationToken: cancellationToken);
|
||||
|
||||
foreach (var collection in collections.ToList())
|
||||
{
|
||||
await dbClient.GetDatabase(TestContainers.MongoContainerConfiguration.Name)
|
||||
await dbClient
|
||||
.GetDatabase(TestContainers.MongoContainerConfiguration.Name)
|
||||
.DropCollectionAsync(collection["name"].AsString, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ResetRabbitMqAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var port = Fixture.RabbitMqTestContainer?.GetMappedPublicPort(
|
||||
TestContainers.RabbitMqContainerConfiguration
|
||||
.ApiPort) ??
|
||||
TestContainers.RabbitMqContainerConfiguration.ApiPort;
|
||||
var port =
|
||||
Fixture.RabbitMqTestContainer?.GetMappedPublicPort(TestContainers.RabbitMqContainerConfiguration.ApiPort)
|
||||
?? TestContainers.RabbitMqContainerConfiguration.ApiPort;
|
||||
|
||||
var managementClient = new ManagementClient(Fixture.RabbitMqTestContainer?.Hostname,
|
||||
var managementClient = new ManagementClient(
|
||||
Fixture.RabbitMqTestContainer?.Hostname,
|
||||
TestContainers.RabbitMqContainerConfiguration?.UserName,
|
||||
TestContainers.RabbitMqContainerConfiguration?.Password, port);
|
||||
TestContainers.RabbitMqContainerConfiguration?.Password,
|
||||
port
|
||||
);
|
||||
|
||||
var bd = await managementClient.GetBindingsAsync(cancellationToken);
|
||||
|
||||
var bindings = bd.Where(
|
||||
x => !string.IsNullOrEmpty(x.Source) && !string.IsNullOrEmpty(x.Destination));
|
||||
var bindings = bd.Where(x => !string.IsNullOrEmpty(x.Source) && !string.IsNullOrEmpty(x.Destination));
|
||||
|
||||
foreach (var binding in bindings)
|
||||
{
|
||||
@ -698,9 +641,7 @@ where TEntryPoint : class
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void RegisterTestsServices(IServiceCollection services)
|
||||
{
|
||||
}
|
||||
protected virtual void RegisterTestsServices(IServiceCollection services) { }
|
||||
|
||||
private async Task SeedDataAsync()
|
||||
{
|
||||
@ -712,14 +653,15 @@ where TEntryPoint : class
|
||||
}
|
||||
|
||||
public abstract class TestReadBase<TEntryPoint, TRContext> : TestFixtureCore<TEntryPoint>
|
||||
// ,IClassFixture<IntegrationTestFactory<TEntryPoint, TWContext>>
|
||||
where TEntryPoint : class
|
||||
where TRContext : MongoDbContext
|
||||
// ,IClassFixture<IntegrationTestFactory<TEntryPoint, TWContext>>
|
||||
where TEntryPoint : class
|
||||
where TRContext : MongoDbContext
|
||||
{
|
||||
protected TestReadBase(
|
||||
TestReadFixture<TEntryPoint, TRContext> integrationTestFixture,
|
||||
ITestOutputHelper outputHelper = null
|
||||
) : base(integrationTestFixture, outputHelper)
|
||||
)
|
||||
: base(integrationTestFixture, outputHelper)
|
||||
{
|
||||
Fixture = integrationTestFixture;
|
||||
}
|
||||
@ -728,14 +670,15 @@ where TRContext : MongoDbContext
|
||||
}
|
||||
|
||||
public abstract class TestWriteBase<TEntryPoint, TWContext> : TestFixtureCore<TEntryPoint>
|
||||
//,IClassFixture<IntegrationTestFactory<TEntryPoint, TWContext>>
|
||||
where TEntryPoint : class
|
||||
where TWContext : DbContext
|
||||
//,IClassFixture<IntegrationTestFactory<TEntryPoint, TWContext>>
|
||||
where TEntryPoint : class
|
||||
where TWContext : DbContext
|
||||
{
|
||||
protected TestWriteBase(
|
||||
TestWriteFixture<TEntryPoint, TWContext> integrationTestFixture,
|
||||
ITestOutputHelper outputHelper = null
|
||||
) : base(integrationTestFixture, outputHelper)
|
||||
)
|
||||
: base(integrationTestFixture, outputHelper, typeof(TWContext))
|
||||
{
|
||||
Fixture = integrationTestFixture;
|
||||
}
|
||||
@ -744,19 +687,19 @@ where TWContext : DbContext
|
||||
}
|
||||
|
||||
public abstract class TestBase<TEntryPoint, TWContext, TRContext> : TestFixtureCore<TEntryPoint>
|
||||
//,IClassFixture<IntegrationTestFactory<TEntryPoint, TWContext, TRContext>>
|
||||
where TEntryPoint : class
|
||||
where TWContext : DbContext
|
||||
where TRContext : MongoDbContext
|
||||
//,IClassFixture<IntegrationTestFactory<TEntryPoint, TWContext, TRContext>>
|
||||
where TEntryPoint : class
|
||||
where TWContext : DbContext
|
||||
where TRContext : MongoDbContext
|
||||
{
|
||||
protected TestBase(
|
||||
TestFixture<TEntryPoint, TWContext, TRContext> integrationTestFixture,
|
||||
ITestOutputHelper outputHelper = null
|
||||
) :
|
||||
base(integrationTestFixture, outputHelper)
|
||||
)
|
||||
: base(integrationTestFixture, outputHelper, typeof(TWContext))
|
||||
{
|
||||
Fixture = integrationTestFixture;
|
||||
}
|
||||
|
||||
public TestFixture<TEntryPoint, TWContext, TRContext> Fixture { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
using DotNet.Testcontainers.Builders;
|
||||
|
||||
namespace BuildingBlocks.TestBase;
|
||||
|
||||
using Testcontainers.EventStoreDb;
|
||||
@ -18,15 +20,19 @@ public static class TestContainers
|
||||
{
|
||||
var configuration = ConfigurationHelper.GetConfiguration();
|
||||
|
||||
RabbitMqContainerConfiguration =
|
||||
configuration.GetOptions<RabbitMqContainerOptions>(nameof(RabbitMqContainerOptions));
|
||||
PostgresContainerConfiguration =
|
||||
configuration.GetOptions<PostgresContainerOptions>(nameof(PostgresContainerOptions));
|
||||
PostgresPersistContainerConfiguration =
|
||||
configuration.GetOptions<PostgresPersistContainerOptions>(nameof(PostgresPersistContainerOptions));
|
||||
RabbitMqContainerConfiguration = configuration.GetOptions<RabbitMqContainerOptions>(
|
||||
nameof(RabbitMqContainerOptions)
|
||||
);
|
||||
PostgresContainerConfiguration = configuration.GetOptions<PostgresContainerOptions>(
|
||||
nameof(PostgresContainerOptions)
|
||||
);
|
||||
PostgresPersistContainerConfiguration = configuration.GetOptions<PostgresPersistContainerOptions>(
|
||||
nameof(PostgresPersistContainerOptions)
|
||||
);
|
||||
MongoContainerConfiguration = configuration.GetOptions<MongoContainerOptions>(nameof(MongoContainerOptions));
|
||||
EventStoreContainerConfiguration =
|
||||
configuration.GetOptions<EventStoreContainerOptions>(nameof(EventStoreContainerOptions));
|
||||
EventStoreContainerConfiguration = configuration.GetOptions<EventStoreContainerOptions>(
|
||||
nameof(EventStoreContainerOptions)
|
||||
);
|
||||
}
|
||||
|
||||
public static PostgreSqlContainer PostgresTestContainer()
|
||||
@ -98,8 +104,7 @@ public static class TestContainers
|
||||
|
||||
public static EventStoreDbContainer EventStoreTestContainer()
|
||||
{
|
||||
var baseBuilder = new EventStoreDbBuilder()
|
||||
.WithLabel("Key", "Value");
|
||||
var baseBuilder = new EventStoreDbBuilder().WithLabel("Key", "Value");
|
||||
|
||||
var builder = baseBuilder
|
||||
.WithImage(EventStoreContainerConfiguration.ImageName)
|
||||
|
||||
@ -1,47 +1,39 @@
|
||||
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS builder
|
||||
WORKDIR /
|
||||
# ---------- Build Stage ----------
|
||||
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
|
||||
WORKDIR /src
|
||||
|
||||
COPY ./.editorconfig ./
|
||||
COPY ./global.json ./
|
||||
COPY ./Directory.Build.props ./
|
||||
# Copy solution-level files
|
||||
COPY .editorconfig .
|
||||
COPY global.json .
|
||||
COPY Directory.Build.props .
|
||||
|
||||
# Setup working directory for the project
|
||||
COPY ./src/BuildingBlocks/BuildingBlocks.csproj ./src/BuildingBlocks/
|
||||
COPY ./src/Services/Booking/src/Booking/Booking.csproj ./src/Services/Booking/src/Booking/
|
||||
COPY ./src/Services/Booking/src/Booking.Api/Booking.Api.csproj ./src/Services/Booking/src/Booking.Api/
|
||||
# Copy project files first (for Docker layer caching)
|
||||
COPY src/BuildingBlocks/BuildingBlocks.csproj src/BuildingBlocks/
|
||||
COPY src/Services/Booking/src/Booking/Booking.csproj src/Services/Booking/src/Booking/
|
||||
COPY src/Services/Booking/src/Booking.Api/Booking.Api.csproj src/Services/Booking/src/Booking.Api/
|
||||
COPY src/Aspire/src/ServiceDefaults/ServiceDefaults.csproj src/Aspire/src/ServiceDefaults/
|
||||
|
||||
# Restore dependencies
|
||||
RUN dotnet restore src/Services/Booking/src/Booking.Api/Booking.Api.csproj
|
||||
|
||||
# Restore nuget packages
|
||||
RUN dotnet restore ./src/Services/Booking/src/Booking.Api/Booking.Api.csproj
|
||||
# Copy the rest of the source
|
||||
COPY src ./src
|
||||
|
||||
# Copy project files
|
||||
COPY ./src/BuildingBlocks ./src/BuildingBlocks/
|
||||
COPY ./src/Services/Booking/src/Booking/ ./src/Services/Booking/src/Booking/
|
||||
COPY ./src/Services/Booking/src/Booking.Api/ ./src/Services/Booking/src/Booking.Api/
|
||||
# Publish (build included)
|
||||
RUN dotnet publish src/Services/Booking/src/Booking.Api/Booking.Api.csproj \
|
||||
-c Release \
|
||||
-o /app/publish \
|
||||
--no-restore
|
||||
|
||||
# Build project with Release configuration
|
||||
# and no restore, as we did it already
|
||||
# ---------- Runtime Stage ----------
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime
|
||||
WORKDIR /app
|
||||
|
||||
RUN ls
|
||||
RUN dotnet build -c Release --no-restore ./src/Services/Booking/src/Booking.Api/Booking.Api.csproj
|
||||
COPY --from=build /app/publish .
|
||||
|
||||
WORKDIR /src/Services/Booking/src/Booking.Api
|
||||
|
||||
# Publish project to output folder
|
||||
# and no build, as we did it already
|
||||
RUN dotnet publish -c Release --no-build -o out
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:9.0
|
||||
|
||||
# Setup working directory for the project
|
||||
WORKDIR /
|
||||
COPY --from=builder /src/Services/Booking/src/Booking.Api/out .
|
||||
|
||||
ENV ASPNETCORE_URLS https://*:443, http://*:80
|
||||
ENV ASPNETCORE_ENVIRONMENT docker
|
||||
ENV ASPNETCORE_URLS=http://+:80
|
||||
ENV ASPNETCORE_ENVIRONMENT=docker
|
||||
|
||||
EXPOSE 80
|
||||
EXPOSE 443
|
||||
|
||||
ENTRYPOINT ["dotnet", "Booking.Api.dll"]
|
||||
|
||||
ENTRYPOINT ["dotnet", "Booking.Api.dll"]
|
||||
@ -1,50 +0,0 @@
|
||||
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS builder
|
||||
WORKDIR /
|
||||
|
||||
COPY ./.editorconfig ./
|
||||
COPY ./global.json ./
|
||||
COPY ./Directory.Build.props ./
|
||||
|
||||
# Setup working directory for the project
|
||||
COPY ./src/BuildingBlocks/BuildingBlocks.csproj ./src/BuildingBlocks/
|
||||
COPY ./src/Services/Booking/src/Booking/Booking.csproj ./src/Services/Booking/src/Booking/
|
||||
COPY ./src/Services/Booking/src/Booking.Api/Booking.Api.csproj ./src/Services/Booking/src/Booking.Api/
|
||||
|
||||
|
||||
# Restore nuget packages
|
||||
RUN --mount=type=cache,id=booking_nuget,target=/root/.nuget/packages \
|
||||
dotnet restore ./src/Services/Booking/src/Booking.Api/Booking.Api.csproj
|
||||
|
||||
# Copy project files
|
||||
COPY ./src/BuildingBlocks ./src/BuildingBlocks/
|
||||
COPY ./src/Services/Booking/src/Booking/ ./src/Services/Booking/src/Booking/
|
||||
COPY ./src/Services/Booking/src/Booking.Api/ ./src/Services/Booking/src/Booking.Api/
|
||||
|
||||
# Build project with Release configuration
|
||||
# and no restore, as we did it already
|
||||
|
||||
RUN ls
|
||||
RUN --mount=type=cache,id=booking_nuget,target=/root/.nuget/packages\
|
||||
dotnet build -c Release --no-restore ./src/Services/Booking/src/Booking.Api/Booking.Api.csproj
|
||||
|
||||
WORKDIR /src/Services/Booking/src/Booking.Api
|
||||
|
||||
# Publish project to output folder
|
||||
# and no build, as we did it already
|
||||
RUN --mount=type=cache,id=booking_nuget,target=/root/.nuget/packages\
|
||||
dotnet publish -c Release --no-build -o out
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:9.0
|
||||
|
||||
# Setup working directory for the project
|
||||
WORKDIR /
|
||||
COPY --from=builder /src/Services/Booking/src/Booking.Api/out .
|
||||
|
||||
ENV ASPNETCORE_URLS https://*:443, http://*:80
|
||||
ENV ASPNETCORE_ENVIRONMENT docker
|
||||
|
||||
EXPOSE 80
|
||||
EXPOSE 443
|
||||
|
||||
ENTRYPOINT ["dotnet", "Booking.Api.dll"]
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Grpc.Tools" Version="2.68.1">
|
||||
<PackageReference Include="Grpc.Tools" Version="2.78.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
@ -14,6 +14,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\Aspire\src\ServiceDefaults\ServiceDefaults.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\BuildingBlocks\BuildingBlocks.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@ -13,9 +13,12 @@ public static class GrpcClientExtensions
|
||||
public static IServiceCollection AddGrpcClients(this IServiceCollection services)
|
||||
{
|
||||
var grpcOptions = services.GetOptions<GrpcOptions>("Grpc");
|
||||
var resilienceOptions = services.GetOptions<HttpStandardResilienceOptions>(nameof(HttpStandardResilienceOptions));
|
||||
var resilienceOptions = services.GetOptions<HttpStandardResilienceOptions>(
|
||||
nameof(HttpStandardResilienceOptions)
|
||||
);
|
||||
|
||||
services.AddGrpcClient<FlightGrpcService.FlightGrpcServiceClient>(o =>
|
||||
services
|
||||
.AddGrpcClient<FlightGrpcService.FlightGrpcServiceClient>(o =>
|
||||
{
|
||||
o.Address = new Uri(grpcOptions.FlightAddress);
|
||||
})
|
||||
@ -25,54 +28,37 @@ public static class GrpcClientExtensions
|
||||
{
|
||||
var timeSpan = TimeSpan.FromMinutes(1);
|
||||
|
||||
options.AddRetry(
|
||||
new HttpRetryStrategyOptions
|
||||
{
|
||||
MaxRetryAttempts = 3,
|
||||
});
|
||||
options.AddRetry(new HttpRetryStrategyOptions { MaxRetryAttempts = 3 });
|
||||
|
||||
options.AddCircuitBreaker(
|
||||
new HttpCircuitBreakerStrategyOptions
|
||||
{
|
||||
SamplingDuration = timeSpan * 2,
|
||||
});
|
||||
new HttpCircuitBreakerStrategyOptions { SamplingDuration = timeSpan * 2 }
|
||||
);
|
||||
|
||||
options.AddTimeout(
|
||||
new HttpTimeoutStrategyOptions
|
||||
{
|
||||
Timeout = timeSpan * 3,
|
||||
});
|
||||
});
|
||||
options.AddTimeout(new HttpTimeoutStrategyOptions { Timeout = timeSpan * 3 });
|
||||
}
|
||||
);
|
||||
|
||||
services.AddGrpcClient<PassengerGrpcService.PassengerGrpcServiceClient>(o =>
|
||||
{
|
||||
o.Address = new Uri(grpcOptions.PassengerAddress);
|
||||
})
|
||||
.AddResilienceHandler(
|
||||
"grpc-passenger-resilience",
|
||||
options =>
|
||||
services
|
||||
.AddGrpcClient<PassengerGrpcService.PassengerGrpcServiceClient>(o =>
|
||||
{
|
||||
var timeSpan = TimeSpan.FromMinutes(1);
|
||||
o.Address = new Uri(grpcOptions.PassengerAddress);
|
||||
})
|
||||
.AddResilienceHandler(
|
||||
"grpc-passenger-resilience",
|
||||
options =>
|
||||
{
|
||||
var timeSpan = TimeSpan.FromMinutes(1);
|
||||
|
||||
options.AddRetry(
|
||||
new HttpRetryStrategyOptions
|
||||
{
|
||||
MaxRetryAttempts = 3,
|
||||
});
|
||||
options.AddRetry(new HttpRetryStrategyOptions { MaxRetryAttempts = 3 });
|
||||
|
||||
options.AddCircuitBreaker(
|
||||
new HttpCircuitBreakerStrategyOptions
|
||||
{
|
||||
SamplingDuration = timeSpan * 2,
|
||||
});
|
||||
options.AddCircuitBreaker(
|
||||
new HttpCircuitBreakerStrategyOptions { SamplingDuration = timeSpan * 2 }
|
||||
);
|
||||
|
||||
options.AddTimeout(
|
||||
new HttpTimeoutStrategyOptions
|
||||
{
|
||||
Timeout = timeSpan * 3,
|
||||
});
|
||||
});
|
||||
options.AddTimeout(new HttpTimeoutStrategyOptions { Timeout = timeSpan * 3 });
|
||||
}
|
||||
);
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,12 +12,14 @@ using BuildingBlocks.PersistMessageProcessor;
|
||||
using BuildingBlocks.ProblemDetails;
|
||||
using BuildingBlocks.Web;
|
||||
using Figgle;
|
||||
using Figgle.Fonts;
|
||||
using FluentValidation;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using ServiceDefaults;
|
||||
|
||||
namespace Booking.Extensions.Infrastructure;
|
||||
|
||||
@ -28,25 +30,7 @@ public static class InfrastructureExtensions
|
||||
var configuration = builder.Configuration;
|
||||
var env = builder.Environment;
|
||||
|
||||
builder.Services.AddCustomHealthCheck();
|
||||
|
||||
builder.AddCustomObservability();
|
||||
|
||||
builder.Services.AddServiceDiscovery();
|
||||
|
||||
builder.Services.ConfigureHttpClientDefaults(http =>
|
||||
{
|
||||
http.AddStandardResilienceHandler(options =>
|
||||
{
|
||||
var timeSpan = TimeSpan.FromMinutes(1);
|
||||
options.CircuitBreaker.SamplingDuration = timeSpan * 2;
|
||||
options.TotalRequestTimeout.Timeout = timeSpan * 3;
|
||||
options.Retry.MaxRetryAttempts = 3;
|
||||
});
|
||||
|
||||
// Turn on service discovery by default
|
||||
http.AddServiceDiscovery();
|
||||
});
|
||||
builder.AddServiceDefaults();
|
||||
|
||||
builder.Services.AddScoped<ICurrentUserProvider, CurrentUserProvider>();
|
||||
builder.Services.AddScoped<IEventMapper, BookingEventMapper>();
|
||||
@ -94,8 +78,7 @@ public static class InfrastructureExtensions
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
app.UseCustomHealthCheck();
|
||||
app.UseCustomObservability();
|
||||
app.UseServiceDefaults();
|
||||
|
||||
app.UseCustomProblemDetails();
|
||||
app.UseCorrelationId();
|
||||
|
||||
@ -19,10 +19,8 @@ namespace Integration.Test.Booking.Features
|
||||
{
|
||||
public class CreateBookingTests : BookingIntegrationTestBase
|
||||
{
|
||||
public CreateBookingTests(TestReadFixture<Program, BookingReadDbContext> integrationTestFixture) : base(
|
||||
integrationTestFixture)
|
||||
{
|
||||
}
|
||||
public CreateBookingTests(TestReadFixture<Program, BookingReadDbContext> integrationTestFixture)
|
||||
: base(integrationTestFixture) { }
|
||||
|
||||
protected override void RegisterTestsServices(IServiceCollection services)
|
||||
{
|
||||
@ -40,46 +38,81 @@ namespace Integration.Test.Booking.Features
|
||||
var response = await Fixture.SendAsync(command);
|
||||
|
||||
// Assert
|
||||
response?.Id.Should().BeGreaterOrEqualTo(0);
|
||||
response?.Id.Should().BeGreaterThanOrEqualTo(0);
|
||||
|
||||
(await Fixture.WaitForPublishing<BookingCreated>()).Should().Be(true);
|
||||
}
|
||||
|
||||
|
||||
private void MockPassengerGrpcServices(IServiceCollection services)
|
||||
{
|
||||
services.Replace(ServiceDescriptor.Singleton(x =>
|
||||
{
|
||||
var mockPassenger = Substitute.For<PassengerGrpcService.PassengerGrpcServiceClient>();
|
||||
services.Replace(
|
||||
ServiceDescriptor.Singleton(x =>
|
||||
{
|
||||
var mockPassenger = Substitute.For<PassengerGrpcService.PassengerGrpcServiceClient>();
|
||||
|
||||
mockPassenger.GetByIdAsync(Arg.Any<Passenger.GetByIdRequest>())
|
||||
.Returns(TestCalls.AsyncUnaryCall(Task.FromResult(FakePassengerResponse.Generate()),
|
||||
Task.FromResult(new Metadata()), () => Status.DefaultSuccess, () => new Metadata(), () => { }));
|
||||
mockPassenger
|
||||
.GetByIdAsync(Arg.Any<Passenger.GetByIdRequest>())
|
||||
.Returns(
|
||||
TestCalls.AsyncUnaryCall(
|
||||
Task.FromResult(FakePassengerResponse.Generate()),
|
||||
Task.FromResult(new Metadata()),
|
||||
() => Status.DefaultSuccess,
|
||||
() => new Metadata(),
|
||||
() => { }
|
||||
)
|
||||
);
|
||||
|
||||
return mockPassenger;
|
||||
}));
|
||||
return mockPassenger;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private void MockFlightGrpcServices(IServiceCollection services)
|
||||
{
|
||||
services.Replace(ServiceDescriptor.Singleton(x =>
|
||||
{
|
||||
var mockFlight = Substitute.For<FlightGrpcService.FlightGrpcServiceClient>();
|
||||
services.Replace(
|
||||
ServiceDescriptor.Singleton(x =>
|
||||
{
|
||||
var mockFlight = Substitute.For<FlightGrpcService.FlightGrpcServiceClient>();
|
||||
|
||||
mockFlight.GetByIdAsync(Arg.Any<GetByIdRequest>())
|
||||
.Returns(TestCalls.AsyncUnaryCall(Task.FromResult(FakeFlightResponse.Generate()),
|
||||
Task.FromResult(new Metadata()), () => Status.DefaultSuccess, () => new Metadata(), () => { }));
|
||||
mockFlight
|
||||
.GetByIdAsync(Arg.Any<GetByIdRequest>())
|
||||
.Returns(
|
||||
TestCalls.AsyncUnaryCall(
|
||||
Task.FromResult(FakeFlightResponse.Generate()),
|
||||
Task.FromResult(new Metadata()),
|
||||
() => Status.DefaultSuccess,
|
||||
() => new Metadata(),
|
||||
() => { }
|
||||
)
|
||||
);
|
||||
|
||||
mockFlight.GetAvailableSeatsAsync(Arg.Any<GetAvailableSeatsRequest>())
|
||||
.Returns(TestCalls.AsyncUnaryCall(Task.FromResult(FakeGetAvailableSeatsResponse.Generate()),
|
||||
Task.FromResult(new Metadata()), () => Status.DefaultSuccess, () => new Metadata(), () => { }));
|
||||
mockFlight
|
||||
.GetAvailableSeatsAsync(Arg.Any<GetAvailableSeatsRequest>())
|
||||
.Returns(
|
||||
TestCalls.AsyncUnaryCall(
|
||||
Task.FromResult(FakeGetAvailableSeatsResponse.Generate()),
|
||||
Task.FromResult(new Metadata()),
|
||||
() => Status.DefaultSuccess,
|
||||
() => new Metadata(),
|
||||
() => { }
|
||||
)
|
||||
);
|
||||
|
||||
mockFlight.ReserveSeatAsync(Arg.Any<ReserveSeatRequest>())
|
||||
.Returns(TestCalls.AsyncUnaryCall(Task.FromResult(FakeReserveSeatResponse.Generate()),
|
||||
Task.FromResult(new Metadata()), () => Status.DefaultSuccess, () => new Metadata(), () => { }));
|
||||
mockFlight
|
||||
.ReserveSeatAsync(Arg.Any<ReserveSeatRequest>())
|
||||
.Returns(
|
||||
TestCalls.AsyncUnaryCall(
|
||||
Task.FromResult(FakeReserveSeatResponse.Generate()),
|
||||
Task.FromResult(new Metadata()),
|
||||
() => Status.DefaultSuccess,
|
||||
() => new Metadata(),
|
||||
() => { }
|
||||
)
|
||||
);
|
||||
|
||||
return mockFlight;
|
||||
}));
|
||||
return mockFlight;
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
using Booking.Api;
|
||||
using Booking.Data;
|
||||
using BuildingBlocks.TestBase;
|
||||
using Xunit;
|
||||
@ -8,13 +7,12 @@ namespace Integration.Test;
|
||||
[Collection(IntegrationTestCollection.Name)]
|
||||
public class BookingIntegrationTestBase : TestReadBase<Program, BookingReadDbContext>
|
||||
{
|
||||
public BookingIntegrationTestBase(TestReadFixture<Program, BookingReadDbContext> integrationTestFixture) : base(integrationTestFixture)
|
||||
{
|
||||
}
|
||||
public BookingIntegrationTestBase(TestReadFixture<Program, BookingReadDbContext> integrationTestFixture)
|
||||
: base(integrationTestFixture) { }
|
||||
}
|
||||
|
||||
[CollectionDefinition(Name)]
|
||||
public class IntegrationTestCollection : ICollectionFixture<TestReadFixture<Program, BookingReadDbContext>>
|
||||
{
|
||||
public const string Name = "Booking Integration Test";
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
namespace Integration.Test.Fakes;
|
||||
|
||||
using Flight;
|
||||
using MassTransit;
|
||||
|
||||
|
||||
@ -7,9 +7,9 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
|
||||
<PackageReference Include="xunit" Version="2.9.3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
||||
@ -1,48 +1,39 @@
|
||||
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS builder
|
||||
WORKDIR /
|
||||
# ---------- Build Stage ----------
|
||||
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
|
||||
WORKDIR /src
|
||||
|
||||
COPY ./.editorconfig ./
|
||||
COPY ./global.json ./
|
||||
COPY ./Directory.Build.props ./
|
||||
# Copy solution-level files
|
||||
COPY .editorconfig .
|
||||
COPY global.json .
|
||||
COPY Directory.Build.props .
|
||||
|
||||
# Setup working directory for the project
|
||||
COPY ./src/BuildingBlocks/BuildingBlocks.csproj ./src/BuildingBlocks/
|
||||
COPY ./src/Services/Flight/src/Flight/Flight.csproj ./src/Services/Flight/src/Flight/
|
||||
COPY ./src/Services/Flight/src/Flight.Api/Flight.Api.csproj ./src/Services/Flight/src/Flight.Api/
|
||||
# Copy project files first (better layer caching)
|
||||
COPY src/BuildingBlocks/BuildingBlocks.csproj src/BuildingBlocks/
|
||||
COPY src/Services/Flight/src/Flight/Flight.csproj src/Services/Flight/src/Flight/
|
||||
COPY src/Services/Flight/src/Flight.Api/Flight.Api.csproj src/Services/Flight/src/Flight.Api/
|
||||
COPY src/Aspire/src/ServiceDefaults/ServiceDefaults.csproj src/Aspire/src/ServiceDefaults/
|
||||
|
||||
# Restore dependencies
|
||||
RUN dotnet restore src/Services/Flight/src/Flight.Api/Flight.Api.csproj
|
||||
|
||||
# Restore nuget packages
|
||||
RUN dotnet restore ./src/Services/Flight/src/Flight.Api/Flight.Api.csproj
|
||||
# Copy remaining source code
|
||||
COPY src ./src
|
||||
|
||||
# Copy project files
|
||||
COPY ./src/BuildingBlocks ./src/BuildingBlocks/
|
||||
COPY ./src/Services/Flight/src/Flight/ ./src/Services/Flight/src/Flight/
|
||||
COPY ./src/Services/Flight/src/Flight.Api/ ./src/Services/Flight/src/Flight.Api/
|
||||
# Publish (build included)
|
||||
RUN dotnet publish src/Services/Flight/src/Flight.Api/Flight.Api.csproj \
|
||||
-c Release \
|
||||
-o /app/publish \
|
||||
--no-restore
|
||||
|
||||
# Build project with Release configuration
|
||||
# and no restore, as we did it already
|
||||
# ---------- Runtime Stage ----------
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime
|
||||
WORKDIR /app
|
||||
|
||||
RUN ls
|
||||
RUN dotnet build -c Release --no-restore ./src/Services/Flight/src/Flight.Api/Flight.Api.csproj
|
||||
COPY --from=build /app/publish .
|
||||
|
||||
WORKDIR /src/Services/Flight/src/Flight.Api
|
||||
|
||||
# Publish project to output folder
|
||||
# and no build, as we did it already
|
||||
RUN dotnet publish -c Release --no-build -o out
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:9.0
|
||||
|
||||
# Setup working directory for the project
|
||||
WORKDIR /
|
||||
COPY --from=builder /src/Services/Flight/src/Flight.Api/out .
|
||||
|
||||
|
||||
ENV ASPNETCORE_URLS https://*:443, http://*:80
|
||||
ENV ASPNETCORE_ENVIRONMENT docker
|
||||
ENV ASPNETCORE_URLS=http://+:80
|
||||
ENV ASPNETCORE_ENVIRONMENT=docker
|
||||
|
||||
EXPOSE 80
|
||||
EXPOSE 443
|
||||
|
||||
ENTRYPOINT ["dotnet", "Flight.Api.dll"]
|
||||
|
||||
ENTRYPOINT ["dotnet", "Flight.Api.dll"]
|
||||
@ -1,51 +0,0 @@
|
||||
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS builder
|
||||
WORKDIR /
|
||||
|
||||
COPY ./.editorconfig ./
|
||||
COPY ./global.json ./
|
||||
COPY ./Directory.Build.props ./
|
||||
|
||||
# Setup working directory for the project
|
||||
COPY ./src/BuildingBlocks/BuildingBlocks.csproj ./src/BuildingBlocks/
|
||||
COPY ./src/Services/Flight/src/Flight/Flight.csproj ./src/Services/Flight/src/Flight/
|
||||
COPY ./src/Services/Flight/src/Flight.Api/Flight.Api.csproj ./src/Services/Flight/src/Flight.Api/
|
||||
|
||||
|
||||
# Restore nuget packages
|
||||
RUN --mount=type=cache,id=flight_nuget,target=/root/.nuget/packages \
|
||||
dotnet restore ./src/Services/Flight/src/Flight.Api/Flight.Api.csproj
|
||||
|
||||
# Copy project files
|
||||
COPY ./src/BuildingBlocks ./src/BuildingBlocks/
|
||||
COPY ./src/Services/Flight/src/Flight/ ./src/Services/Flight/src/Flight/
|
||||
COPY ./src/Services/Flight/src/Flight.Api/ ./src/Services/Flight/src/Flight.Api/
|
||||
|
||||
# Build project with Release configuration
|
||||
# and no restore, as we did it already
|
||||
|
||||
RUN ls
|
||||
RUN --mount=type=cache,id=flight_nuget,target=/root/.nuget/packages \
|
||||
dotnet build -c Release --no-restore ./src/Services/Flight/src/Flight.Api/Flight.Api.csproj
|
||||
|
||||
WORKDIR /src/Services/Flight/src/Flight.Api
|
||||
|
||||
# Publish project to output folder
|
||||
# and no build, as we did it already
|
||||
RUN --mount=type=cache,id=flight_nuget,target=/root/.nuget/packages \
|
||||
dotnet publish -c Release --no-build -o out
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:9.0
|
||||
|
||||
# Setup working directory for the project
|
||||
WORKDIR /
|
||||
COPY --from=builder /src/Services/Flight/src/Flight.Api/out .
|
||||
|
||||
|
||||
ENV ASPNETCORE_URLS https://*:443, http://*:80
|
||||
ENV ASPNETCORE_ENVIRONMENT docker
|
||||
|
||||
EXPOSE 80
|
||||
EXPOSE 443
|
||||
|
||||
ENTRYPOINT ["dotnet", "Flight.Api.dll"]
|
||||
|
||||
@ -12,6 +12,7 @@ using BuildingBlocks.PersistMessageProcessor;
|
||||
using BuildingBlocks.ProblemDetails;
|
||||
using BuildingBlocks.Web;
|
||||
using Figgle;
|
||||
using Figgle.Fonts;
|
||||
using Flight.Data;
|
||||
using Flight.Data.Seed;
|
||||
using Flight.GrpcServer.Services;
|
||||
@ -21,6 +22,7 @@ using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using ServiceDefaults;
|
||||
|
||||
namespace Flight.Extensions.Infrastructure;
|
||||
|
||||
@ -32,25 +34,7 @@ public static class InfrastructureExtensions
|
||||
var configuration = builder.Configuration;
|
||||
var env = builder.Environment;
|
||||
|
||||
builder.Services.AddCustomHealthCheck();
|
||||
|
||||
builder.AddCustomObservability();
|
||||
|
||||
builder.Services.AddServiceDiscovery();
|
||||
|
||||
builder.Services.ConfigureHttpClientDefaults(http =>
|
||||
{
|
||||
http.AddStandardResilienceHandler(options =>
|
||||
{
|
||||
var timeSpan = TimeSpan.FromMinutes(1);
|
||||
options.CircuitBreaker.SamplingDuration = timeSpan * 2;
|
||||
options.TotalRequestTimeout.Timeout = timeSpan * 3;
|
||||
options.Retry.MaxRetryAttempts = 3;
|
||||
});
|
||||
|
||||
// Turn on service discovery by default
|
||||
http.AddServiceDiscovery();
|
||||
});
|
||||
builder.AddServiceDefaults();
|
||||
|
||||
builder.Services.AddScoped<ICurrentUserProvider, CurrentUserProvider>();
|
||||
builder.Services.AddScoped<IEventMapper, FlightEventMapper>();
|
||||
@ -100,8 +84,7 @@ public static class InfrastructureExtensions
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
app.UseCustomHealthCheck();
|
||||
app.UseCustomObservability();
|
||||
app.UseServiceDefaults();
|
||||
|
||||
app.UseCustomProblemDetails();
|
||||
app.UseCorrelationId();
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Grpc.AspNetCore" Version="2.67.0" />
|
||||
<PackageReference Include="Grpc.Net.Client" Version="2.67.0" />
|
||||
<PackageReference Include="Grpc.Tools" Version="2.68.1">
|
||||
<PackageReference Include="Grpc.AspNetCore" Version="2.76.0" />
|
||||
<PackageReference Include="Grpc.Net.Client" Version="2.76.0" />
|
||||
<PackageReference Include="Grpc.Tools" Version="2.78.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.0">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.3">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
@ -22,6 +22,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\Aspire\src\ServiceDefaults\ServiceDefaults.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\BuildingBlocks\BuildingBlocks.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
|
||||
namespace Flight.Flights.Dtos;
|
||||
|
||||
public record FlightDto(Guid Id, string FlightNumber, Guid AircraftId, Guid DepartureAirportId,
|
||||
DateTime DepartureDate, DateTime ArriveDate, Guid ArriveAirportId, decimal DurationMinutes, DateTime FlightDate,
|
||||
Enums.FlightStatus Status, decimal Price);
|
||||
@ -1,4 +1,5 @@
|
||||
namespace Flight.Flights.ValueObjects;
|
||||
|
||||
using System;
|
||||
using Flight.Flights.Exceptions;
|
||||
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
namespace Flight.Flights.ValueObjects;
|
||||
|
||||
using System;
|
||||
using Flight.Flights.Exceptions;
|
||||
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
namespace Flight.Flights.ValueObjects;
|
||||
|
||||
using System;
|
||||
using Flight.Flights.Exceptions;
|
||||
|
||||
|
||||
@ -7,9 +7,9 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
|
||||
<PackageReference Include="xunit" Version="2.9.3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
||||
@ -34,6 +34,6 @@ public class GetAvailableFlightsTests : FlightIntegrationTestBase
|
||||
|
||||
// Assert
|
||||
response?.Should().NotBeNull();
|
||||
response?.Count.Should().BeGreaterOrEqualTo(2);
|
||||
response?.Count.Should().BeGreaterThanOrEqualTo(2);
|
||||
}
|
||||
}
|
||||
@ -7,9 +7,9 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
|
||||
<PackageReference Include="xunit" Version="2.9.3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
||||
@ -38,6 +38,6 @@ public class GetAvailableSeatsTests : FlightIntegrationTestBase
|
||||
|
||||
// Assert
|
||||
response?.Should().NotBeNull();
|
||||
response?.SeatDtos?.Count.Should().BeGreaterOrEqualTo(1);
|
||||
response?.SeatDtos?.Count.Should().BeGreaterThanOrEqualTo(1);
|
||||
}
|
||||
}
|
||||
@ -8,6 +8,7 @@ using Integration.Test.Fakes;
|
||||
using Xunit;
|
||||
|
||||
namespace Integration.Test.Seat.Features;
|
||||
|
||||
public class ReserveSeatTests : FlightIntegrationTestBase
|
||||
{
|
||||
public ReserveSeatTests(
|
||||
|
||||
@ -7,9 +7,9 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
|
||||
<PackageReference Include="xunit" Version="2.9.3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
||||
@ -1,47 +1,39 @@
|
||||
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS builder
|
||||
# ---------- Build Stage ----------
|
||||
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
|
||||
WORKDIR /src
|
||||
|
||||
# Setup working directory for the project
|
||||
WORKDIR /
|
||||
# Copy solution-level files
|
||||
COPY .editorconfig .
|
||||
COPY global.json .
|
||||
COPY Directory.Build.props .
|
||||
|
||||
COPY ./.editorconfig ./
|
||||
COPY ./global.json ./
|
||||
COPY ./Directory.Build.props ./
|
||||
# Copy project files first (for better Docker layer caching)
|
||||
COPY src/BuildingBlocks/BuildingBlocks.csproj src/BuildingBlocks/
|
||||
COPY src/Services/Identity/src/Identity/Identity.csproj src/Services/Identity/src/Identity/
|
||||
COPY src/Services/Identity/src/Identity.Api/Identity.Api.csproj src/Services/Identity/src/Identity.Api/
|
||||
COPY src/Aspire/src/ServiceDefaults/ServiceDefaults.csproj src/Aspire/src/ServiceDefaults/
|
||||
|
||||
COPY ./src/BuildingBlocks/BuildingBlocks.csproj ./src/BuildingBlocks/
|
||||
COPY ./src/Services/Identity/src/Identity/Identity.csproj ./src/Services/Identity/src/Identity/
|
||||
COPY ./src/Services/Identity/src/Identity.Api/Identity.Api.csproj ./src/Services/Identity/src/Identity.Api/
|
||||
# Restore dependencies
|
||||
RUN dotnet restore src/Services/Identity/src/Identity.Api/Identity.Api.csproj
|
||||
|
||||
# Restore nuget packages
|
||||
RUN dotnet restore ./src/Services/Identity/src/Identity.Api/Identity.Api.csproj
|
||||
# Copy remaining source code
|
||||
COPY src ./src
|
||||
|
||||
# Copy project files
|
||||
COPY ./src/BuildingBlocks ./src/BuildingBlocks/
|
||||
COPY ./src/Services/Identity/src/Identity/ ./src/Services/Identity/src/Identity/
|
||||
COPY ./src/Services/Identity/src/Identity.Api/ ./src/Services/Identity/src/Identity.Api/
|
||||
# Publish (build included, no separate build step needed)
|
||||
RUN dotnet publish src/Services/Identity/src/Identity.Api/Identity.Api.csproj \
|
||||
-c Release \
|
||||
-o /app/publish \
|
||||
--no-restore
|
||||
|
||||
# Build project with Release configuration
|
||||
# and no restore, as we did it already
|
||||
# ---------- Runtime Stage ----------
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime
|
||||
WORKDIR /app
|
||||
|
||||
RUN ls
|
||||
RUN dotnet build -c Release --no-restore ./src/Services/Identity/src/Identity.Api/Identity.Api.csproj
|
||||
COPY --from=build /app/publish .
|
||||
|
||||
WORKDIR /src/Services/Identity/src/Identity.Api
|
||||
|
||||
# Publish project to output folder
|
||||
# and no build, as we did it already
|
||||
RUN dotnet publish -c Release --no-build -o out
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:9.0
|
||||
|
||||
# Setup working directory for the project
|
||||
WORKDIR /
|
||||
COPY --from=builder /src/Services/Identity/src/Identity.Api/out .
|
||||
|
||||
ENV ASPNETCORE_URLS https://*:443, http://*:80
|
||||
ENV ASPNETCORE_ENVIRONMENT docker
|
||||
ENV ASPNETCORE_URLS=http://+:80
|
||||
ENV ASPNETCORE_ENVIRONMENT=docker
|
||||
|
||||
EXPOSE 80
|
||||
EXPOSE 443
|
||||
|
||||
ENTRYPOINT ["dotnet", "Identity.Api.dll"]
|
||||
|
||||
ENTRYPOINT ["dotnet", "Identity.Api.dll"]
|
||||
@ -1,50 +0,0 @@
|
||||
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS builder
|
||||
|
||||
# Setup working directory for the project
|
||||
WORKDIR /
|
||||
|
||||
COPY ./.editorconfig ./
|
||||
COPY ./global.json ./
|
||||
COPY ./Directory.Build.props ./
|
||||
|
||||
COPY ./src/BuildingBlocks/BuildingBlocks.csproj ./src/BuildingBlocks/
|
||||
COPY ./src/Services/Identity/src/Identity/Identity.csproj ./src/Services/Identity/src/Identity/
|
||||
COPY ./src/Services/Identity/src/Identity.Api/Identity.Api.csproj ./src/Services/Identity/src/Identity.Api/
|
||||
|
||||
# Restore nuget packages
|
||||
RUN --mount=type=cache,id=identity_nuget,target=/root/.nuget/packages \
|
||||
dotnet restore ./src/Services/Identity/src/Identity.Api/Identity.Api.csproj
|
||||
|
||||
# Copy project files
|
||||
COPY ./src/BuildingBlocks ./src/BuildingBlocks/
|
||||
COPY ./src/Services/Identity/src/Identity/ ./src/Services/Identity/src/Identity/
|
||||
COPY ./src/Services/Identity/src/Identity.Api/ ./src/Services/Identity/src/Identity.Api/
|
||||
|
||||
# Build project with Release configuration
|
||||
# and no restore, as we did it already
|
||||
|
||||
RUN ls
|
||||
RUN --mount=type=cache,id=identity_nuget,target=/root/.nuget/packages \
|
||||
dotnet build -c Release --no-restore ./src/Services/Identity/src/Identity.Api/Identity.Api.csproj
|
||||
|
||||
WORKDIR /src/Services/Identity/src/Identity.Api
|
||||
|
||||
# Publish project to output folder
|
||||
# and no build, as we did it already
|
||||
RUN --mount=type=cache,id=identity_nuget,target=/root/.nuget/packages \
|
||||
dotnet publish -c Release --no-build -o out
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:9.0
|
||||
|
||||
# Setup working directory for the project
|
||||
WORKDIR /
|
||||
COPY --from=builder /src/Services/Identity/src/Identity.Api/out .
|
||||
|
||||
ENV ASPNETCORE_URLS https://*:443, http://*:80
|
||||
ENV ASPNETCORE_ENVIRONMENT docker
|
||||
|
||||
EXPOSE 80
|
||||
EXPOSE 443
|
||||
|
||||
ENTRYPOINT ["dotnet", "Identity.Api.dll"]
|
||||
|
||||
@ -1,14 +1,13 @@
|
||||
using BuildingBlocks.Core;
|
||||
using BuildingBlocks.EFCore;
|
||||
using BuildingBlocks.HealthCheck;
|
||||
using BuildingBlocks.Mapster;
|
||||
using BuildingBlocks.MassTransit;
|
||||
using BuildingBlocks.OpenApi;
|
||||
using BuildingBlocks.OpenTelemetryCollector;
|
||||
using BuildingBlocks.PersistMessageProcessor;
|
||||
using BuildingBlocks.ProblemDetails;
|
||||
using BuildingBlocks.Web;
|
||||
using Figgle;
|
||||
using Figgle.Fonts;
|
||||
using FluentValidation;
|
||||
using Identity.Data;
|
||||
using Identity.Data.Seed;
|
||||
@ -18,6 +17,7 @@ using Microsoft.AspNetCore.HttpOverrides;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using ServiceDefaults;
|
||||
|
||||
namespace Identity.Extensions.Infrastructure;
|
||||
|
||||
@ -28,25 +28,7 @@ public static class InfrastructureExtensions
|
||||
var configuration = builder.Configuration;
|
||||
var env = builder.Environment;
|
||||
|
||||
builder.Services.AddCustomHealthCheck();
|
||||
|
||||
builder.AddCustomObservability();
|
||||
|
||||
builder.Services.AddServiceDiscovery();
|
||||
|
||||
builder.Services.ConfigureHttpClientDefaults(http =>
|
||||
{
|
||||
http.AddStandardResilienceHandler(options =>
|
||||
{
|
||||
var timeSpan = TimeSpan.FromMinutes(1);
|
||||
options.CircuitBreaker.SamplingDuration = timeSpan * 2;
|
||||
options.TotalRequestTimeout.Timeout = timeSpan * 3;
|
||||
options.Retry.MaxRetryAttempts = 3;
|
||||
});
|
||||
|
||||
// Turn on service discovery by default
|
||||
http.AddServiceDiscovery();
|
||||
});
|
||||
builder.AddServiceDefaults();
|
||||
|
||||
builder.Services.AddScoped<ICurrentUserProvider, CurrentUserProvider>();
|
||||
builder.Services.AddScoped<IEventMapper, IdentityEventMapper>();
|
||||
@ -94,8 +76,7 @@ public static class InfrastructureExtensions
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
app.UseCustomHealthCheck();
|
||||
app.UseCustomObservability();
|
||||
app.UseServiceDefaults();
|
||||
|
||||
app.UseForwardedHeaders();
|
||||
|
||||
|
||||
@ -1,17 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.0">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.3">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Data\Migrations" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\Aspire\src\ServiceDefaults\ServiceDefaults.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\BuildingBlocks\BuildingBlocks.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@ -10,10 +10,8 @@ namespace Integration.Test.Identity.Features;
|
||||
|
||||
public class RegisterNewUserTests : IdentityIntegrationTestBase
|
||||
{
|
||||
public RegisterNewUserTests(
|
||||
TestWriteFixture<Program, IdentityContext> integrationTestFactory) : base(integrationTestFactory)
|
||||
{
|
||||
}
|
||||
public RegisterNewUserTests(TestWriteFixture<Program, IdentityContext> integrationTestFactory)
|
||||
: base(integrationTestFactory) { }
|
||||
|
||||
[Fact]
|
||||
public async Task should_create_new_user_to_db_and_publish_message_to_broker()
|
||||
@ -30,4 +28,4 @@ public class RegisterNewUserTests : IdentityIntegrationTestBase
|
||||
|
||||
(await Fixture.WaitForPublishing<UserCreated>()).Should().Be(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,9 +7,9 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
|
||||
<PackageReference Include="xunit" Version="2.9.3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
||||
@ -1,47 +1,39 @@
|
||||
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS builder
|
||||
WORKDIR /
|
||||
# ---------- Build Stage ----------
|
||||
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
|
||||
WORKDIR /src
|
||||
|
||||
COPY ./.editorconfig ./
|
||||
COPY ./global.json ./
|
||||
COPY ./Directory.Build.props ./
|
||||
# Copy solution-level files
|
||||
COPY .editorconfig .
|
||||
COPY global.json .
|
||||
COPY Directory.Build.props .
|
||||
|
||||
# Setup working directory for the project
|
||||
COPY ./src/BuildingBlocks/BuildingBlocks.csproj ./src/BuildingBlocks/
|
||||
COPY ./src/Services/Passenger/src/Passenger/Passenger.csproj ./src/Services/Passenger/src/Passenger/
|
||||
COPY ./src/Services/Passenger/src/Passenger.Api/Passenger.Api.csproj ./src/Services/Passenger/src/Passenger.Api/
|
||||
# Copy project files first (better Docker layer caching)
|
||||
COPY src/BuildingBlocks/BuildingBlocks.csproj src/BuildingBlocks/
|
||||
COPY src/Services/Passenger/src/Passenger/Passenger.csproj src/Services/Passenger/src/Passenger/
|
||||
COPY src/Services/Passenger/src/Passenger.Api/Passenger.Api.csproj src/Services/Passenger/src/Passenger.Api/
|
||||
COPY src/Aspire/src/ServiceDefaults/ServiceDefaults.csproj src/Aspire/src/ServiceDefaults/
|
||||
|
||||
# Restore dependencies
|
||||
RUN dotnet restore src/Services/Passenger/src/Passenger.Api/Passenger.Api.csproj
|
||||
|
||||
# Restore nuget packages
|
||||
RUN dotnet restore ./src/Services/Passenger/src/Passenger.Api/Passenger.Api.csproj
|
||||
# Copy remaining source code
|
||||
COPY src ./src
|
||||
|
||||
# Copy project files
|
||||
COPY ./src/BuildingBlocks ./src/BuildingBlocks/
|
||||
COPY ./src/Services/Passenger/src/Passenger/ ./src/Services/Passenger/src/Passenger/
|
||||
COPY ./src/Services/Passenger/src/Passenger.Api/ ./src/Services/Passenger/src/Passenger.Api/
|
||||
# Publish (build included)
|
||||
RUN dotnet publish src/Services/Passenger/src/Passenger.Api/Passenger.Api.csproj \
|
||||
-c Release \
|
||||
-o /app/publish \
|
||||
--no-restore
|
||||
|
||||
# Build project with Release configuration
|
||||
# and no restore, as we did it already
|
||||
# ---------- Runtime Stage ----------
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime
|
||||
WORKDIR /app
|
||||
|
||||
RUN ls
|
||||
RUN dotnet build -c Release --no-restore ./src/Services/Passenger/src/Passenger.Api/Passenger.Api.csproj
|
||||
COPY --from=build /app/publish .
|
||||
|
||||
WORKDIR /src/Services/Passenger/src/Passenger.Api
|
||||
|
||||
# Publish project to output folder
|
||||
# and no build, as we did it already
|
||||
RUN dotnet publish -c Release --no-build -o out
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:9.0
|
||||
|
||||
# Setup working directory for the project
|
||||
WORKDIR /
|
||||
COPY --from=builder /src/Services/Passenger/src/Passenger.Api/out .
|
||||
|
||||
ENV ASPNETCORE_URLS https://*:443, http://*:80
|
||||
ENV ASPNETCORE_ENVIRONMENT docker
|
||||
ENV ASPNETCORE_URLS=http://+:80
|
||||
ENV ASPNETCORE_ENVIRONMENT=docker
|
||||
|
||||
EXPOSE 80
|
||||
EXPOSE 443
|
||||
|
||||
ENTRYPOINT ["dotnet", "Passenger.Api.dll"]
|
||||
|
||||
ENTRYPOINT ["dotnet", "Passenger.Api.dll"]
|
||||
@ -1,50 +0,0 @@
|
||||
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS builder
|
||||
WORKDIR /
|
||||
|
||||
COPY ./.editorconfig ./
|
||||
COPY ./global.json ./
|
||||
COPY ./Directory.Build.props ./
|
||||
|
||||
# Setup working directory for the project
|
||||
COPY ./src/BuildingBlocks/BuildingBlocks.csproj ./src/BuildingBlocks/
|
||||
COPY ./src/Services/Passenger/src/Passenger/Passenger.csproj ./src/Services/Passenger/src/Passenger/
|
||||
COPY ./src/Services/Passenger/src/Passenger.Api/Passenger.Api.csproj ./src/Services/Passenger/src/Passenger.Api/
|
||||
|
||||
|
||||
# Restore nuget packages
|
||||
RUN --mount=type=cache,id=passenger_nuget,target=/root/.nuget/packages \
|
||||
dotnet restore ./src/Services/Passenger/src/Passenger.Api/Passenger.Api.csproj
|
||||
|
||||
# Copy project files
|
||||
COPY ./src/BuildingBlocks ./src/BuildingBlocks/
|
||||
COPY ./src/Services/Passenger/src/Passenger/ ./src/Services/Passenger/src/Passenger/
|
||||
COPY ./src/Services/Passenger/src/Passenger.Api/ ./src/Services/Passenger/src/Passenger.Api/
|
||||
|
||||
# Build project with Release configuration
|
||||
# and no restore, as we did it already
|
||||
|
||||
RUN ls
|
||||
RUN --mount=type=cache,id=passenger_nuget,target=/root/.nuget/packages \
|
||||
dotnet build -c Release --no-restore ./src/Services/Passenger/src/Passenger.Api/Passenger.Api.csproj
|
||||
|
||||
WORKDIR /src/Services/Passenger/src/Passenger.Api
|
||||
|
||||
# Publish project to output folder
|
||||
# and no build, as we did it already
|
||||
RUN --mount=type=cache,id=passenger_nuget,target=/root/.nuget/packages \
|
||||
dotnet publish -c Release --no-build -o out
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:9.0
|
||||
|
||||
# Setup working directory for the project
|
||||
WORKDIR /
|
||||
COPY --from=builder /src/Services/Passenger/src/Passenger.Api/out .
|
||||
|
||||
ENV ASPNETCORE_URLS https://*:443, http://*:80
|
||||
ENV ASPNETCORE_ENVIRONMENT docker
|
||||
|
||||
EXPOSE 80
|
||||
EXPOSE 443
|
||||
|
||||
ENTRYPOINT ["dotnet", "Passenger.Api.dll"]
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
using SmartCharging.Infrastructure.Exceptions;
|
||||
|
||||
namespace Passenger.Exceptions;
|
||||
|
||||
using System;
|
||||
|
||||
public class InvalidPassengerIdException : DomainException
|
||||
|
||||
@ -12,6 +12,7 @@ using BuildingBlocks.PersistMessageProcessor;
|
||||
using BuildingBlocks.ProblemDetails;
|
||||
using BuildingBlocks.Web;
|
||||
using Figgle;
|
||||
using Figgle.Fonts;
|
||||
using FluentValidation;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
@ -20,6 +21,7 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Passenger.Data;
|
||||
using Passenger.GrpcServer.Services;
|
||||
using ServiceDefaults;
|
||||
|
||||
namespace Passenger.Extensions.Infrastructure;
|
||||
|
||||
@ -30,26 +32,7 @@ public static class InfrastructureExtensions
|
||||
var configuration = builder.Configuration;
|
||||
var env = builder.Environment;
|
||||
|
||||
builder.Services.AddCustomHealthCheck();
|
||||
|
||||
builder.AddCustomObservability();
|
||||
|
||||
builder.Services.AddServiceDiscovery();
|
||||
|
||||
builder.Services.ConfigureHttpClientDefaults(http =>
|
||||
{
|
||||
http.AddStandardResilienceHandler(options =>
|
||||
{
|
||||
var timeSpan = TimeSpan.FromMinutes(1);
|
||||
options.CircuitBreaker.SamplingDuration = timeSpan * 2;
|
||||
options.TotalRequestTimeout.Timeout = timeSpan * 3;
|
||||
options.Retry.MaxRetryAttempts = 3;
|
||||
});
|
||||
|
||||
// Turn on service discovery by default
|
||||
http.AddServiceDiscovery();
|
||||
});
|
||||
|
||||
builder.AddServiceDefaults();
|
||||
|
||||
builder.Services.AddScoped<ICurrentUserProvider, CurrentUserProvider>();
|
||||
builder.Services.AddScoped<IEventMapper, PassengerEventMapper>();
|
||||
@ -97,8 +80,7 @@ public static class InfrastructureExtensions
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
app.UseCustomHealthCheck();
|
||||
app.UseCustomObservability();
|
||||
app.UseServiceDefaults();
|
||||
|
||||
app.UseCustomProblemDetails();
|
||||
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Grpc.AspNetCore" Version="2.67.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.0">
|
||||
<PackageReference Include="Grpc.AspNetCore" Version="2.76.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.3">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
@ -17,6 +17,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\Aspire\src\ServiceDefaults\ServiceDefaults.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\BuildingBlocks\BuildingBlocks.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@ -1,2 +1,3 @@
|
||||
namespace Passenger.Passengers.Dtos;
|
||||
|
||||
public record PassengerDto(Guid Id, string Name, string PassportNumber, Enums.PassengerType PassengerType, int Age);
|
||||
@ -1,4 +1,5 @@
|
||||
namespace Passenger.Passengers.Exceptions;
|
||||
|
||||
using BuildingBlocks.Exception;
|
||||
|
||||
public class InvalidAgeException : BadRequestException
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
namespace Passenger.Passengers.Exceptions;
|
||||
|
||||
using BuildingBlocks.Exception;
|
||||
|
||||
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
namespace Passenger.Passengers.Exceptions;
|
||||
|
||||
using BuildingBlocks.Exception;
|
||||
|
||||
|
||||
|
||||
@ -17,17 +17,23 @@ using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Passenger.Passengers.ValueObjects;
|
||||
using ValueObjects;
|
||||
|
||||
public record CompleteRegisterPassenger
|
||||
(string PassportNumber, Enums.PassengerType PassengerType, int Age) : ICommand<CompleteRegisterPassengerResult>,
|
||||
public record CompleteRegisterPassenger(string PassportNumber, Enums.PassengerType PassengerType, int Age)
|
||||
: ICommand<CompleteRegisterPassengerResult>,
|
||||
IInternalCommand
|
||||
{
|
||||
public Guid Id { get; init; } = NewId.NextGuid();
|
||||
}
|
||||
|
||||
public record PassengerRegistrationCompletedDomainEvent(Guid Id, string Name, string PassportNumber,
|
||||
Enums.PassengerType PassengerType, int Age, bool IsDeleted = false) : IDomainEvent;
|
||||
public record PassengerRegistrationCompletedDomainEvent(
|
||||
Guid Id,
|
||||
string Name,
|
||||
string PassportNumber,
|
||||
Enums.PassengerType PassengerType,
|
||||
int Age,
|
||||
bool IsDeleted = false
|
||||
) : IDomainEvent;
|
||||
|
||||
public record CompleteRegisterPassengerResult(PassengerDto PassengerDto);
|
||||
|
||||
@ -39,18 +45,25 @@ public class CompleteRegisterPassengerEndpoint : IMinimalEndpoint
|
||||
{
|
||||
public IEndpointRouteBuilder MapEndpoint(IEndpointRouteBuilder builder)
|
||||
{
|
||||
builder.MapPost($"{EndpointConfig.BaseApiPath}/passenger/complete-registration", async (
|
||||
CompleteRegisterPassengerRequestDto request, IMapper mapper,
|
||||
IMediator mediator, CancellationToken cancellationToken) =>
|
||||
{
|
||||
var command = mapper.Map<CompleteRegisterPassenger>(request);
|
||||
builder
|
||||
.MapPost(
|
||||
$"{EndpointConfig.BaseApiPath}/passenger/complete-registration",
|
||||
async (
|
||||
CompleteRegisterPassengerRequestDto request,
|
||||
IMapper mapper,
|
||||
IMediator mediator,
|
||||
CancellationToken cancellationToken
|
||||
) =>
|
||||
{
|
||||
var command = mapper.Map<CompleteRegisterPassenger>(request);
|
||||
|
||||
var result = await mediator.Send(command, cancellationToken);
|
||||
var result = await mediator.Send(command, cancellationToken);
|
||||
|
||||
var response = result.Adapt<CompleteRegisterPassengerResponseDto>();
|
||||
var response = result.Adapt<CompleteRegisterPassengerResponseDto>();
|
||||
|
||||
return Results.Ok(response);
|
||||
})
|
||||
return Results.Ok(response);
|
||||
}
|
||||
)
|
||||
.RequireAuthorization(nameof(ApiScope))
|
||||
.WithName("CompleteRegisterPassenger")
|
||||
.WithApiVersionSet(builder.NewApiVersionSet("Passenger").Build())
|
||||
@ -71,17 +84,19 @@ public class CompleteRegisterPassengerValidator : AbstractValidator<CompleteRegi
|
||||
{
|
||||
RuleFor(x => x.PassportNumber).NotNull().WithMessage("The PassportNumber is required!");
|
||||
RuleFor(x => x.Age).GreaterThan(0).WithMessage("The Age must be greater than 0!");
|
||||
RuleFor(x => x.PassengerType).Must(p => p.GetType().IsEnum &&
|
||||
p == Enums.PassengerType.Baby ||
|
||||
p == Enums.PassengerType.Female ||
|
||||
p == Enums.PassengerType.Male ||
|
||||
p == Enums.PassengerType.Unknown)
|
||||
RuleFor(x => x.PassengerType)
|
||||
.Must(p =>
|
||||
p.GetType().IsEnum && p == Enums.PassengerType.Baby
|
||||
|| p == Enums.PassengerType.Female
|
||||
|| p == Enums.PassengerType.Male
|
||||
|| p == Enums.PassengerType.Unknown
|
||||
)
|
||||
.WithMessage("PassengerType must be Male, Female, Baby or Unknown");
|
||||
}
|
||||
}
|
||||
|
||||
internal class CompleteRegisterPassengerCommandHandler : ICommandHandler<CompleteRegisterPassenger,
|
||||
CompleteRegisterPassengerResult>
|
||||
internal class CompleteRegisterPassengerCommandHandler
|
||||
: ICommandHandler<CompleteRegisterPassenger, CompleteRegisterPassengerResult>
|
||||
{
|
||||
private readonly IMapper _mapper;
|
||||
private readonly PassengerDbContext _passengerDbContext;
|
||||
@ -92,21 +107,30 @@ internal class CompleteRegisterPassengerCommandHandler : ICommandHandler<Complet
|
||||
_passengerDbContext = passengerDbContext;
|
||||
}
|
||||
|
||||
public async Task<CompleteRegisterPassengerResult> Handle(CompleteRegisterPassenger request,
|
||||
CancellationToken cancellationToken)
|
||||
public async Task<CompleteRegisterPassengerResult> Handle(
|
||||
CompleteRegisterPassenger request,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
Guard.Against.Null(request, nameof(request));
|
||||
|
||||
var passenger = await _passengerDbContext.Passengers.SingleOrDefaultAsync(
|
||||
x => x.PassportNumber.Value == request.PassportNumber, cancellationToken);
|
||||
x => x.PassportNumber.Value == request.PassportNumber,
|
||||
cancellationToken
|
||||
);
|
||||
|
||||
if (passenger is null)
|
||||
{
|
||||
throw new PassengerNotExist();
|
||||
}
|
||||
|
||||
passenger.CompleteRegistrationPassenger(passenger.Id, passenger.Name,
|
||||
passenger.PassportNumber, request.PassengerType, Age.Of(request.Age));
|
||||
passenger.CompleteRegistrationPassenger(
|
||||
passenger.Id,
|
||||
passenger.Name,
|
||||
passenger.PassportNumber,
|
||||
request.PassengerType,
|
||||
Age.Of(request.Age)
|
||||
);
|
||||
|
||||
var updatePassenger = _passengerDbContext.Passengers.Update(passenger).Entity;
|
||||
|
||||
@ -114,4 +138,4 @@ internal class CompleteRegisterPassengerCommandHandler : ICommandHandler<Complet
|
||||
|
||||
return new CompleteRegisterPassengerResult(passengerDto);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
namespace Passenger.Passengers.Models;
|
||||
|
||||
public class PassengerReadModel
|
||||
{
|
||||
public required Guid Id { get; init; }
|
||||
|
||||
@ -4,15 +4,14 @@ using Passenger.Passengers.Enums;
|
||||
namespace Integration.Test.Fakes;
|
||||
|
||||
using global::Passenger.Passengers.Features.CompletingRegisterPassenger.V1;
|
||||
using MassTransit;
|
||||
|
||||
public sealed class FakeCompleteRegisterPassengerCommand : AutoFaker<CompleteRegisterPassenger>
|
||||
{
|
||||
public FakeCompleteRegisterPassengerCommand(string passportNumber)
|
||||
public FakeCompleteRegisterPassengerCommand(string passportNumber, Guid passengerId)
|
||||
{
|
||||
RuleFor(r => r.Id, _ => NewId.NextGuid());
|
||||
RuleFor(r => r.Id, _ => passengerId);
|
||||
RuleFor(r => r.PassportNumber, _ => passportNumber);
|
||||
RuleFor(r => r.PassengerType, _ => PassengerType.Male);
|
||||
RuleFor(r => r.Age, _ => 30);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,16 +0,0 @@
|
||||
using AutoBogus;
|
||||
using BuildingBlocks.Contracts.EventBus.Messages;
|
||||
|
||||
namespace Integration.Test.Fakes;
|
||||
|
||||
using MassTransit;
|
||||
|
||||
public class FakeUserCreated : AutoFaker<UserCreated>
|
||||
{
|
||||
public FakeUserCreated()
|
||||
{
|
||||
RuleFor(r => r.Id, _ => NewId.NextGuid());
|
||||
RuleFor(r => r.Name, _ => "Sam");
|
||||
RuleFor(r => r.PassportNumber, _ => "123456789");
|
||||
}
|
||||
}
|
||||
@ -7,9 +7,9 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
|
||||
<PackageReference Include="xunit" Version="2.9.3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
||||
@ -1,41 +1,41 @@
|
||||
using System.Threading.Tasks;
|
||||
using BuildingBlocks.Contracts.EventBus.Messages;
|
||||
using BuildingBlocks.TestBase;
|
||||
using FluentAssertions;
|
||||
using Integration.Test.Fakes;
|
||||
using Passenger.Api;
|
||||
using Passenger.Data;
|
||||
using Passenger.Passengers.ValueObjects;
|
||||
using Xunit;
|
||||
|
||||
namespace Integration.Test.Passenger.Features;
|
||||
|
||||
public class CompleteRegisterPassengerTests : PassengerIntegrationTestBase
|
||||
{
|
||||
|
||||
public CompleteRegisterPassengerTests(
|
||||
TestFixture<Program, PassengerDbContext, PassengerReadDbContext> integrationTestFactory) : base(integrationTestFactory)
|
||||
{
|
||||
}
|
||||
TestFixture<Program, PassengerDbContext, PassengerReadDbContext> integrationTestFactory
|
||||
)
|
||||
: base(integrationTestFactory) { }
|
||||
|
||||
[Fact]
|
||||
public async Task should_complete_register_passenger_and_update_to_db()
|
||||
{
|
||||
// Arrange
|
||||
var userCreated = new FakeUserCreated().Generate();
|
||||
var passenger = global::Passenger.Passengers.Models.Passenger.Create(
|
||||
PassengerId.Of(Guid.CreateVersion7()),
|
||||
Name.Of("Sam"),
|
||||
PassportNumber.Of("123456789")
|
||||
);
|
||||
|
||||
await Fixture.Publish(userCreated);
|
||||
(await Fixture.WaitForPublishing<UserCreated>()).Should().Be(true);
|
||||
(await Fixture.WaitForConsuming<UserCreated>()).Should().Be(true);
|
||||
await Fixture.InsertAsync(passenger);
|
||||
|
||||
var command = new FakeCompleteRegisterPassengerCommand(userCreated.PassportNumber).Generate();
|
||||
var command = new FakeCompleteRegisterPassengerCommand(passenger.PassportNumber, passenger.Id).Generate();
|
||||
|
||||
// Act
|
||||
var response = await Fixture.SendAsync(command);
|
||||
|
||||
// Assert
|
||||
response.Should().NotBeNull();
|
||||
response?.PassengerDto?.Name.Should().Be(userCreated.Name);
|
||||
response?.PassengerDto?.Name.Should().Be(passenger.Name);
|
||||
response?.PassengerDto?.PassportNumber.Should().Be(command.PassportNumber);
|
||||
response?.PassengerDto?.PassengerType.ToString().Should().Be(command.PassengerType.ToString());
|
||||
response?.PassengerDto?.Age.Should().Be(command.Age);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user