mirror of
https://github.com/meysamhadeli/booking-microservices.git
synced 2026-04-14 20:54:05 +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,
|
"isRoot": true,
|
||||||
"tools": {
|
"tools": {
|
||||||
"dotnet-outdated-tool": {
|
"dotnet-outdated-tool": {
|
||||||
"version": "4.6.4",
|
"version": "4.6.9",
|
||||||
"commands": [
|
"commands": [
|
||||||
"dotnet-outdated"
|
"dotnet-outdated"
|
||||||
],
|
]
|
||||||
"rollForward": false
|
|
||||||
},
|
},
|
||||||
"dotnet-ef": {
|
"dotnet-ef": {
|
||||||
"version": "9.0.0",
|
"version": "10.0.3",
|
||||||
"commands": [
|
"commands": [
|
||||||
"dotnet-ef"
|
"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
|
dotnet_diagnostic.CA1716.severity = Suggestion
|
||||||
# CA1032: Implement standard exception constructors
|
# CA1032: Implement standard exception constructors
|
||||||
dotnet_diagnostic.CA1032.severity = Suggestion
|
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/
|
# 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
|
- name: Setup .NET
|
||||||
uses: actions/setup-dotnet@v4
|
uses: actions/setup-dotnet@v4
|
||||||
with:
|
with:
|
||||||
dotnet-version: '9.x.x'
|
dotnet-version: '10.x.x'
|
||||||
|
|
||||||
# https://learn.microsoft.com/en-us/dotnet/core/tools/global-tools
|
# https://learn.microsoft.com/en-us/dotnet/core/tools/global-tools
|
||||||
- name: Restore .NET 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"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<Project>
|
<Project>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<RepositoryRoot>$(MSBuildThisFileDirectory)</RepositoryRoot>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="StyleCop.Analyzers" PrivateAssets="all" Version="1.1.118">
|
<PackageReference Include="StyleCop.Analyzers" PrivateAssets="all" Version="1.1.118">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</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>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</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>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</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>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</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>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</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>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="AsyncAwaitBestPractices" PrivateAssets="all" Version="9.0.0">
|
<PackageReference Include="AsyncAwaitBestPractices" PrivateAssets="all" Version="10.0.0">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
|
||||||
</PackageReference>
|
|
||||||
<PackageReference Include="SerilogAnalyzer" PrivateAssets="all" Version="0.15.0">
|
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="CSharpGuidelinesAnalyzer" PrivateAssets="all" Version="3.8.5">
|
<PackageReference Include="CSharpGuidelinesAnalyzer" PrivateAssets="all" Version="3.8.5">
|
||||||
|
|||||||
15
README.md
15
README.md
@ -6,15 +6,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</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:
|
## You can find other version of this project here:
|
||||||
- [Booking with Modular Monolith Architecture](https://github.com/meysamhadeli/booking-modular-monolith)
|
- [Booking with Modular Monolith Architecture](https://github.com/meysamhadeli/booking-modular-monolith)
|
||||||
- [Booking with Monolith Architecture](https://github.com/meysamhadeli/booking-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>
|
<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>
|
<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>
|
</div>
|
||||||
@ -77,10 +74,10 @@
|
|||||||
|
|
||||||
## Technologies - Libraries
|
## 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.
|
- ✔️ **[`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.
|
- ✔️ **[`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.
|
- ✔️ **[`Masstransit`](https://github.com/MassTransit/MassTransit)** - Distributed Application Framework for .NET.
|
||||||
- ✔️ **[`MediatR`](https://github.com/jbogard/MediatR)** - Simple, unambitious mediator implementation in .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.
|
- ✔️ **[`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
|
### 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
|
```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
|
> ### Docker Compose
|
||||||
|
|
||||||
|
|||||||
@ -75,6 +75,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{C4287034-683
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AppHost", "src\Aspire\src\AppHost\AppHost.csproj", "{490BCB11-314C-473C-9B85-A32164783507}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AppHost", "src\Aspire\src\AppHost\AppHost.csproj", "{490BCB11-314C-473C-9B85-A32164783507}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceDefaults", "src\Aspire\src\ServiceDefaults\ServiceDefaults.csproj", "{5B7BF918-E47F-4932-B5C5-E8C2C35890E4}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
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|x64.Build.0 = Release|Any CPU
|
||||||
{490BCB11-314C-473C-9B85-A32164783507}.Release|x86.ActiveCfg = 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
|
{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
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@ -329,5 +343,6 @@ Global
|
|||||||
{D1B6353A-63F5-4DD9-90E6-42B2CFDF1DEA} = {CD4A4407-C3B0-422D-BB8C-2A810CED9938}
|
{D1B6353A-63F5-4DD9-90E6-42B2CFDF1DEA} = {CD4A4407-C3B0-422D-BB8C-2A810CED9938}
|
||||||
{C4287034-6833-4505-A6EB-704A86392ECB} = {D1B6353A-63F5-4DD9-90E6-42B2CFDF1DEA}
|
{C4287034-6833-4505-A6EB-704A86392ECB} = {D1B6353A-63F5-4DD9-90E6-42B2CFDF1DEA}
|
||||||
{490BCB11-314C-473C-9B85-A32164783507} = {C4287034-6833-4505-A6EB-704A86392ECB}
|
{490BCB11-314C-473C-9B85-A32164783507} = {C4287034-6833-4505-A6EB-704A86392ECB}
|
||||||
|
{5B7BF918-E47F-4932-B5C5-E8C2C35890E4} = {C4287034-6833-4505-A6EB-704A86392ECB}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|||||||
@ -355,7 +355,7 @@ services:
|
|||||||
args:
|
args:
|
||||||
Version: "1"
|
Version: "1"
|
||||||
context: ../../
|
context: ../../
|
||||||
dockerfile: src/ApiGateway/dev.Dockerfile
|
dockerfile: src/ApiGateway/Dockerfile
|
||||||
container_name: api-gateway
|
container_name: api-gateway
|
||||||
ports:
|
ports:
|
||||||
- "5001:80"
|
- "5001:80"
|
||||||
@ -382,7 +382,7 @@ services:
|
|||||||
args:
|
args:
|
||||||
Version: "1"
|
Version: "1"
|
||||||
context: ../../
|
context: ../../
|
||||||
dockerfile: src/Services/Flight/dev.Dockerfile
|
dockerfile: src/Services/Flight/Dockerfile
|
||||||
container_name: flight
|
container_name: flight
|
||||||
ports:
|
ports:
|
||||||
- 5004:80
|
- 5004:80
|
||||||
@ -408,7 +408,7 @@ services:
|
|||||||
args:
|
args:
|
||||||
Version: "1"
|
Version: "1"
|
||||||
context: ../../
|
context: ../../
|
||||||
dockerfile: src/Services/Identity/dev.Dockerfile
|
dockerfile: src/Services/Identity/Dockerfile
|
||||||
container_name: identity
|
container_name: identity
|
||||||
ports:
|
ports:
|
||||||
- 6005:80
|
- 6005:80
|
||||||
@ -435,7 +435,7 @@ services:
|
|||||||
args:
|
args:
|
||||||
Version: "1"
|
Version: "1"
|
||||||
context: ../../
|
context: ../../
|
||||||
dockerfile: src/Services/Passenger/dev.Dockerfile
|
dockerfile: src/Services/Passenger/Dockerfile
|
||||||
container_name: passenger
|
container_name: passenger
|
||||||
ports:
|
ports:
|
||||||
- 6012:80
|
- 6012:80
|
||||||
@ -462,7 +462,7 @@ services:
|
|||||||
args:
|
args:
|
||||||
Version: "1"
|
Version: "1"
|
||||||
context: ../../
|
context: ../../
|
||||||
dockerfile: src/Services/Booking/dev.Dockerfile
|
dockerfile: src/Services/Booking/Dockerfile
|
||||||
container_name: booking
|
container_name: booking
|
||||||
ports:
|
ports:
|
||||||
- 6010:80
|
- 6010:80
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"sdk": {
|
"sdk": {
|
||||||
"version": "9.0.100",
|
"version": "10.0.103",
|
||||||
"rollForward": "latestFeature"
|
"rollForward": "latestFeature"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,8 +7,8 @@
|
|||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prepare": "husky && dotnet tool restore",
|
"prepare": "husky && dotnet tool restore",
|
||||||
"format": "dotnet format booking-microservices.sln --severity error --verbosity detailed",
|
"format": "dotnet tool run dotnet-csharpier booking-microservices.sln",
|
||||||
"ci-format": "dotnet format booking-microservices.sln --verify-no-changes --severity error --verbosity detailed",
|
"ci-format": "dotnet tool run dotnet-csharpier booking-microservices.sln --check",
|
||||||
"upgrade-packages": "dotnet outdated --upgrade"
|
"upgrade-packages": "dotnet outdated --upgrade"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@ -1,45 +1,38 @@
|
|||||||
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS builder
|
# ---------- Build Stage ----------
|
||||||
WORKDIR /
|
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
|
||||||
|
WORKDIR /src
|
||||||
|
|
||||||
COPY ./.editorconfig ./
|
# Copy solution-level files
|
||||||
COPY ./global.json ./
|
COPY .editorconfig .
|
||||||
COPY ./Directory.Build.props ./
|
COPY global.json .
|
||||||
|
COPY Directory.Build.props .
|
||||||
|
|
||||||
# Setup working directory for the project
|
# Copy project files first (better Docker caching)
|
||||||
COPY ./src/BuildingBlocks/BuildingBlocks.csproj ./building-blocks/
|
COPY src/BuildingBlocks/BuildingBlocks.csproj src/BuildingBlocks/
|
||||||
COPY ./src/ApiGateway/src/ApiGateway.csproj ./src/ApiGateway/src/
|
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
|
# Copy the rest of the source code
|
||||||
RUN dotnet restore ./src/ApiGateway/src/ApiGateway.csproj
|
COPY src ./src
|
||||||
|
|
||||||
# Copy project files
|
# Publish (build included)
|
||||||
COPY ./src/BuildingBlocks ./src/BuildingBlocks/
|
RUN dotnet publish src/ApiGateway/src/ApiGateway.csproj \
|
||||||
COPY ./src/ApiGateway/src ./src/ApiGateway/src/
|
-c Release \
|
||||||
|
-o /app/publish \
|
||||||
|
--no-restore
|
||||||
|
|
||||||
# Build project with Release configuration
|
# ---------- Runtime Stage ----------
|
||||||
# and no restore, as we did it already
|
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
RUN ls
|
COPY --from=build /app/publish .
|
||||||
RUN dotnet build -c Release --no-restore ./src/ApiGateway/src/ApiGateway.csproj
|
|
||||||
|
|
||||||
WORKDIR /src/ApiGateway/src
|
ENV ASPNETCORE_URLS=http://+:80
|
||||||
|
ENV ASPNETCORE_ENVIRONMENT=docker
|
||||||
# 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
|
|
||||||
|
|
||||||
EXPOSE 80
|
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 BuildingBlocks.Web;
|
||||||
using Figgle;
|
using Figgle;
|
||||||
|
using Figgle.Fonts;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
var env = builder.Environment;
|
var env = builder.Environment;
|
||||||
@ -7,7 +8,6 @@ var appOptions = builder.Services.GetOptions<AppOptions>("AppOptions");
|
|||||||
Console.WriteLine(FiggleFonts.Standard.Render(appOptions.Name));
|
Console.WriteLine(FiggleFonts.Standard.Render(appOptions.Name));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
builder.Services.AddControllers();
|
builder.Services.AddControllers();
|
||||||
builder.Services.AddHttpContextAccessor();
|
builder.Services.AddHttpContextAccessor();
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<Sdk Name="Aspire.AppHost.SDK" Version="9.3.1"/>
|
<Sdk Name="Aspire.AppHost.SDK" Version="13.1.1"/>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<IsAspireHost>true</IsAspireHost>
|
<IsAspireHost>true</IsAspireHost>
|
||||||
@ -12,12 +12,13 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Aspire.Hosting.AppHost" Version="9.3.1" />
|
<PackageReference Include="Aspire.Hosting.AppHost" Version="13.1.1" />
|
||||||
<PackageReference Include="Aspire.Hosting.MongoDB" Version="9.3.1" />
|
<PackageReference Include="Aspire.Hosting.Docker" Version="13.1.1-preview.1.26105.8" />
|
||||||
<PackageReference Include="Aspire.Hosting.PostgreSQL" Version="9.3.1" />
|
<PackageReference Include="Aspire.Hosting.MongoDB" Version="13.1.1" />
|
||||||
<PackageReference Include="Aspire.Hosting.RabbitMQ" Version="9.3.1" />
|
<PackageReference Include="Aspire.Hosting.PostgreSQL" Version="13.1.1" />
|
||||||
<PackageReference Include="Aspire.Hosting.Redis" Version="9.3.1" />
|
<PackageReference Include="Aspire.Hosting.RabbitMQ" Version="13.1.1" />
|
||||||
<PackageReference Include="CommunityToolkit.Aspire.Hosting.EventStore" Version="9.6.0" />
|
<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" />
|
<PackageReference Include="Elastic.Aspire.Hosting.Elasticsearch" Version="9.3.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@ -2,39 +2,82 @@ using System.Net.Sockets;
|
|||||||
|
|
||||||
var builder = DistributedApplication.CreateBuilder(args);
|
var builder = DistributedApplication.CreateBuilder(args);
|
||||||
|
|
||||||
// 1. Database Services
|
builder.AddDockerComposeEnvironment("docker-compose");
|
||||||
var username = builder.AddParameter("username", "postgres", secret: true);
|
|
||||||
var password = builder.AddParameter("password", "postgres", secret: true);
|
|
||||||
|
|
||||||
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")
|
.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(
|
.WithArgs(
|
||||||
"-c", "wal_level=logical",
|
"-c",
|
||||||
"-c", "max_prepared_transactions=10"
|
"wal_level=logical",
|
||||||
)
|
"-c",
|
||||||
.WithDataVolume("postgres-data")
|
"max_prepared_transactions=10");
|
||||||
.WithLifetime(ContainerLifetime.Persistent);
|
|
||||||
|
if (builder.ExecutionContext.IsPublishMode)
|
||||||
|
{
|
||||||
|
postgres.WithDataVolume("postgres-data")
|
||||||
|
.WithLifetime(ContainerLifetime.Persistent);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
var flightDb = postgres.AddDatabase("flight");
|
var flightDb = postgres.AddDatabase("flight");
|
||||||
var passengerDb = postgres.AddDatabase("passenger");
|
var passengerDb = postgres.AddDatabase("passenger");
|
||||||
var identityDb = postgres.AddDatabase("identity");
|
var identityDb = postgres.AddDatabase("identity");
|
||||||
var persistMessageDb = postgres.AddDatabase("persist-message");
|
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 mongoPassword = builder.AddParameter("mongo-password", "secret", secret: true);
|
||||||
|
|
||||||
var mongo = builder.AddMongoDB("mongo", userName: mongoUsername, password: mongoPassword)
|
var mongo = builder.AddMongoDB("mongo", userName: mongoUsername, password: mongoPassword)
|
||||||
.WithImage("mongo:latest")
|
.WithImage("mongo")
|
||||||
.WithEndpoint(port: 27017, targetPort: 27017, name: "mongo")
|
.WithImageTag("latest")
|
||||||
.WithDataVolume("mongo-data")
|
.WithEndpoint(
|
||||||
.WithLifetime(ContainerLifetime.Persistent);
|
"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")
|
var redis = builder.AddRedis("redis")
|
||||||
.WithImage("redis:latest")
|
.WithImage("redis:latest")
|
||||||
.WithEndpoint(port: 6379, targetPort: 6379, name: "redis")
|
.WithEndpoint(
|
||||||
.WithDataVolume("redis-data")
|
"tcp",
|
||||||
.WithLifetime(ContainerLifetime.Persistent);
|
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")
|
var eventstore = builder.AddEventStore("eventstore")
|
||||||
.WithImage("eventstore/eventstore")
|
.WithImage("eventstore/eventstore")
|
||||||
@ -43,41 +86,103 @@ var eventstore = builder.AddEventStore("eventstore")
|
|||||||
.WithEnvironment("EVENTSTORE_START_STANDARD_PROJECTIONS", "True")
|
.WithEnvironment("EVENTSTORE_START_STANDARD_PROJECTIONS", "True")
|
||||||
.WithEnvironment("EVENTSTORE_INSECURE", "True")
|
.WithEnvironment("EVENTSTORE_INSECURE", "True")
|
||||||
.WithEnvironment("EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP", "True")
|
.WithEnvironment("EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP", "True")
|
||||||
.WithHttpEndpoint(port: 2113, targetPort: 2113, name: "eventstore-http")
|
.WithEndpoint(
|
||||||
.WithDataVolume("eventstore-data")
|
"http",
|
||||||
.WithLifetime(ContainerLifetime.Persistent);
|
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
|
// 2. Messaging Services
|
||||||
var rabbitmq = builder.AddRabbitMQ("rabbitmq")
|
var rabbitmqUsername = builder.AddParameter("rabbitmq-username", "guest", secret: true);
|
||||||
.WithImage("rabbitmq:management")
|
var rabbitmqPassword = builder.AddParameter("rabbitmq-password", "guest", secret: true);
|
||||||
.WithEndpoint(port: 5672, targetPort: 5672, name: "rabbitmq-amqp")
|
|
||||||
.WithEndpoint(port: 15672, targetPort: 15672, name: "rabbitmq-management")
|
|
||||||
.WithLifetime(ContainerLifetime.Persistent);
|
|
||||||
|
|
||||||
// 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")
|
var jaeger = builder.AddContainer("jaeger-all-in-one", "jaegertracing/all-in-one")
|
||||||
.WithEndpoint(port: 6831, targetPort: 6831, name: "jaeger-udp", protocol: ProtocolType.Udp)
|
.WithEndpoint(
|
||||||
.WithEndpoint(port: 16686, targetPort: 16686, name: "jaeger-ui")
|
port: 6831,
|
||||||
.WithEndpoint(port: 14268, targetPort: 14268, name: "jaeger-api")
|
targetPort: 6831,
|
||||||
.WithEndpoint(port: 14317, targetPort: 4317, name: "jaeger-otlp-grpc")
|
name: "agent",
|
||||||
.WithEndpoint(port: 14318, targetPort: 4318, name: "jaeger-otlp-http")
|
protocol: ProtocolType.Udp,
|
||||||
.WithLifetime(ContainerLifetime.Persistent);
|
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")
|
var zipkin = builder.AddContainer("zipkin-all-in-one", "openzipkin/zipkin")
|
||||||
.WithEndpoint(port: 9411, targetPort: 9411, name: "zipkin-api")
|
.WithEndpoint(port: 9411, targetPort: 9411, name: "http", isProxied: true, isExternal: true);
|
||||||
.WithLifetime(ContainerLifetime.Persistent);
|
|
||||||
|
if (builder.ExecutionContext.IsPublishMode)
|
||||||
|
{
|
||||||
|
zipkin.WithLifetime(ContainerLifetime.Persistent);
|
||||||
|
}
|
||||||
|
|
||||||
var otelCollector = builder.AddContainer("otel-collector", "otel/opentelemetry-collector-contrib")
|
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")
|
.WithArgs("--config=/etc/otelcol-contrib/config.yaml")
|
||||||
.WithEndpoint(port: 11888, targetPort: 1888, name: "otel-pprof")
|
.WithEndpoint(port: 11888, targetPort: 1888, name: "otel-pprof", isProxied: true, isExternal: true)
|
||||||
.WithEndpoint(port: 8888, targetPort: 8888, name: "otel-metrics")
|
.WithEndpoint(port: 8888, targetPort: 8888, name: "otel-metrics", isProxied: true, isExternal: true)
|
||||||
.WithEndpoint(port: 8889, targetPort: 8889, name: "otel-exporter-metrics")
|
.WithEndpoint(port: 8889, targetPort: 8889, name: "otel-exporter-metrics", isProxied: true, isExternal: true)
|
||||||
.WithEndpoint(port: 13133, targetPort: 13133, name: "otel-health")
|
.WithEndpoint(port: 13133, targetPort: 13133, name: "otel-health", isProxied: true, isExternal: true)
|
||||||
.WithEndpoint(port: 4317, targetPort: 4317, name: "otel-grpc")
|
.WithEndpoint(port: 4317, targetPort: 4317, name: "otel-grpc", isProxied: true, isExternal: true)
|
||||||
.WithEndpoint(port: 4318, targetPort: 4318, name: "otel-http")
|
.WithEndpoint(port: 4318, targetPort: 4318, name: "otel-http", isProxied: true, isExternal: true)
|
||||||
.WithEndpoint(port: 55679, targetPort: 55679, name: "otel-zpages")
|
.WithEndpoint(port: 55679, targetPort: 55679, name: "otel-zpages", isProxied: true, isExternal: true);
|
||||||
.WithLifetime(ContainerLifetime.Persistent);
|
|
||||||
|
if (builder.ExecutionContext.IsPublishMode)
|
||||||
|
{
|
||||||
|
otelCollector.WithLifetime(ContainerLifetime.Persistent);
|
||||||
|
}
|
||||||
|
|
||||||
var prometheus = builder.AddContainer("prometheus", "prom/prometheus")
|
var prometheus = builder.AddContainer("prometheus", "prom/prometheus")
|
||||||
.WithBindMount("../../../../deployments/configs/prometheus.yaml", "/etc/prometheus/prometheus.yml")
|
.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.libraries=/usr/share/prometheus/console_libraries",
|
||||||
"--web.console.templates=/usr/share/prometheus/consoles",
|
"--web.console.templates=/usr/share/prometheus/consoles",
|
||||||
"--web.enable-remote-write-receiver")
|
"--web.enable-remote-write-receiver")
|
||||||
.WithEndpoint(port: 9090, targetPort: 9090, name: "prometheus-web")
|
.WithEndpoint(port: 9090, targetPort: 9090, name: "http", isProxied: true, isExternal: true);
|
||||||
.WithLifetime(ContainerLifetime.Persistent);
|
|
||||||
|
if (builder.ExecutionContext.IsPublishMode)
|
||||||
|
{
|
||||||
|
prometheus.WithLifetime(ContainerLifetime.Persistent);
|
||||||
|
}
|
||||||
|
|
||||||
var grafana = builder.AddContainer("grafana", "grafana/grafana")
|
var grafana = builder.AddContainer("grafana", "grafana/grafana")
|
||||||
.WithEnvironment("GF_INSTALL_PLUGINS", "grafana-clock-panel,grafana-simple-json-datasource")
|
.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")
|
.WithEnvironment("GF_FEATURE_TOGGLES_ENABLE", "traceqlEditor")
|
||||||
.WithBindMount("../../../../deployments/configs/grafana/provisioning", "/etc/grafana/provisioning")
|
.WithBindMount("../../../../deployments/configs/grafana/provisioning", "/etc/grafana/provisioning")
|
||||||
.WithBindMount("../../../../deployments/configs/grafana/dashboards", "/var/lib/grafana/dashboards")
|
.WithBindMount("../../../../deployments/configs/grafana/dashboards", "/var/lib/grafana/dashboards")
|
||||||
.WithEndpoint(port: 3000, targetPort: 3000, name: "grafana-web")
|
.WithEndpoint(port: 3000, targetPort: 3000, name: "http", isProxied: true, isExternal: true);
|
||||||
.WithLifetime(ContainerLifetime.Persistent);
|
|
||||||
|
if (builder.ExecutionContext.IsPublishMode)
|
||||||
|
{
|
||||||
|
grafana.WithLifetime(ContainerLifetime.Persistent);
|
||||||
|
}
|
||||||
|
|
||||||
var nodeExporter = builder.AddContainer("node-exporter", "prom/node-exporter")
|
var nodeExporter = builder.AddContainer("node-exporter", "prom/node-exporter")
|
||||||
.WithBindMount("/proc", "/host/proc", isReadOnly: true)
|
.WithBindMount("/proc", "/host/proc", isReadOnly: true)
|
||||||
@ -108,22 +221,36 @@ var nodeExporter = builder.AddContainer("node-exporter", "prom/node-exporter")
|
|||||||
"--path.procfs=/host/proc",
|
"--path.procfs=/host/proc",
|
||||||
"--path.rootfs=/rootfs",
|
"--path.rootfs=/rootfs",
|
||||||
"--path.sysfs=/host/sys")
|
"--path.sysfs=/host/sys")
|
||||||
.WithEndpoint(port: 9101, targetPort: 9100, name: "node-exporter")
|
.WithEndpoint(port: 9101, targetPort: 9100, name: "http", isProxied: true, isExternal: true);
|
||||||
.WithLifetime(ContainerLifetime.Persistent);
|
|
||||||
|
if (builder.ExecutionContext.IsPublishMode)
|
||||||
|
{
|
||||||
|
nodeExporter.WithLifetime(ContainerLifetime.Persistent);
|
||||||
|
}
|
||||||
|
|
||||||
var tempo = builder.AddContainer("tempo", "grafana/tempo")
|
var tempo = builder.AddContainer("tempo", "grafana/tempo")
|
||||||
.WithBindMount("../../../../deployments/configs/tempo.yaml", "/etc/tempo.yaml", isReadOnly: true)
|
.WithBindMount("../../../../deployments/configs/tempo.yaml", "/etc/tempo.yaml", isReadOnly: true)
|
||||||
.WithArgs("--config.file=/etc/tempo.yaml")
|
.WithArgs("--config.file=/etc/tempo.yaml")
|
||||||
.WithEndpoint(port: 3200, targetPort: 3200, name: "tempo")
|
.WithEndpoint(port: 3200, targetPort: 3200, name: "http", isProxied: true, isExternal: false)
|
||||||
.WithEndpoint(port: 24317, targetPort: 4317, name: "tempo-otlp-grpc")
|
.WithEndpoint(port: 9095, targetPort: 9095, name: "grpc", isProxied: true, isExternal: false)
|
||||||
.WithEndpoint(port: 24318, targetPort: 4318, name: "tempo-otlp-http")
|
.WithEndpoint(port: 4317, targetPort: 4317, name: "otlp-grpc", isProxied: true, isExternal: false)
|
||||||
.WithLifetime(ContainerLifetime.Persistent);
|
.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")
|
var loki = builder.AddContainer("loki", "grafana/loki")
|
||||||
.WithBindMount("../../../../deployments/configs/loki-config.yaml", "/etc/loki/local-config.yaml", isReadOnly: true)
|
.WithBindMount("../../../../deployments/configs/loki-config.yaml", "/etc/loki/local-config.yaml", isReadOnly: true)
|
||||||
.WithArgs("-config.file=/etc/loki/local-config.yaml")
|
.WithArgs("-config.file=/etc/loki/local-config.yaml")
|
||||||
.WithEndpoint(port: 3100, targetPort: 3100, name: "loki")
|
.WithEndpoint(port: 3100, targetPort: 3100, name: "http", isProxied: true, isExternal: false)
|
||||||
.WithLifetime(ContainerLifetime.Persistent);
|
.WithEndpoint(port: 9096, targetPort: 9096, name: "grpc", isProxied: true, isExternal: false);
|
||||||
|
|
||||||
|
if (builder.ExecutionContext.IsPublishMode)
|
||||||
|
{
|
||||||
|
loki.WithLifetime(ContainerLifetime.Persistent);
|
||||||
|
}
|
||||||
|
|
||||||
var elasticsearch = builder.AddElasticsearch("elasticsearch")
|
var elasticsearch = builder.AddElasticsearch("elasticsearch")
|
||||||
.WithImage("docker.elastic.co/elasticsearch/elasticsearch:8.17.0")
|
.WithImage("docker.elastic.co/elasticsearch/elasticsearch:8.17.0")
|
||||||
@ -139,18 +266,41 @@ var elasticsearch = builder.AddElasticsearch("elasticsearch")
|
|||||||
.WithEnvironment("transport.host", "localhost")
|
.WithEnvironment("transport.host", "localhost")
|
||||||
.WithEnvironment("bootstrap.memory_lock", "true")
|
.WithEnvironment("bootstrap.memory_lock", "true")
|
||||||
.WithEnvironment("cluster.routing.allocation.disk.threshold_enabled", "false")
|
.WithEnvironment("cluster.routing.allocation.disk.threshold_enabled", "false")
|
||||||
.WithEndpoint(port: 9200, targetPort: 9200, name: "elasticsearch-http")
|
.WithEndpoint(
|
||||||
.WithEndpoint(port: 9300, targetPort: 9300, name: "elasticsearch-transport")
|
"http",
|
||||||
.WithDataVolume("elastic-data")
|
e =>
|
||||||
.WithLifetime(ContainerLifetime.Persistent);
|
{
|
||||||
|
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")
|
var kibana = builder.AddContainer("kibana", "docker.elastic.co/kibana/kibana:8.17.0")
|
||||||
.WithEnvironment("ELASTICSEARCH_HOSTS", "http://elasticsearch:9200")
|
.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)
|
.WithReference(elasticsearch)
|
||||||
.WaitFor(elasticsearch)
|
.WaitFor(elasticsearch);
|
||||||
.WithLifetime(ContainerLifetime.Persistent);
|
|
||||||
|
|
||||||
|
if (builder.ExecutionContext.IsPublishMode)
|
||||||
|
{
|
||||||
|
kibana.WithLifetime(ContainerLifetime.Persistent);
|
||||||
|
}
|
||||||
|
|
||||||
// 5. Application Services
|
// 5. Application Services
|
||||||
var identity = builder.AddProject<Projects.Identity_Api>("identity-service")
|
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>
|
<ItemGroup>
|
||||||
<PackageReference Include="Ardalis.GuardClauses" Version="5.0.0" />
|
<PackageReference Include="Ardalis.GuardClauses" Version="5.0.0" />
|
||||||
<PackageReference Include="Asp.Versioning.Abstractions" Version="8.1.0" />
|
<PackageReference Include="Asp.Versioning.Abstractions" Version="8.1.0" />
|
||||||
<PackageReference Include="Asp.Versioning.Http" Version="8.1.0" />
|
<PackageReference Include="Asp.Versioning.Http" Version="8.1.1" />
|
||||||
<PackageReference Include="Asp.Versioning.Mvc" Version="8.1.0" />
|
<PackageReference Include="Asp.Versioning.Mvc" Version="8.1.1" />
|
||||||
<PackageReference Include="Asp.Versioning.Mvc.ApiExplorer" Version="8.1.0" />
|
<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="Grpc.Core.Testing" Version="2.46.6" />
|
||||||
<PackageReference Include="EasyCaching.Core" Version="1.9.2" />
|
<PackageReference Include="EasyCaching.Core" Version="1.9.2" />
|
||||||
<PackageReference Include="EasyCaching.InMemory" Version="1.9.2" />
|
<PackageReference Include="EasyCaching.InMemory" Version="1.9.2" />
|
||||||
<PackageReference Include="EasyNetQ.Management.Client" Version="3.0.0" />
|
<PackageReference Include="EasyNetQ.Management.Client" Version="3.0.1" />
|
||||||
<PackageReference Include="EFCore.NamingConventions" Version="9.0.0" />
|
<PackageReference Include="EFCore.NamingConventions" Version="10.0.1" />
|
||||||
<PackageReference Include="Figgle" Version="0.5.1" />
|
<PackageReference Include="Figgle" Version="0.6.5" />
|
||||||
<PackageReference Include="FluentValidation" Version="11.11.0" />
|
<PackageReference Include="FluentValidation" Version="12.1.1" />
|
||||||
<PackageReference Include="FluentValidation.AspNetCore" Version="11.3.0" />
|
<PackageReference Include="FluentValidation.AspNetCore" Version="11.3.1" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.3" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.0" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="10.0.3" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="9.0.0" />
|
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="10.0.3" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="9.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="10.3.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.ServiceDiscovery" Version="9.3.1" />
|
<PackageReference Include="Microsoft.Extensions.ServiceDiscovery" Version="10.3.0" />
|
||||||
<PackageReference Include="Npgsql" Version="9.0.1" />
|
<PackageReference Include="Npgsql" Version="10.0.1" />
|
||||||
<PackageReference Include="NSubstitute" Version="5.3.0" />
|
<PackageReference Include="NSubstitute" Version="5.3.0" />
|
||||||
<PackageReference Include="Polly" Version="8.5.0" />
|
<PackageReference Include="Polly" Version="8.6.5" />
|
||||||
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
|
<PackageReference Include="Humanizer.Core" Version="3.0.1" />
|
||||||
<PackageReference Include="IdGen" Version="3.0.7" />
|
<PackageReference Include="IdGen" Version="3.0.7" />
|
||||||
<PackageReference Include="Mapster" Version="7.4.0" />
|
<PackageReference Include="Mapster" Version="7.4.0" />
|
||||||
<PackageReference Include="Mapster.DependencyInjection" Version="1.0.1" />
|
<PackageReference Include="Mapster.DependencyInjection" Version="1.0.1" />
|
||||||
<PackageReference Include="MediatR" Version="12.4.1" />
|
<PackageReference Include="MediatR" Version="14.0.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.3" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.0" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.3" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="9.0.0" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="10.0.3" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.0" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.3" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.3" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.3" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.3" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.0" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.3" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="9.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="10.0.3" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.3" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.3" />
|
||||||
<PackageReference Include="MongoDB.Driver" Version="3.1.0" />
|
<PackageReference Include="MongoDB.Driver" Version="3.6.0" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
||||||
<PackageReference Include="Scalar.AspNetCore" Version="1.2.64" />
|
<PackageReference Include="Scalar.AspNetCore" Version="2.12.38" />
|
||||||
<PackageReference Include="Scrutor" Version="5.0.2" />
|
<PackageReference Include="Scrutor" Version="7.0.0" />
|
||||||
<PackageReference Include="Sieve" Version="2.5.5" />
|
<PackageReference Include="Sieve" Version="2.5.5" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="7.1.0" />
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.1.2" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="7.1.0" />
|
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="10.1.2" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="7.1.0" />
|
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="10.1.2" />
|
||||||
<PackageReference Include="MassTransit" Version="8.3.6" />
|
<PackageReference Include="MassTransit" Version="8.5.8" />
|
||||||
<PackageReference Include="MassTransit.RabbitMQ" Version="8.3.6" />
|
<PackageReference Include="MassTransit.RabbitMQ" Version="8.5.8" />
|
||||||
<PackageReference Include="Duende.IdentityServer" Version="7.0.8" />
|
<PackageReference Include="Duende.IdentityServer" Version="7.4.5" />
|
||||||
<PackageReference Include="Duende.IdentityServer.AspNetIdentity" Version="7.0.8" />
|
<PackageReference Include="Duende.IdentityServer.AspNetIdentity" Version="7.4.5" />
|
||||||
<PackageReference Include="Duende.IdentityServer.EntityFramework" Version="7.0.8" />
|
<PackageReference Include="Duende.IdentityServer.EntityFramework" Version="7.4.5" />
|
||||||
<PackageReference Include="Duende.IdentityServer.EntityFramework.Storage" Version="7.0.8" />
|
<PackageReference Include="Duende.IdentityServer.EntityFramework.Storage" Version="7.4.5" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="10.0.3" />
|
||||||
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
|
<PackageReference Include="System.Linq.Async" Version="7.0.0" />
|
||||||
<PackageReference Include="System.Linq.Async.Queryable" Version="6.0.1" />
|
<PackageReference Include="System.Linq.Async.Queryable" Version="7.0.0" />
|
||||||
<PackageReference Include="Testcontainers" Version="4.0.0" />
|
<PackageReference Include="Testcontainers" Version="4.9.0" />
|
||||||
<PackageReference Include="Testcontainers.EventStoreDb" Version="4.0.0" />
|
<PackageReference Include="Testcontainers.EventStoreDb" Version="4.9.0" />
|
||||||
<PackageReference Include="Testcontainers.MongoDb" Version="4.0.0" />
|
<PackageReference Include="Testcontainers.MongoDb" Version="4.9.0" />
|
||||||
<PackageReference Include="Testcontainers.PostgreSql" Version="4.0.0" />
|
<PackageReference Include="Testcontainers.PostgreSql" Version="4.9.0" />
|
||||||
<PackageReference Include="Testcontainers.RabbitMq" Version="4.0.0" />
|
<PackageReference Include="Testcontainers.RabbitMq" Version="4.9.0" />
|
||||||
<PackageReference Include="Unchase.Swashbuckle.AspNetCore.Extensions" Version="2.7.1" />
|
|
||||||
<PackageReference Include="xunit.abstractions" Version="2.0.3" />
|
<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="Xunit.Extensions.Logging" Version="1.1.0" />
|
||||||
<PackageReference Include="Yarp.ReverseProxy" Version="2.2.0" />
|
<PackageReference Include="Yarp.ReverseProxy" Version="2.3.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="9.0.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" Version="9.0.0" />
|
||||||
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" 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" />
|
<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.Rabbitmq" Version="9.0.0" />
|
||||||
<PackageReference Include="AspNetCore.HealthChecks.MongoDb" Version="9.0.0" />
|
<PackageReference Include="AspNetCore.HealthChecks.MongoDb" Version="9.0.0" />
|
||||||
|
|
||||||
<PackageReference Include="Npgsql.OpenTelemetry" Version="9.0.1" />
|
<PackageReference Include="Npgsql.OpenTelemetry" Version="10.0.1" />
|
||||||
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.11.1" />
|
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.15.0" />
|
||||||
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.11.1"/>
|
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.15.0" />
|
||||||
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.11.0"/>
|
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.15.0" />
|
||||||
<PackageReference Include="OpenTelemetry.Instrumentation.GrpcNetClient" Version="1.11.0-beta.1"/>
|
<PackageReference Include="OpenTelemetry.Instrumentation.GrpcNetClient" Version="1.15.0-beta.1" />
|
||||||
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.11.0"/>
|
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.15.0" />
|
||||||
<PackageReference Include="OpenTelemetry.Instrumentation.Process" Version="1.11.0-beta.1"/>
|
<PackageReference Include="OpenTelemetry.Instrumentation.Process" Version="1.15.0-beta.1" />
|
||||||
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.11.0"/>
|
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.15.0" />
|
||||||
<PackageReference Include="OpenTelemetry.Exporter.Prometheus.AspNetCore" Version="1.11.0-beta.1"/>
|
<PackageReference Include="OpenTelemetry.Exporter.Prometheus.AspNetCore" Version="1.15.0-beta.1" />
|
||||||
<PackageReference Include="Grafana.OpenTelemetry" Version="1.2.0"/>
|
<PackageReference Include="Grafana.OpenTelemetry" Version="1.5.2" />
|
||||||
<PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.11.1"/>
|
<PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.15.0" />
|
||||||
<PackageReference Include="OpenTelemetry.Exporter.Zipkin" Version="1.11.1"/>
|
<PackageReference Include="OpenTelemetry.Exporter.Zipkin" Version="1.15.0" />
|
||||||
|
|
||||||
<PackageReference Include="EventStore.Client.Grpc.Streams" Version="23.3.7" />
|
<PackageReference Include="EventStore.Client.Grpc.Streams" Version="23.3.9" />
|
||||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.1" />
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.0" />
|
||||||
|
|
||||||
<PackageReference Include="AutoBogus" Version="2.13.1" />
|
<PackageReference Include="AutoBogus" Version="2.13.1" />
|
||||||
<PackageReference Include="Bogus" Version="35.6.1" />
|
<PackageReference Include="Bogus" Version="35.6.5" />
|
||||||
<PackageReference Include="FluentAssertions" Version="7.0.0" />
|
<PackageReference Include="FluentAssertions" Version="8.8.0" />
|
||||||
<PackageReference Include="Respawn" Version="6.2.1" />
|
<PackageReference Include="Respawn" Version="7.0.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="9.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="10.0.3" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.3" />
|
||||||
<PackageReference Include="WebMotions.Fake.Authentication.JwtBearer" Version="8.0.1" />
|
<PackageReference Include="WebMotions.Fake.Authentication.JwtBearer" Version="10.0.0" />
|
||||||
|
|
||||||
<PackageReference Include="Google.Protobuf" Version="3.29.1" />
|
<PackageReference Include="Google.Protobuf" Version="3.33.5" />
|
||||||
<PackageReference Include="Grpc.Net.ClientFactory" Version="2.67.0" />
|
<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>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
using Microsoft.AspNetCore.OpenApi;
|
using Microsoft.AspNetCore.OpenApi;
|
||||||
using Microsoft.OpenApi.Models;
|
using Microsoft.OpenApi;
|
||||||
|
|
||||||
public class SecuritySchemeDocumentTransformer : IOpenApiDocumentTransformer
|
public class SecuritySchemeDocumentTransformer : IOpenApiDocumentTransformer
|
||||||
{
|
{
|
||||||
@ -9,12 +9,14 @@ public class SecuritySchemeDocumentTransformer : IOpenApiDocumentTransformer
|
|||||||
CancellationToken cancellationToken
|
CancellationToken cancellationToken
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
document.Components ??= new();
|
document.Components ??= new OpenApiComponents();
|
||||||
|
|
||||||
// Bearer token scheme
|
// Initialize with the correct interface type
|
||||||
document.Components.SecuritySchemes.Add(
|
document.Components.SecuritySchemes ??= new Dictionary<string, IOpenApiSecurityScheme>();
|
||||||
"Bearer",
|
|
||||||
new OpenApiSecurityScheme
|
var securitySchemes = new Dictionary<string, IOpenApiSecurityScheme>
|
||||||
|
{
|
||||||
|
["Bearer"] = new OpenApiSecurityScheme
|
||||||
{
|
{
|
||||||
Name = "Authorization",
|
Name = "Authorization",
|
||||||
Type = SecuritySchemeType.Http,
|
Type = SecuritySchemeType.Http,
|
||||||
@ -23,21 +25,23 @@ public class SecuritySchemeDocumentTransformer : IOpenApiDocumentTransformer
|
|||||||
In = ParameterLocation.Header,
|
In = ParameterLocation.Header,
|
||||||
Description =
|
Description =
|
||||||
"Enter 'Bearer' [space] and your token in the text input below.\n\nExample: 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'",
|
"Enter 'Bearer' [space] and your token in the text input below.\n\nExample: 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'",
|
||||||
}
|
},
|
||||||
);
|
["ApiKey"] = new OpenApiSecurityScheme
|
||||||
|
|
||||||
// API Key scheme
|
|
||||||
document.Components.SecuritySchemes.Add(
|
|
||||||
"ApiKey",
|
|
||||||
new OpenApiSecurityScheme
|
|
||||||
{
|
{
|
||||||
Name = "X-API-KEY",
|
Name = "X-API-KEY",
|
||||||
Type = SecuritySchemeType.ApiKey,
|
Type = SecuritySchemeType.ApiKey,
|
||||||
In = ParameterLocation.Header,
|
In = ParameterLocation.Header,
|
||||||
Description =
|
Description = "Enter your API key in the text input below.\n\nExample: '12345-abcdef'",
|
||||||
"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;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -162,10 +162,7 @@ public static class Extensions
|
|||||||
{
|
{
|
||||||
instrumentationOptions.RecordException = true;
|
instrumentationOptions.RecordException = true;
|
||||||
})
|
})
|
||||||
.AddEntityFrameworkCoreInstrumentation(instrumentationOptions =>
|
.AddEntityFrameworkCoreInstrumentation()
|
||||||
{
|
|
||||||
instrumentationOptions.SetDbStatementForText = true;
|
|
||||||
})
|
|
||||||
.AddSource(DiagnosticHeaders.DefaultListenerName)
|
.AddSource(DiagnosticHeaders.DefaultListenerName)
|
||||||
.AddNpgsql()
|
.AddNpgsql()
|
||||||
// `AddSource` for adding custom activity sources
|
// `AddSource` for adding custom activity sources
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using Ardalis.GuardClauses;
|
|
||||||
using BuildingBlocks.Core.Event;
|
using BuildingBlocks.Core.Event;
|
||||||
using BuildingBlocks.Core.Model;
|
using BuildingBlocks.Core.Model;
|
||||||
using BuildingBlocks.EFCore;
|
using BuildingBlocks.EFCore;
|
||||||
@ -13,7 +12,6 @@ using Grpc.Net.Client;
|
|||||||
using MassTransit;
|
using MassTransit;
|
||||||
using MassTransit.Testing;
|
using MassTransit.Testing;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc.Testing;
|
using Microsoft.AspNetCore.Mvc.Testing;
|
||||||
@ -38,11 +36,11 @@ using Testcontainers.PostgreSql;
|
|||||||
using Testcontainers.RabbitMq;
|
using Testcontainers.RabbitMq;
|
||||||
|
|
||||||
public class TestFixture<TEntryPoint> : IAsyncLifetime
|
public class TestFixture<TEntryPoint> : IAsyncLifetime
|
||||||
where TEntryPoint : class
|
where TEntryPoint : class
|
||||||
{
|
{
|
||||||
private readonly WebApplicationFactory<TEntryPoint> _factory;
|
private readonly WebApplicationFactory<TEntryPoint> _factory;
|
||||||
private int Timeout => 120; // Second
|
private int Timeout => 120; // Second
|
||||||
private ITestHarness TestHarness => ServiceProvider?.GetTestHarness();
|
public ITestHarness TestHarness => ServiceProvider?.GetTestHarness();
|
||||||
private Action<IServiceCollection> TestRegistrationServices { get; set; }
|
private Action<IServiceCollection> TestRegistrationServices { get; set; }
|
||||||
private PostgreSqlContainer PostgresTestcontainer;
|
private PostgreSqlContainer PostgresTestcontainer;
|
||||||
private PostgreSqlContainer PostgresPersistTestContainer;
|
private PostgreSqlContainer PostgresPersistTestContainer;
|
||||||
@ -59,11 +57,11 @@ where TEntryPoint : class
|
|||||||
get
|
get
|
||||||
{
|
{
|
||||||
var claims = new Dictionary<string, object>
|
var claims = new Dictionary<string, object>
|
||||||
{
|
{
|
||||||
{ ClaimTypes.Name, "test@sample.com" },
|
{ ClaimTypes.Name, "test@sample.com" },
|
||||||
{ ClaimTypes.Role, "admin" },
|
{ ClaimTypes.Role, "admin" },
|
||||||
{ "scope", "flight-api" }
|
{ "scope", "flight-api" },
|
||||||
};
|
};
|
||||||
|
|
||||||
var httpClient = _factory.CreateClient();
|
var httpClient = _factory.CreateClient();
|
||||||
httpClient.SetFakeBearerToken(claims); // Uses FakeJwtBearer
|
httpClient.SetFakeBearerToken(claims); // Uses FakeJwtBearer
|
||||||
@ -72,9 +70,7 @@ where TEntryPoint : class
|
|||||||
}
|
}
|
||||||
|
|
||||||
public GrpcChannel Channel =>
|
public GrpcChannel Channel =>
|
||||||
GrpcChannel.ForAddress(
|
GrpcChannel.ForAddress(HttpClient.BaseAddress!, new GrpcChannelOptions { HttpClient = HttpClient });
|
||||||
HttpClient.BaseAddress!,
|
|
||||||
new GrpcChannelOptions { HttpClient = HttpClient });
|
|
||||||
|
|
||||||
public IServiceProvider ServiceProvider => _factory?.Services;
|
public IServiceProvider ServiceProvider => _factory?.Services;
|
||||||
public IConfiguration Configuration => _factory?.Services.GetRequiredService<IConfiguration>();
|
public IConfiguration Configuration => _factory?.Services.GetRequiredService<IConfiguration>();
|
||||||
@ -82,54 +78,55 @@ where TEntryPoint : class
|
|||||||
|
|
||||||
protected TestFixture()
|
protected TestFixture()
|
||||||
{
|
{
|
||||||
_factory = new WebApplicationFactory<TEntryPoint>()
|
_factory = new WebApplicationFactory<TEntryPoint>().WithWebHostBuilder(builder =>
|
||||||
.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);
|
options.AddPolicy(
|
||||||
|
nameof(ApiScope),
|
||||||
builder.UseEnvironment("test");
|
policy =>
|
||||||
|
|
||||||
builder.ConfigureServices(
|
|
||||||
services =>
|
|
||||||
{
|
{
|
||||||
TestRegistrationServices?.Invoke(services);
|
policy.AddAuthenticationSchemes(FakeJwtBearerDefaults.AuthenticationScheme);
|
||||||
services.ReplaceSingleton(AddHttpContextAccessorMock);
|
policy.RequireAuthenticatedUser();
|
||||||
|
policy.RequireClaim("scope", "flight-api"); // Test-specific scope
|
||||||
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
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task InitializeAsync()
|
public async Task InitializeAsync()
|
||||||
@ -157,10 +154,10 @@ where TEntryPoint : class
|
|||||||
return null;
|
return null;
|
||||||
|
|
||||||
var loggerFactory = LoggerFactory.Create(builder =>
|
var loggerFactory = LoggerFactory.Create(builder =>
|
||||||
{
|
{
|
||||||
builder.AddXunit(output);
|
builder.AddXunit(output);
|
||||||
builder.SetMinimumLevel(LogLevel.Debug);
|
builder.SetMinimumLevel(LogLevel.Debug);
|
||||||
});
|
});
|
||||||
return loggerFactory.CreateLogger("TestLogger");
|
return loggerFactory.CreateLogger("TestLogger");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -179,132 +176,93 @@ where TEntryPoint : class
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public Task<TResponse> SendAsync<TResponse>(IRequest<TResponse> request)
|
public Task<TResponse> SendAsync<TResponse>(IRequest<TResponse> request)
|
||||||
{
|
{
|
||||||
return ExecuteScopeAsync(
|
return ExecuteScopeAsync(sp =>
|
||||||
sp =>
|
{
|
||||||
{
|
var mediator = sp.GetRequiredService<IMediator>();
|
||||||
var mediator = sp.GetRequiredService<IMediator>();
|
|
||||||
|
|
||||||
return mediator.Send(request);
|
return mediator.Send(request);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task SendAsync(IRequest request)
|
public Task SendAsync(IRequest request)
|
||||||
{
|
{
|
||||||
return ExecuteScopeAsync(
|
return ExecuteScopeAsync(sp =>
|
||||||
sp =>
|
{
|
||||||
{
|
var mediator = sp.GetRequiredService<IMediator>();
|
||||||
var mediator = sp.GetRequiredService<IMediator>();
|
return mediator.Send(request);
|
||||||
return mediator.Send(request);
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Publish<TMessage>(
|
public async Task Publish<TMessage>(TMessage message, CancellationToken cancellationToken = default)
|
||||||
TMessage message,
|
where TMessage : class, IEvent
|
||||||
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);
|
await TestHarness.Bus.Publish(message, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> WaitForPublishing<TMessage>(
|
public async Task<bool> WaitForPublishing<TMessage>(CancellationToken cancellationToken = default)
|
||||||
CancellationToken cancellationToken = default
|
where TMessage : class, IEvent
|
||||||
)
|
|
||||||
where TMessage : class, IEvent
|
|
||||||
{
|
{
|
||||||
var result = await WaitUntilConditionMet(
|
var result = await WaitUntilConditionMet(
|
||||||
async () =>
|
async () =>
|
||||||
{
|
{
|
||||||
var published =
|
var published = await TestHarness.Published.Any<TMessage>(cancellationToken);
|
||||||
await TestHarness.Published.Any<TMessage>(cancellationToken);
|
|
||||||
|
|
||||||
return published;
|
return published;
|
||||||
});
|
},
|
||||||
|
cancellationToken: cancellationToken
|
||||||
|
);
|
||||||
|
|
||||||
return result;
|
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
|
CancellationToken cancellationToken = default
|
||||||
)
|
)
|
||||||
where TMessage : class, IEvent
|
|
||||||
{
|
{
|
||||||
var result = await WaitUntilConditionMet(
|
var effectiveTimeout = timeout ?? TimeSpan.FromSeconds(Timeout);
|
||||||
async () =>
|
var effectivePollInterval = pollInterval ?? TimeSpan.FromMilliseconds(200);
|
||||||
{
|
|
||||||
var consumed =
|
|
||||||
await TestHarness.Consumed.Any<TMessage>(cancellationToken);
|
|
||||||
|
|
||||||
return consumed;
|
return WaitUntilConditionMet(
|
||||||
});
|
conditionToMet: async () =>
|
||||||
|
{
|
||||||
return result;
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
}
|
return await condition();
|
||||||
|
},
|
||||||
public async Task<bool> ShouldProcessedPersistInternalCommand<TInternalCommand>(
|
timeoutSecond: (int)Math.Ceiling(effectiveTimeout.TotalSeconds),
|
||||||
CancellationToken cancellationToken = default
|
pollInterval: effectivePollInterval,
|
||||||
)
|
cancellationToken: cancellationToken
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ref: https://tech.energyhelpline.com/in-memory-testing-with-masstransit/
|
// Ref: https://tech.energyhelpline.com/in-memory-testing-with-masstransit/
|
||||||
private async Task<bool> WaitUntilConditionMet(
|
private async Task<bool> WaitUntilConditionMet(
|
||||||
Func<Task<bool>> conditionToMet,
|
Func<Task<bool>> conditionToMet,
|
||||||
int? timeoutSecond = null
|
int? timeoutSecond = null,
|
||||||
|
TimeSpan? pollInterval = null,
|
||||||
|
CancellationToken cancellationToken = default
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
var time = timeoutSecond ?? Timeout;
|
var time = timeoutSecond ?? Timeout;
|
||||||
|
var delay = pollInterval ?? TimeSpan.FromMilliseconds(100);
|
||||||
|
|
||||||
var startTime = DateTime.Now;
|
var startTime = DateTime.UtcNow;
|
||||||
var timeoutExpired = false;
|
while (DateTime.UtcNow - startTime <= TimeSpan.FromSeconds(time))
|
||||||
var meet = await conditionToMet.Invoke();
|
|
||||||
|
|
||||||
while (!meet)
|
|
||||||
{
|
{
|
||||||
if (timeoutExpired)
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
await Task.Delay(100);
|
if (await conditionToMet.Invoke())
|
||||||
meet = await conditionToMet.Invoke();
|
return true;
|
||||||
timeoutExpired = DateTime.Now - startTime > TimeSpan.FromSeconds(time);
|
|
||||||
|
await Task.Delay(delay, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task StartTestContainerAsync()
|
private async Task StartTestContainerAsync()
|
||||||
@ -337,39 +295,25 @@ where TEntryPoint : class
|
|||||||
configuration.AddInMemoryCollection(
|
configuration.AddInMemoryCollection(
|
||||||
new KeyValuePair<string, string>[]
|
new KeyValuePair<string, string>[]
|
||||||
{
|
{
|
||||||
new(
|
new("PostgresOptions:ConnectionString", PostgresTestcontainer.GetConnectionString()),
|
||||||
"PostgresOptions:ConnectionString",
|
new("PostgresOptions:ConnectionString:Flight", PostgresTestcontainer.GetConnectionString()),
|
||||||
PostgresTestcontainer.GetConnectionString()),
|
new("PostgresOptions:ConnectionString:Identity", PostgresTestcontainer.GetConnectionString()),
|
||||||
new(
|
new("PostgresOptions:ConnectionString:Passenger", PostgresTestcontainer.GetConnectionString()),
|
||||||
"PostgresOptions:ConnectionString:Flight",
|
new("PersistMessageOptions:ConnectionString", PostgresPersistTestContainer.GetConnectionString()),
|
||||||
PostgresTestcontainer.GetConnectionString()),
|
new("RabbitMqOptions:HostName", "127.0.0.1"),
|
||||||
new(
|
new("RabbitMqOptions:UserName", TestContainers.RabbitMqContainerConfiguration.UserName),
|
||||||
"PostgresOptions:ConnectionString:Identity",
|
new("RabbitMqOptions:Password", TestContainers.RabbitMqContainerConfiguration.Password),
|
||||||
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(
|
new(
|
||||||
"RabbitMqOptions:Port",
|
"RabbitMqOptions:Port",
|
||||||
RabbitMqTestContainer.GetMappedPublicPort(
|
RabbitMqTestContainer
|
||||||
TestContainers.RabbitMqContainerConfiguration.Port)
|
.GetMappedPublicPort(TestContainers.RabbitMqContainerConfiguration.Port)
|
||||||
.ToString(NumberFormatInfo.InvariantInfo)),
|
.ToString(NumberFormatInfo.InvariantInfo)
|
||||||
|
),
|
||||||
new("MongoOptions:ConnectionString", MongoDbTestContainer.GetConnectionString()),
|
new("MongoOptions:ConnectionString", MongoDbTestContainer.GetConnectionString()),
|
||||||
new("MongoOptions:DatabaseName", TestContainers.MongoContainerConfiguration.Name),
|
new("MongoOptions:DatabaseName", TestContainers.MongoContainerConfiguration.Name),
|
||||||
new(
|
new("EventStoreOptions:ConnectionString", EventStoreDbTestContainer.GetConnectionString()),
|
||||||
"EventStoreOptions:ConnectionString",
|
}
|
||||||
EventStoreDbTestContainer.GetConnectionString())
|
);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private IHttpContextAccessor AddHttpContextAccessorMock(IServiceProvider serviceProvider)
|
private IHttpContextAccessor AddHttpContextAccessorMock(IServiceProvider serviceProvider)
|
||||||
@ -377,8 +321,7 @@ where TEntryPoint : class
|
|||||||
var httpContextAccessorMock = Substitute.For<IHttpContextAccessor>();
|
var httpContextAccessorMock = Substitute.For<IHttpContextAccessor>();
|
||||||
using var scope = serviceProvider.CreateScope();
|
using var scope = serviceProvider.CreateScope();
|
||||||
|
|
||||||
httpContextAccessorMock.HttpContext = new DefaultHttpContext
|
httpContextAccessorMock.HttpContext = new DefaultHttpContext { RequestServices = scope.ServiceProvider };
|
||||||
{ RequestServices = scope.ServiceProvider };
|
|
||||||
|
|
||||||
httpContextAccessorMock.HttpContext.Request.Host = new HostString("localhost", 6012);
|
httpContextAccessorMock.HttpContext.Request.Host = new HostString("localhost", 6012);
|
||||||
httpContextAccessorMock.HttpContext.Request.Scheme = "http";
|
httpContextAccessorMock.HttpContext.Request.Scheme = "http";
|
||||||
@ -388,100 +331,90 @@ where TEntryPoint : class
|
|||||||
}
|
}
|
||||||
|
|
||||||
public class TestWriteFixture<TEntryPoint, TWContext> : TestFixture<TEntryPoint>
|
public class TestWriteFixture<TEntryPoint, TWContext> : TestFixture<TEntryPoint>
|
||||||
where TEntryPoint : class
|
where TEntryPoint : class
|
||||||
where TWContext : DbContext
|
where TWContext : DbContext
|
||||||
{
|
{
|
||||||
public Task ExecuteDbContextAsync(Func<TWContext, Task> action)
|
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)
|
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)
|
public Task ExecuteDbContextAsync(Func<TWContext, IMediator, Task> action)
|
||||||
{
|
{
|
||||||
return ExecuteScopeAsync(
|
return ExecuteScopeAsync(sp => action(sp.GetRequiredService<TWContext>(), sp.GetRequiredService<IMediator>()));
|
||||||
sp => action(sp.GetService<TWContext>(), sp.GetService<IMediator>()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<T> ExecuteDbContextAsync<T>(Func<TWContext, Task<T>> action)
|
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)
|
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)
|
public Task<T> ExecuteDbContextAsync<T>(Func<TWContext, IMediator, Task<T>> action)
|
||||||
{
|
{
|
||||||
return ExecuteScopeAsync(
|
return ExecuteScopeAsync(sp => action(sp.GetRequiredService<TWContext>(), sp.GetRequiredService<IMediator>()));
|
||||||
sp => action(sp.GetService<TWContext>(), sp.GetService<IMediator>()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task InsertAsync<T>(params T[] entities)
|
public Task InsertAsync<T>(params T[] entities)
|
||||||
where T : class
|
where T : class
|
||||||
{
|
{
|
||||||
return ExecuteDbContextAsync(
|
return ExecuteDbContextAsync(db =>
|
||||||
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)
|
public async Task InsertAsync<TEntity>(TEntity entity)
|
||||||
where TEntity : class
|
where TEntity : class
|
||||||
{
|
{
|
||||||
await ExecuteDbContextAsync(
|
await ExecuteDbContextAsync(db =>
|
||||||
db =>
|
{
|
||||||
{
|
db.Set<TEntity>().Add(entity);
|
||||||
db.Set<TEntity>().Add(entity);
|
|
||||||
|
|
||||||
return db.SaveChangesAsync();
|
return db.SaveChangesAsync();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task InsertAsync<TEntity, TEntity2>(TEntity entity, TEntity2 entity2)
|
public Task InsertAsync<TEntity, TEntity2>(TEntity entity, TEntity2 entity2)
|
||||||
where TEntity : class
|
where TEntity : class
|
||||||
where TEntity2 : class
|
where TEntity2 : class
|
||||||
{
|
{
|
||||||
return ExecuteDbContextAsync(
|
return ExecuteDbContextAsync(db =>
|
||||||
db =>
|
{
|
||||||
{
|
db.Set<TEntity>().Add(entity);
|
||||||
db.Set<TEntity>().Add(entity);
|
db.Set<TEntity2>().Add(entity2);
|
||||||
db.Set<TEntity2>().Add(entity2);
|
|
||||||
|
|
||||||
return db.SaveChangesAsync();
|
return db.SaveChangesAsync();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task InsertAsync<TEntity, TEntity2, TEntity3>(
|
public Task InsertAsync<TEntity, TEntity2, TEntity3>(TEntity entity, TEntity2 entity2, TEntity3 entity3)
|
||||||
TEntity entity,
|
where TEntity : class
|
||||||
TEntity2 entity2,
|
where TEntity2 : class
|
||||||
TEntity3 entity3
|
where TEntity3 : class
|
||||||
)
|
|
||||||
where TEntity : class
|
|
||||||
where TEntity2 : class
|
|
||||||
where TEntity3 : class
|
|
||||||
{
|
{
|
||||||
return ExecuteDbContextAsync(
|
return ExecuteDbContextAsync(db =>
|
||||||
db =>
|
{
|
||||||
{
|
db.Set<TEntity>().Add(entity);
|
||||||
db.Set<TEntity>().Add(entity);
|
db.Set<TEntity2>().Add(entity2);
|
||||||
db.Set<TEntity2>().Add(entity2);
|
db.Set<TEntity3>().Add(entity3);
|
||||||
db.Set<TEntity3>().Add(entity3);
|
|
||||||
|
|
||||||
return db.SaveChangesAsync();
|
return db.SaveChangesAsync();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task InsertAsync<TEntity, TEntity2, TEntity3, TEntity4>(
|
public Task InsertAsync<TEntity, TEntity2, TEntity3, TEntity4>(
|
||||||
@ -490,39 +423,38 @@ where TWContext : DbContext
|
|||||||
TEntity3 entity3,
|
TEntity3 entity3,
|
||||||
TEntity4 entity4
|
TEntity4 entity4
|
||||||
)
|
)
|
||||||
where TEntity : class
|
where TEntity : class
|
||||||
where TEntity2 : class
|
where TEntity2 : class
|
||||||
where TEntity3 : class
|
where TEntity3 : class
|
||||||
where TEntity4 : class
|
where TEntity4 : class
|
||||||
{
|
{
|
||||||
return ExecuteDbContextAsync(
|
return ExecuteDbContextAsync(db =>
|
||||||
db =>
|
{
|
||||||
{
|
db.Set<TEntity>().Add(entity);
|
||||||
db.Set<TEntity>().Add(entity);
|
db.Set<TEntity2>().Add(entity2);
|
||||||
db.Set<TEntity2>().Add(entity2);
|
db.Set<TEntity3>().Add(entity3);
|
||||||
db.Set<TEntity3>().Add(entity3);
|
db.Set<TEntity4>().Add(entity4);
|
||||||
db.Set<TEntity4>().Add(entity4);
|
|
||||||
|
|
||||||
return db.SaveChangesAsync();
|
return db.SaveChangesAsync();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<T> FindAsync<T, TKey>(TKey id)
|
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());
|
return ExecuteDbContextAsync(db => db.Set<T>().FindAsync(id).AsTask());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<T> FirstOrDefaultAsync<T>()
|
public Task<T> FirstOrDefaultAsync<T>()
|
||||||
where T : class, IEntity
|
where T : class, IEntity
|
||||||
{
|
{
|
||||||
return ExecuteDbContextAsync(db => db.Set<T>().FirstOrDefaultAsync());
|
return ExecuteDbContextAsync(db => db.Set<T>().FirstOrDefaultAsync());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TestReadFixture<TEntryPoint, TRContext> : TestFixture<TEntryPoint>
|
public class TestReadFixture<TEntryPoint, TRContext> : TestFixture<TEntryPoint>
|
||||||
where TEntryPoint : class
|
where TEntryPoint : class
|
||||||
where TRContext : MongoDbContext
|
where TRContext : MongoDbContext
|
||||||
{
|
{
|
||||||
public Task ExecuteReadContextAsync(Func<TRContext, Task> action)
|
public Task ExecuteReadContextAsync(Func<TRContext, Task> action)
|
||||||
{
|
{
|
||||||
@ -535,21 +467,19 @@ where TRContext : MongoDbContext
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async Task InsertMongoDbContextAsync<T>(string collectionName, params T[] entities)
|
public async Task InsertMongoDbContextAsync<T>(string collectionName, params T[] entities)
|
||||||
where T : class
|
where T : class
|
||||||
{
|
{
|
||||||
await ExecuteReadContextAsync(
|
await ExecuteReadContextAsync(async db =>
|
||||||
async db =>
|
{
|
||||||
{
|
await db.GetCollection<T>(collectionName).InsertManyAsync(entities.ToList());
|
||||||
await db.GetCollection<T>(collectionName).InsertManyAsync(entities.ToList());
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TestFixture<TEntryPoint, TWContext, TRContext>
|
public class TestFixture<TEntryPoint, TWContext, TRContext> : TestWriteFixture<TEntryPoint, TWContext>
|
||||||
: TestWriteFixture<TEntryPoint, TWContext>
|
where TEntryPoint : class
|
||||||
where TEntryPoint : class
|
where TWContext : DbContext
|
||||||
where TWContext : DbContext
|
where TRContext : MongoDbContext
|
||||||
where TRContext : MongoDbContext
|
|
||||||
{
|
{
|
||||||
public Task ExecuteReadContextAsync(Func<TRContext, Task> action)
|
public Task ExecuteReadContextAsync(Func<TRContext, Task> action)
|
||||||
{
|
{
|
||||||
@ -562,38 +492,38 @@ where TRContext : MongoDbContext
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async Task InsertMongoDbContextAsync<T>(string collectionName, params T[] entities)
|
public async Task InsertMongoDbContextAsync<T>(string collectionName, params T[] entities)
|
||||||
where T : class
|
where T : class
|
||||||
{
|
{
|
||||||
await ExecuteReadContextAsync(
|
await ExecuteReadContextAsync(async db =>
|
||||||
async db =>
|
{
|
||||||
{
|
await db.GetCollection<T>(collectionName).InsertManyAsync(entities.ToList());
|
||||||
await db.GetCollection<T>(collectionName).InsertManyAsync(entities.ToList());
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TestFixtureCore<TEntryPoint> : IAsyncLifetime
|
public class TestFixtureCore<TEntryPoint> : IAsyncLifetime
|
||||||
where TEntryPoint : class
|
where TEntryPoint : class
|
||||||
{
|
{
|
||||||
private Respawner _reSpawnerDefaultDb;
|
private Respawner _reSpawnerDefaultDb;
|
||||||
private Respawner _reSpawnerPersistDb;
|
private Respawner _reSpawnerPersistDb;
|
||||||
private NpgsqlConnection DefaultDbConnection { get; set; }
|
private NpgsqlConnection DefaultDbConnection { get; set; }
|
||||||
private NpgsqlConnection PersistDbConnection { get; set; }
|
private NpgsqlConnection PersistDbConnection { get; set; }
|
||||||
|
private Type _dbContextType;
|
||||||
|
|
||||||
public TestFixtureCore(
|
public TestFixtureCore(
|
||||||
TestFixture<TEntryPoint> integrationTestFixture,
|
TestFixture<TEntryPoint> integrationTestFixture,
|
||||||
ITestOutputHelper outputHelper
|
ITestOutputHelper outputHelper,
|
||||||
|
Type dbContextType = null
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
Fixture = integrationTestFixture;
|
Fixture = integrationTestFixture;
|
||||||
integrationTestFixture.RegisterServices(RegisterTestsServices);
|
integrationTestFixture.RegisterServices(RegisterTestsServices);
|
||||||
integrationTestFixture.Logger = integrationTestFixture.CreateLogger(outputHelper);
|
integrationTestFixture.Logger = integrationTestFixture.CreateLogger(outputHelper);
|
||||||
|
_dbContextType = dbContextType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TestFixture<TEntryPoint> Fixture { get; }
|
public TestFixture<TEntryPoint> Fixture { get; }
|
||||||
|
|
||||||
|
|
||||||
public async Task InitializeAsync()
|
public async Task InitializeAsync()
|
||||||
{
|
{
|
||||||
await InitPostgresAsync();
|
await InitPostgresAsync();
|
||||||
@ -613,25 +543,37 @@ where TEntryPoint : class
|
|||||||
|
|
||||||
if (!string.IsNullOrEmpty(persistOptions?.ConnectionString))
|
if (!string.IsNullOrEmpty(persistOptions?.ConnectionString))
|
||||||
{
|
{
|
||||||
await Fixture.PersistMessageBackgroundService.StartAsync(
|
|
||||||
Fixture.CancellationTokenSource.Token);
|
|
||||||
|
|
||||||
PersistDbConnection = new NpgsqlConnection(persistOptions.ConnectionString);
|
PersistDbConnection = new NpgsqlConnection(persistOptions.ConnectionString);
|
||||||
await PersistDbConnection.OpenAsync();
|
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(
|
_reSpawnerPersistDb = await Respawner.CreateAsync(
|
||||||
PersistDbConnection,
|
PersistDbConnection,
|
||||||
new RespawnerOptions { DbAdapter = DbAdapter.Postgres });
|
new RespawnerOptions { DbAdapter = DbAdapter.Postgres }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(postgresOptions?.ConnectionString))
|
if (!string.IsNullOrEmpty(postgresOptions?.ConnectionString) && _dbContextType != null)
|
||||||
{
|
{
|
||||||
DefaultDbConnection = new NpgsqlConnection(postgresOptions.ConnectionString);
|
DefaultDbConnection = new NpgsqlConnection(postgresOptions.ConnectionString);
|
||||||
await DefaultDbConnection.OpenAsync();
|
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(
|
_reSpawnerDefaultDb = await Respawner.CreateAsync(
|
||||||
DefaultDbConnection,
|
DefaultDbConnection,
|
||||||
new RespawnerOptions { DbAdapter = DbAdapter.Postgres });
|
new RespawnerOptions { DbAdapter = DbAdapter.Postgres, TablesToIgnore = ["__EFMigrationsHistory"] }
|
||||||
|
);
|
||||||
|
|
||||||
await SeedDataAsync();
|
await SeedDataAsync();
|
||||||
}
|
}
|
||||||
@ -643,8 +585,7 @@ where TEntryPoint : class
|
|||||||
{
|
{
|
||||||
await _reSpawnerPersistDb.ResetAsync(PersistDbConnection);
|
await _reSpawnerPersistDb.ResetAsync(PersistDbConnection);
|
||||||
|
|
||||||
await Fixture.PersistMessageBackgroundService.StopAsync(
|
await Fixture.PersistMessageBackgroundService.StopAsync(Fixture.CancellationTokenSource.Token);
|
||||||
Fixture.CancellationTokenSource.Token);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (DefaultDbConnection is not null)
|
if (DefaultDbConnection is not null)
|
||||||
@ -659,31 +600,33 @@ where TEntryPoint : class
|
|||||||
var dbClient = new MongoClient(Fixture.MongoDbTestContainer?.GetConnectionString());
|
var dbClient = new MongoClient(Fixture.MongoDbTestContainer?.GetConnectionString());
|
||||||
|
|
||||||
var collections = await dbClient
|
var collections = await dbClient
|
||||||
.GetDatabase(TestContainers.MongoContainerConfiguration.Name)
|
.GetDatabase(TestContainers.MongoContainerConfiguration.Name)
|
||||||
.ListCollectionsAsync(cancellationToken: cancellationToken);
|
.ListCollectionsAsync(cancellationToken: cancellationToken);
|
||||||
|
|
||||||
foreach (var collection in collections.ToList())
|
foreach (var collection in collections.ToList())
|
||||||
{
|
{
|
||||||
await dbClient.GetDatabase(TestContainers.MongoContainerConfiguration.Name)
|
await dbClient
|
||||||
|
.GetDatabase(TestContainers.MongoContainerConfiguration.Name)
|
||||||
.DropCollectionAsync(collection["name"].AsString, cancellationToken);
|
.DropCollectionAsync(collection["name"].AsString, cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ResetRabbitMqAsync(CancellationToken cancellationToken = default)
|
private async Task ResetRabbitMqAsync(CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var port = Fixture.RabbitMqTestContainer?.GetMappedPublicPort(
|
var port =
|
||||||
TestContainers.RabbitMqContainerConfiguration
|
Fixture.RabbitMqTestContainer?.GetMappedPublicPort(TestContainers.RabbitMqContainerConfiguration.ApiPort)
|
||||||
.ApiPort) ??
|
?? 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?.UserName,
|
||||||
TestContainers.RabbitMqContainerConfiguration?.Password, port);
|
TestContainers.RabbitMqContainerConfiguration?.Password,
|
||||||
|
port
|
||||||
|
);
|
||||||
|
|
||||||
var bd = await managementClient.GetBindingsAsync(cancellationToken);
|
var bd = await managementClient.GetBindingsAsync(cancellationToken);
|
||||||
|
|
||||||
var bindings = bd.Where(
|
var bindings = bd.Where(x => !string.IsNullOrEmpty(x.Source) && !string.IsNullOrEmpty(x.Destination));
|
||||||
x => !string.IsNullOrEmpty(x.Source) && !string.IsNullOrEmpty(x.Destination));
|
|
||||||
|
|
||||||
foreach (var binding in bindings)
|
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()
|
private async Task SeedDataAsync()
|
||||||
{
|
{
|
||||||
@ -712,14 +653,15 @@ where TEntryPoint : class
|
|||||||
}
|
}
|
||||||
|
|
||||||
public abstract class TestReadBase<TEntryPoint, TRContext> : TestFixtureCore<TEntryPoint>
|
public abstract class TestReadBase<TEntryPoint, TRContext> : TestFixtureCore<TEntryPoint>
|
||||||
// ,IClassFixture<IntegrationTestFactory<TEntryPoint, TWContext>>
|
// ,IClassFixture<IntegrationTestFactory<TEntryPoint, TWContext>>
|
||||||
where TEntryPoint : class
|
where TEntryPoint : class
|
||||||
where TRContext : MongoDbContext
|
where TRContext : MongoDbContext
|
||||||
{
|
{
|
||||||
protected TestReadBase(
|
protected TestReadBase(
|
||||||
TestReadFixture<TEntryPoint, TRContext> integrationTestFixture,
|
TestReadFixture<TEntryPoint, TRContext> integrationTestFixture,
|
||||||
ITestOutputHelper outputHelper = null
|
ITestOutputHelper outputHelper = null
|
||||||
) : base(integrationTestFixture, outputHelper)
|
)
|
||||||
|
: base(integrationTestFixture, outputHelper)
|
||||||
{
|
{
|
||||||
Fixture = integrationTestFixture;
|
Fixture = integrationTestFixture;
|
||||||
}
|
}
|
||||||
@ -728,14 +670,15 @@ where TRContext : MongoDbContext
|
|||||||
}
|
}
|
||||||
|
|
||||||
public abstract class TestWriteBase<TEntryPoint, TWContext> : TestFixtureCore<TEntryPoint>
|
public abstract class TestWriteBase<TEntryPoint, TWContext> : TestFixtureCore<TEntryPoint>
|
||||||
//,IClassFixture<IntegrationTestFactory<TEntryPoint, TWContext>>
|
//,IClassFixture<IntegrationTestFactory<TEntryPoint, TWContext>>
|
||||||
where TEntryPoint : class
|
where TEntryPoint : class
|
||||||
where TWContext : DbContext
|
where TWContext : DbContext
|
||||||
{
|
{
|
||||||
protected TestWriteBase(
|
protected TestWriteBase(
|
||||||
TestWriteFixture<TEntryPoint, TWContext> integrationTestFixture,
|
TestWriteFixture<TEntryPoint, TWContext> integrationTestFixture,
|
||||||
ITestOutputHelper outputHelper = null
|
ITestOutputHelper outputHelper = null
|
||||||
) : base(integrationTestFixture, outputHelper)
|
)
|
||||||
|
: base(integrationTestFixture, outputHelper, typeof(TWContext))
|
||||||
{
|
{
|
||||||
Fixture = integrationTestFixture;
|
Fixture = integrationTestFixture;
|
||||||
}
|
}
|
||||||
@ -744,19 +687,19 @@ where TWContext : DbContext
|
|||||||
}
|
}
|
||||||
|
|
||||||
public abstract class TestBase<TEntryPoint, TWContext, TRContext> : TestFixtureCore<TEntryPoint>
|
public abstract class TestBase<TEntryPoint, TWContext, TRContext> : TestFixtureCore<TEntryPoint>
|
||||||
//,IClassFixture<IntegrationTestFactory<TEntryPoint, TWContext, TRContext>>
|
//,IClassFixture<IntegrationTestFactory<TEntryPoint, TWContext, TRContext>>
|
||||||
where TEntryPoint : class
|
where TEntryPoint : class
|
||||||
where TWContext : DbContext
|
where TWContext : DbContext
|
||||||
where TRContext : MongoDbContext
|
where TRContext : MongoDbContext
|
||||||
{
|
{
|
||||||
protected TestBase(
|
protected TestBase(
|
||||||
TestFixture<TEntryPoint, TWContext, TRContext> integrationTestFixture,
|
TestFixture<TEntryPoint, TWContext, TRContext> integrationTestFixture,
|
||||||
ITestOutputHelper outputHelper = null
|
ITestOutputHelper outputHelper = null
|
||||||
) :
|
)
|
||||||
base(integrationTestFixture, outputHelper)
|
: base(integrationTestFixture, outputHelper, typeof(TWContext))
|
||||||
{
|
{
|
||||||
Fixture = integrationTestFixture;
|
Fixture = integrationTestFixture;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TestFixture<TEntryPoint, TWContext, TRContext> Fixture { get; }
|
public TestFixture<TEntryPoint, TWContext, TRContext> Fixture { get; }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
using DotNet.Testcontainers.Builders;
|
||||||
|
|
||||||
namespace BuildingBlocks.TestBase;
|
namespace BuildingBlocks.TestBase;
|
||||||
|
|
||||||
using Testcontainers.EventStoreDb;
|
using Testcontainers.EventStoreDb;
|
||||||
@ -18,15 +20,19 @@ public static class TestContainers
|
|||||||
{
|
{
|
||||||
var configuration = ConfigurationHelper.GetConfiguration();
|
var configuration = ConfigurationHelper.GetConfiguration();
|
||||||
|
|
||||||
RabbitMqContainerConfiguration =
|
RabbitMqContainerConfiguration = configuration.GetOptions<RabbitMqContainerOptions>(
|
||||||
configuration.GetOptions<RabbitMqContainerOptions>(nameof(RabbitMqContainerOptions));
|
nameof(RabbitMqContainerOptions)
|
||||||
PostgresContainerConfiguration =
|
);
|
||||||
configuration.GetOptions<PostgresContainerOptions>(nameof(PostgresContainerOptions));
|
PostgresContainerConfiguration = configuration.GetOptions<PostgresContainerOptions>(
|
||||||
PostgresPersistContainerConfiguration =
|
nameof(PostgresContainerOptions)
|
||||||
configuration.GetOptions<PostgresPersistContainerOptions>(nameof(PostgresPersistContainerOptions));
|
);
|
||||||
|
PostgresPersistContainerConfiguration = configuration.GetOptions<PostgresPersistContainerOptions>(
|
||||||
|
nameof(PostgresPersistContainerOptions)
|
||||||
|
);
|
||||||
MongoContainerConfiguration = configuration.GetOptions<MongoContainerOptions>(nameof(MongoContainerOptions));
|
MongoContainerConfiguration = configuration.GetOptions<MongoContainerOptions>(nameof(MongoContainerOptions));
|
||||||
EventStoreContainerConfiguration =
|
EventStoreContainerConfiguration = configuration.GetOptions<EventStoreContainerOptions>(
|
||||||
configuration.GetOptions<EventStoreContainerOptions>(nameof(EventStoreContainerOptions));
|
nameof(EventStoreContainerOptions)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static PostgreSqlContainer PostgresTestContainer()
|
public static PostgreSqlContainer PostgresTestContainer()
|
||||||
@ -98,8 +104,7 @@ public static class TestContainers
|
|||||||
|
|
||||||
public static EventStoreDbContainer EventStoreTestContainer()
|
public static EventStoreDbContainer EventStoreTestContainer()
|
||||||
{
|
{
|
||||||
var baseBuilder = new EventStoreDbBuilder()
|
var baseBuilder = new EventStoreDbBuilder().WithLabel("Key", "Value");
|
||||||
.WithLabel("Key", "Value");
|
|
||||||
|
|
||||||
var builder = baseBuilder
|
var builder = baseBuilder
|
||||||
.WithImage(EventStoreContainerConfiguration.ImageName)
|
.WithImage(EventStoreContainerConfiguration.ImageName)
|
||||||
|
|||||||
@ -1,47 +1,39 @@
|
|||||||
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS builder
|
# ---------- Build Stage ----------
|
||||||
WORKDIR /
|
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
|
||||||
|
WORKDIR /src
|
||||||
|
|
||||||
COPY ./.editorconfig ./
|
# Copy solution-level files
|
||||||
COPY ./global.json ./
|
COPY .editorconfig .
|
||||||
COPY ./Directory.Build.props ./
|
COPY global.json .
|
||||||
|
COPY Directory.Build.props .
|
||||||
|
|
||||||
# Setup working directory for the project
|
# Copy project files first (for Docker layer caching)
|
||||||
COPY ./src/BuildingBlocks/BuildingBlocks.csproj ./src/BuildingBlocks/
|
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/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/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
|
# Copy the rest of the source
|
||||||
RUN dotnet restore ./src/Services/Booking/src/Booking.Api/Booking.Api.csproj
|
COPY src ./src
|
||||||
|
|
||||||
# Copy project files
|
# Publish (build included)
|
||||||
COPY ./src/BuildingBlocks ./src/BuildingBlocks/
|
RUN dotnet publish src/Services/Booking/src/Booking.Api/Booking.Api.csproj \
|
||||||
COPY ./src/Services/Booking/src/Booking/ ./src/Services/Booking/src/Booking/
|
-c Release \
|
||||||
COPY ./src/Services/Booking/src/Booking.Api/ ./src/Services/Booking/src/Booking.Api/
|
-o /app/publish \
|
||||||
|
--no-restore
|
||||||
|
|
||||||
# Build project with Release configuration
|
# ---------- Runtime Stage ----------
|
||||||
# and no restore, as we did it already
|
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
RUN ls
|
COPY --from=build /app/publish .
|
||||||
RUN dotnet build -c Release --no-restore ./src/Services/Booking/src/Booking.Api/Booking.Api.csproj
|
|
||||||
|
|
||||||
WORKDIR /src/Services/Booking/src/Booking.Api
|
ENV ASPNETCORE_URLS=http://+:80
|
||||||
|
ENV ASPNETCORE_ENVIRONMENT=docker
|
||||||
# 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
|
|
||||||
|
|
||||||
EXPOSE 80
|
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">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Grpc.Tools" Version="2.68.1">
|
<PackageReference Include="Grpc.Tools" Version="2.78.0">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
@ -14,6 +14,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\..\..\Aspire\src\ServiceDefaults\ServiceDefaults.csproj" />
|
||||||
<ProjectReference Include="..\..\..\..\BuildingBlocks\BuildingBlocks.csproj" />
|
<ProjectReference Include="..\..\..\..\BuildingBlocks\BuildingBlocks.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@ -13,9 +13,12 @@ public static class GrpcClientExtensions
|
|||||||
public static IServiceCollection AddGrpcClients(this IServiceCollection services)
|
public static IServiceCollection AddGrpcClients(this IServiceCollection services)
|
||||||
{
|
{
|
||||||
var grpcOptions = services.GetOptions<GrpcOptions>("Grpc");
|
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);
|
o.Address = new Uri(grpcOptions.FlightAddress);
|
||||||
})
|
})
|
||||||
@ -25,54 +28,37 @@ public static class GrpcClientExtensions
|
|||||||
{
|
{
|
||||||
var timeSpan = TimeSpan.FromMinutes(1);
|
var timeSpan = TimeSpan.FromMinutes(1);
|
||||||
|
|
||||||
options.AddRetry(
|
options.AddRetry(new HttpRetryStrategyOptions { MaxRetryAttempts = 3 });
|
||||||
new HttpRetryStrategyOptions
|
|
||||||
{
|
|
||||||
MaxRetryAttempts = 3,
|
|
||||||
});
|
|
||||||
|
|
||||||
options.AddCircuitBreaker(
|
options.AddCircuitBreaker(
|
||||||
new HttpCircuitBreakerStrategyOptions
|
new HttpCircuitBreakerStrategyOptions { SamplingDuration = timeSpan * 2 }
|
||||||
{
|
);
|
||||||
SamplingDuration = timeSpan * 2,
|
|
||||||
});
|
|
||||||
|
|
||||||
options.AddTimeout(
|
options.AddTimeout(new HttpTimeoutStrategyOptions { Timeout = timeSpan * 3 });
|
||||||
new HttpTimeoutStrategyOptions
|
}
|
||||||
{
|
);
|
||||||
Timeout = timeSpan * 3,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
services.AddGrpcClient<PassengerGrpcService.PassengerGrpcServiceClient>(o =>
|
services
|
||||||
{
|
.AddGrpcClient<PassengerGrpcService.PassengerGrpcServiceClient>(o =>
|
||||||
o.Address = new Uri(grpcOptions.PassengerAddress);
|
|
||||||
})
|
|
||||||
.AddResilienceHandler(
|
|
||||||
"grpc-passenger-resilience",
|
|
||||||
options =>
|
|
||||||
{
|
{
|
||||||
var timeSpan = TimeSpan.FromMinutes(1);
|
o.Address = new Uri(grpcOptions.PassengerAddress);
|
||||||
|
})
|
||||||
|
.AddResilienceHandler(
|
||||||
|
"grpc-passenger-resilience",
|
||||||
|
options =>
|
||||||
|
{
|
||||||
|
var timeSpan = TimeSpan.FromMinutes(1);
|
||||||
|
|
||||||
options.AddRetry(
|
options.AddRetry(new HttpRetryStrategyOptions { MaxRetryAttempts = 3 });
|
||||||
new HttpRetryStrategyOptions
|
|
||||||
{
|
|
||||||
MaxRetryAttempts = 3,
|
|
||||||
});
|
|
||||||
|
|
||||||
options.AddCircuitBreaker(
|
options.AddCircuitBreaker(
|
||||||
new HttpCircuitBreakerStrategyOptions
|
new HttpCircuitBreakerStrategyOptions { SamplingDuration = timeSpan * 2 }
|
||||||
{
|
);
|
||||||
SamplingDuration = timeSpan * 2,
|
|
||||||
});
|
|
||||||
|
|
||||||
options.AddTimeout(
|
options.AddTimeout(new HttpTimeoutStrategyOptions { Timeout = timeSpan * 3 });
|
||||||
new HttpTimeoutStrategyOptions
|
}
|
||||||
{
|
);
|
||||||
Timeout = timeSpan * 3,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,12 +12,14 @@ using BuildingBlocks.PersistMessageProcessor;
|
|||||||
using BuildingBlocks.ProblemDetails;
|
using BuildingBlocks.ProblemDetails;
|
||||||
using BuildingBlocks.Web;
|
using BuildingBlocks.Web;
|
||||||
using Figgle;
|
using Figgle;
|
||||||
|
using Figgle.Fonts;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using ServiceDefaults;
|
||||||
|
|
||||||
namespace Booking.Extensions.Infrastructure;
|
namespace Booking.Extensions.Infrastructure;
|
||||||
|
|
||||||
@ -28,25 +30,7 @@ public static class InfrastructureExtensions
|
|||||||
var configuration = builder.Configuration;
|
var configuration = builder.Configuration;
|
||||||
var env = builder.Environment;
|
var env = builder.Environment;
|
||||||
|
|
||||||
builder.Services.AddCustomHealthCheck();
|
builder.AddServiceDefaults();
|
||||||
|
|
||||||
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.Services.AddScoped<ICurrentUserProvider, CurrentUserProvider>();
|
builder.Services.AddScoped<ICurrentUserProvider, CurrentUserProvider>();
|
||||||
builder.Services.AddScoped<IEventMapper, BookingEventMapper>();
|
builder.Services.AddScoped<IEventMapper, BookingEventMapper>();
|
||||||
@ -94,8 +78,7 @@ public static class InfrastructureExtensions
|
|||||||
app.UseAuthentication();
|
app.UseAuthentication();
|
||||||
app.UseAuthorization();
|
app.UseAuthorization();
|
||||||
|
|
||||||
app.UseCustomHealthCheck();
|
app.UseServiceDefaults();
|
||||||
app.UseCustomObservability();
|
|
||||||
|
|
||||||
app.UseCustomProblemDetails();
|
app.UseCustomProblemDetails();
|
||||||
app.UseCorrelationId();
|
app.UseCorrelationId();
|
||||||
|
|||||||
@ -19,10 +19,8 @@ namespace Integration.Test.Booking.Features
|
|||||||
{
|
{
|
||||||
public class CreateBookingTests : BookingIntegrationTestBase
|
public class CreateBookingTests : BookingIntegrationTestBase
|
||||||
{
|
{
|
||||||
public CreateBookingTests(TestReadFixture<Program, BookingReadDbContext> integrationTestFixture) : base(
|
public CreateBookingTests(TestReadFixture<Program, BookingReadDbContext> integrationTestFixture)
|
||||||
integrationTestFixture)
|
: base(integrationTestFixture) { }
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void RegisterTestsServices(IServiceCollection services)
|
protected override void RegisterTestsServices(IServiceCollection services)
|
||||||
{
|
{
|
||||||
@ -40,46 +38,81 @@ namespace Integration.Test.Booking.Features
|
|||||||
var response = await Fixture.SendAsync(command);
|
var response = await Fixture.SendAsync(command);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
response?.Id.Should().BeGreaterOrEqualTo(0);
|
response?.Id.Should().BeGreaterThanOrEqualTo(0);
|
||||||
|
|
||||||
(await Fixture.WaitForPublishing<BookingCreated>()).Should().Be(true);
|
(await Fixture.WaitForPublishing<BookingCreated>()).Should().Be(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void MockPassengerGrpcServices(IServiceCollection services)
|
private void MockPassengerGrpcServices(IServiceCollection services)
|
||||||
{
|
{
|
||||||
services.Replace(ServiceDescriptor.Singleton(x =>
|
services.Replace(
|
||||||
{
|
ServiceDescriptor.Singleton(x =>
|
||||||
var mockPassenger = Substitute.For<PassengerGrpcService.PassengerGrpcServiceClient>();
|
{
|
||||||
|
var mockPassenger = Substitute.For<PassengerGrpcService.PassengerGrpcServiceClient>();
|
||||||
|
|
||||||
mockPassenger.GetByIdAsync(Arg.Any<Passenger.GetByIdRequest>())
|
mockPassenger
|
||||||
.Returns(TestCalls.AsyncUnaryCall(Task.FromResult(FakePassengerResponse.Generate()),
|
.GetByIdAsync(Arg.Any<Passenger.GetByIdRequest>())
|
||||||
Task.FromResult(new Metadata()), () => Status.DefaultSuccess, () => new Metadata(), () => { }));
|
.Returns(
|
||||||
|
TestCalls.AsyncUnaryCall(
|
||||||
|
Task.FromResult(FakePassengerResponse.Generate()),
|
||||||
|
Task.FromResult(new Metadata()),
|
||||||
|
() => Status.DefaultSuccess,
|
||||||
|
() => new Metadata(),
|
||||||
|
() => { }
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
return mockPassenger;
|
return mockPassenger;
|
||||||
}));
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void MockFlightGrpcServices(IServiceCollection services)
|
private void MockFlightGrpcServices(IServiceCollection services)
|
||||||
{
|
{
|
||||||
services.Replace(ServiceDescriptor.Singleton(x =>
|
services.Replace(
|
||||||
{
|
ServiceDescriptor.Singleton(x =>
|
||||||
var mockFlight = Substitute.For<FlightGrpcService.FlightGrpcServiceClient>();
|
{
|
||||||
|
var mockFlight = Substitute.For<FlightGrpcService.FlightGrpcServiceClient>();
|
||||||
|
|
||||||
mockFlight.GetByIdAsync(Arg.Any<GetByIdRequest>())
|
mockFlight
|
||||||
.Returns(TestCalls.AsyncUnaryCall(Task.FromResult(FakeFlightResponse.Generate()),
|
.GetByIdAsync(Arg.Any<GetByIdRequest>())
|
||||||
Task.FromResult(new Metadata()), () => Status.DefaultSuccess, () => new Metadata(), () => { }));
|
.Returns(
|
||||||
|
TestCalls.AsyncUnaryCall(
|
||||||
|
Task.FromResult(FakeFlightResponse.Generate()),
|
||||||
|
Task.FromResult(new Metadata()),
|
||||||
|
() => Status.DefaultSuccess,
|
||||||
|
() => new Metadata(),
|
||||||
|
() => { }
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
mockFlight.GetAvailableSeatsAsync(Arg.Any<GetAvailableSeatsRequest>())
|
mockFlight
|
||||||
.Returns(TestCalls.AsyncUnaryCall(Task.FromResult(FakeGetAvailableSeatsResponse.Generate()),
|
.GetAvailableSeatsAsync(Arg.Any<GetAvailableSeatsRequest>())
|
||||||
Task.FromResult(new Metadata()), () => Status.DefaultSuccess, () => new Metadata(), () => { }));
|
.Returns(
|
||||||
|
TestCalls.AsyncUnaryCall(
|
||||||
|
Task.FromResult(FakeGetAvailableSeatsResponse.Generate()),
|
||||||
|
Task.FromResult(new Metadata()),
|
||||||
|
() => Status.DefaultSuccess,
|
||||||
|
() => new Metadata(),
|
||||||
|
() => { }
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
mockFlight.ReserveSeatAsync(Arg.Any<ReserveSeatRequest>())
|
mockFlight
|
||||||
.Returns(TestCalls.AsyncUnaryCall(Task.FromResult(FakeReserveSeatResponse.Generate()),
|
.ReserveSeatAsync(Arg.Any<ReserveSeatRequest>())
|
||||||
Task.FromResult(new Metadata()), () => Status.DefaultSuccess, () => new Metadata(), () => { }));
|
.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 Booking.Data;
|
||||||
using BuildingBlocks.TestBase;
|
using BuildingBlocks.TestBase;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
@ -8,13 +7,12 @@ namespace Integration.Test;
|
|||||||
[Collection(IntegrationTestCollection.Name)]
|
[Collection(IntegrationTestCollection.Name)]
|
||||||
public class BookingIntegrationTestBase : TestReadBase<Program, BookingReadDbContext>
|
public class BookingIntegrationTestBase : TestReadBase<Program, BookingReadDbContext>
|
||||||
{
|
{
|
||||||
public BookingIntegrationTestBase(TestReadFixture<Program, BookingReadDbContext> integrationTestFixture) : base(integrationTestFixture)
|
public BookingIntegrationTestBase(TestReadFixture<Program, BookingReadDbContext> integrationTestFixture)
|
||||||
{
|
: base(integrationTestFixture) { }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[CollectionDefinition(Name)]
|
[CollectionDefinition(Name)]
|
||||||
public class IntegrationTestCollection : ICollectionFixture<TestReadFixture<Program, BookingReadDbContext>>
|
public class IntegrationTestCollection : ICollectionFixture<TestReadFixture<Program, BookingReadDbContext>>
|
||||||
{
|
{
|
||||||
public const string Name = "Booking Integration Test";
|
public const string Name = "Booking Integration Test";
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
namespace Integration.Test.Fakes;
|
namespace Integration.Test.Fakes;
|
||||||
|
|
||||||
using Flight;
|
using Flight;
|
||||||
using MassTransit;
|
using MassTransit;
|
||||||
|
|
||||||
|
|||||||
@ -7,9 +7,9 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
|
||||||
<PackageReference Include="xunit" Version="2.9.2" />
|
<PackageReference Include="xunit" Version="2.9.3" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|||||||
@ -1,48 +1,39 @@
|
|||||||
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS builder
|
# ---------- Build Stage ----------
|
||||||
WORKDIR /
|
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
|
||||||
|
WORKDIR /src
|
||||||
|
|
||||||
COPY ./.editorconfig ./
|
# Copy solution-level files
|
||||||
COPY ./global.json ./
|
COPY .editorconfig .
|
||||||
COPY ./Directory.Build.props ./
|
COPY global.json .
|
||||||
|
COPY Directory.Build.props .
|
||||||
|
|
||||||
# Setup working directory for the project
|
# Copy project files first (better layer caching)
|
||||||
COPY ./src/BuildingBlocks/BuildingBlocks.csproj ./src/BuildingBlocks/
|
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/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/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
|
# Copy remaining source code
|
||||||
RUN dotnet restore ./src/Services/Flight/src/Flight.Api/Flight.Api.csproj
|
COPY src ./src
|
||||||
|
|
||||||
# Copy project files
|
# Publish (build included)
|
||||||
COPY ./src/BuildingBlocks ./src/BuildingBlocks/
|
RUN dotnet publish src/Services/Flight/src/Flight.Api/Flight.Api.csproj \
|
||||||
COPY ./src/Services/Flight/src/Flight/ ./src/Services/Flight/src/Flight/
|
-c Release \
|
||||||
COPY ./src/Services/Flight/src/Flight.Api/ ./src/Services/Flight/src/Flight.Api/
|
-o /app/publish \
|
||||||
|
--no-restore
|
||||||
|
|
||||||
# Build project with Release configuration
|
# ---------- Runtime Stage ----------
|
||||||
# and no restore, as we did it already
|
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
RUN ls
|
COPY --from=build /app/publish .
|
||||||
RUN dotnet build -c Release --no-restore ./src/Services/Flight/src/Flight.Api/Flight.Api.csproj
|
|
||||||
|
|
||||||
WORKDIR /src/Services/Flight/src/Flight.Api
|
ENV ASPNETCORE_URLS=http://+:80
|
||||||
|
ENV ASPNETCORE_ENVIRONMENT=docker
|
||||||
# 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
|
|
||||||
|
|
||||||
EXPOSE 80
|
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.ProblemDetails;
|
||||||
using BuildingBlocks.Web;
|
using BuildingBlocks.Web;
|
||||||
using Figgle;
|
using Figgle;
|
||||||
|
using Figgle.Fonts;
|
||||||
using Flight.Data;
|
using Flight.Data;
|
||||||
using Flight.Data.Seed;
|
using Flight.Data.Seed;
|
||||||
using Flight.GrpcServer.Services;
|
using Flight.GrpcServer.Services;
|
||||||
@ -21,6 +22,7 @@ using Microsoft.AspNetCore.Http;
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using ServiceDefaults;
|
||||||
|
|
||||||
namespace Flight.Extensions.Infrastructure;
|
namespace Flight.Extensions.Infrastructure;
|
||||||
|
|
||||||
@ -32,25 +34,7 @@ public static class InfrastructureExtensions
|
|||||||
var configuration = builder.Configuration;
|
var configuration = builder.Configuration;
|
||||||
var env = builder.Environment;
|
var env = builder.Environment;
|
||||||
|
|
||||||
builder.Services.AddCustomHealthCheck();
|
builder.AddServiceDefaults();
|
||||||
|
|
||||||
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.Services.AddScoped<ICurrentUserProvider, CurrentUserProvider>();
|
builder.Services.AddScoped<ICurrentUserProvider, CurrentUserProvider>();
|
||||||
builder.Services.AddScoped<IEventMapper, FlightEventMapper>();
|
builder.Services.AddScoped<IEventMapper, FlightEventMapper>();
|
||||||
@ -100,8 +84,7 @@ public static class InfrastructureExtensions
|
|||||||
app.UseAuthentication();
|
app.UseAuthentication();
|
||||||
app.UseAuthorization();
|
app.UseAuthorization();
|
||||||
|
|
||||||
app.UseCustomHealthCheck();
|
app.UseServiceDefaults();
|
||||||
app.UseCustomObservability();
|
|
||||||
|
|
||||||
app.UseCustomProblemDetails();
|
app.UseCustomProblemDetails();
|
||||||
app.UseCorrelationId();
|
app.UseCorrelationId();
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Grpc.AspNetCore" Version="2.67.0" />
|
<PackageReference Include="Grpc.AspNetCore" Version="2.76.0" />
|
||||||
<PackageReference Include="Grpc.Net.Client" Version="2.67.0" />
|
<PackageReference Include="Grpc.Net.Client" Version="2.76.0" />
|
||||||
<PackageReference Include="Grpc.Tools" Version="2.68.1">
|
<PackageReference Include="Grpc.Tools" Version="2.78.0">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.0">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.3">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
@ -22,6 +22,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\..\..\Aspire\src\ServiceDefaults\ServiceDefaults.csproj" />
|
||||||
<ProjectReference Include="..\..\..\..\BuildingBlocks\BuildingBlocks.csproj" />
|
<ProjectReference Include="..\..\..\..\BuildingBlocks\BuildingBlocks.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Flight.Flights.Dtos;
|
namespace Flight.Flights.Dtos;
|
||||||
|
|
||||||
public record FlightDto(Guid Id, string FlightNumber, Guid AircraftId, Guid DepartureAirportId,
|
public record FlightDto(Guid Id, string FlightNumber, Guid AircraftId, Guid DepartureAirportId,
|
||||||
DateTime DepartureDate, DateTime ArriveDate, Guid ArriveAirportId, decimal DurationMinutes, DateTime FlightDate,
|
DateTime DepartureDate, DateTime ArriveDate, Guid ArriveAirportId, decimal DurationMinutes, DateTime FlightDate,
|
||||||
Enums.FlightStatus Status, decimal Price);
|
Enums.FlightStatus Status, decimal Price);
|
||||||
@ -1,4 +1,5 @@
|
|||||||
namespace Flight.Flights.ValueObjects;
|
namespace Flight.Flights.ValueObjects;
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using Flight.Flights.Exceptions;
|
using Flight.Flights.Exceptions;
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
namespace Flight.Flights.ValueObjects;
|
namespace Flight.Flights.ValueObjects;
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using Flight.Flights.Exceptions;
|
using Flight.Flights.Exceptions;
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
namespace Flight.Flights.ValueObjects;
|
namespace Flight.Flights.ValueObjects;
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using Flight.Flights.Exceptions;
|
using Flight.Flights.Exceptions;
|
||||||
|
|
||||||
|
|||||||
@ -7,9 +7,9 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
|
||||||
<PackageReference Include="xunit" Version="2.9.2" />
|
<PackageReference Include="xunit" Version="2.9.3" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|||||||
@ -34,6 +34,6 @@ public class GetAvailableFlightsTests : FlightIntegrationTestBase
|
|||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
response?.Should().NotBeNull();
|
response?.Should().NotBeNull();
|
||||||
response?.Count.Should().BeGreaterOrEqualTo(2);
|
response?.Count.Should().BeGreaterThanOrEqualTo(2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -7,9 +7,9 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
|
||||||
<PackageReference Include="xunit" Version="2.9.2" />
|
<PackageReference Include="xunit" Version="2.9.3" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|||||||
@ -38,6 +38,6 @@ public class GetAvailableSeatsTests : FlightIntegrationTestBase
|
|||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
response?.Should().NotBeNull();
|
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;
|
using Xunit;
|
||||||
|
|
||||||
namespace Integration.Test.Seat.Features;
|
namespace Integration.Test.Seat.Features;
|
||||||
|
|
||||||
public class ReserveSeatTests : FlightIntegrationTestBase
|
public class ReserveSeatTests : FlightIntegrationTestBase
|
||||||
{
|
{
|
||||||
public ReserveSeatTests(
|
public ReserveSeatTests(
|
||||||
|
|||||||
@ -7,9 +7,9 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
|
||||||
<PackageReference Include="xunit" Version="2.9.2" />
|
<PackageReference Include="xunit" Version="2.9.3" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</PackageReference>
|
</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
|
# Copy solution-level files
|
||||||
WORKDIR /
|
COPY .editorconfig .
|
||||||
|
COPY global.json .
|
||||||
|
COPY Directory.Build.props .
|
||||||
|
|
||||||
COPY ./.editorconfig ./
|
# Copy project files first (for better Docker layer caching)
|
||||||
COPY ./global.json ./
|
COPY src/BuildingBlocks/BuildingBlocks.csproj src/BuildingBlocks/
|
||||||
COPY ./Directory.Build.props ./
|
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/
|
# Restore dependencies
|
||||||
COPY ./src/Services/Identity/src/Identity/Identity.csproj ./src/Services/Identity/src/Identity/
|
RUN dotnet restore src/Services/Identity/src/Identity.Api/Identity.Api.csproj
|
||||||
COPY ./src/Services/Identity/src/Identity.Api/Identity.Api.csproj ./src/Services/Identity/src/Identity.Api/
|
|
||||||
|
|
||||||
# Restore nuget packages
|
# Copy remaining source code
|
||||||
RUN dotnet restore ./src/Services/Identity/src/Identity.Api/Identity.Api.csproj
|
COPY src ./src
|
||||||
|
|
||||||
# Copy project files
|
# Publish (build included, no separate build step needed)
|
||||||
COPY ./src/BuildingBlocks ./src/BuildingBlocks/
|
RUN dotnet publish src/Services/Identity/src/Identity.Api/Identity.Api.csproj \
|
||||||
COPY ./src/Services/Identity/src/Identity/ ./src/Services/Identity/src/Identity/
|
-c Release \
|
||||||
COPY ./src/Services/Identity/src/Identity.Api/ ./src/Services/Identity/src/Identity.Api/
|
-o /app/publish \
|
||||||
|
--no-restore
|
||||||
|
|
||||||
# Build project with Release configuration
|
# ---------- Runtime Stage ----------
|
||||||
# and no restore, as we did it already
|
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
RUN ls
|
COPY --from=build /app/publish .
|
||||||
RUN dotnet build -c Release --no-restore ./src/Services/Identity/src/Identity.Api/Identity.Api.csproj
|
|
||||||
|
|
||||||
WORKDIR /src/Services/Identity/src/Identity.Api
|
ENV ASPNETCORE_URLS=http://+:80
|
||||||
|
ENV ASPNETCORE_ENVIRONMENT=docker
|
||||||
# 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
|
|
||||||
|
|
||||||
EXPOSE 80
|
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.Core;
|
||||||
using BuildingBlocks.EFCore;
|
using BuildingBlocks.EFCore;
|
||||||
using BuildingBlocks.HealthCheck;
|
|
||||||
using BuildingBlocks.Mapster;
|
using BuildingBlocks.Mapster;
|
||||||
using BuildingBlocks.MassTransit;
|
using BuildingBlocks.MassTransit;
|
||||||
using BuildingBlocks.OpenApi;
|
using BuildingBlocks.OpenApi;
|
||||||
using BuildingBlocks.OpenTelemetryCollector;
|
|
||||||
using BuildingBlocks.PersistMessageProcessor;
|
using BuildingBlocks.PersistMessageProcessor;
|
||||||
using BuildingBlocks.ProblemDetails;
|
using BuildingBlocks.ProblemDetails;
|
||||||
using BuildingBlocks.Web;
|
using BuildingBlocks.Web;
|
||||||
using Figgle;
|
using Figgle;
|
||||||
|
using Figgle.Fonts;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
using Identity.Data;
|
using Identity.Data;
|
||||||
using Identity.Data.Seed;
|
using Identity.Data.Seed;
|
||||||
@ -18,6 +17,7 @@ using Microsoft.AspNetCore.HttpOverrides;
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using ServiceDefaults;
|
||||||
|
|
||||||
namespace Identity.Extensions.Infrastructure;
|
namespace Identity.Extensions.Infrastructure;
|
||||||
|
|
||||||
@ -28,25 +28,7 @@ public static class InfrastructureExtensions
|
|||||||
var configuration = builder.Configuration;
|
var configuration = builder.Configuration;
|
||||||
var env = builder.Environment;
|
var env = builder.Environment;
|
||||||
|
|
||||||
builder.Services.AddCustomHealthCheck();
|
builder.AddServiceDefaults();
|
||||||
|
|
||||||
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.Services.AddScoped<ICurrentUserProvider, CurrentUserProvider>();
|
builder.Services.AddScoped<ICurrentUserProvider, CurrentUserProvider>();
|
||||||
builder.Services.AddScoped<IEventMapper, IdentityEventMapper>();
|
builder.Services.AddScoped<IEventMapper, IdentityEventMapper>();
|
||||||
@ -94,8 +76,7 @@ public static class InfrastructureExtensions
|
|||||||
app.UseAuthentication();
|
app.UseAuthentication();
|
||||||
app.UseAuthorization();
|
app.UseAuthorization();
|
||||||
|
|
||||||
app.UseCustomHealthCheck();
|
app.UseServiceDefaults();
|
||||||
app.UseCustomObservability();
|
|
||||||
|
|
||||||
app.UseForwardedHeaders();
|
app.UseForwardedHeaders();
|
||||||
|
|
||||||
|
|||||||
@ -1,17 +1,14 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.0">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.3">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Data\Migrations" />
|
<ProjectReference Include="..\..\..\..\Aspire\src\ServiceDefaults\ServiceDefaults.csproj" />
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\..\..\..\BuildingBlocks\BuildingBlocks.csproj" />
|
<ProjectReference Include="..\..\..\..\BuildingBlocks\BuildingBlocks.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@ -10,10 +10,8 @@ namespace Integration.Test.Identity.Features;
|
|||||||
|
|
||||||
public class RegisterNewUserTests : IdentityIntegrationTestBase
|
public class RegisterNewUserTests : IdentityIntegrationTestBase
|
||||||
{
|
{
|
||||||
public RegisterNewUserTests(
|
public RegisterNewUserTests(TestWriteFixture<Program, IdentityContext> integrationTestFactory)
|
||||||
TestWriteFixture<Program, IdentityContext> integrationTestFactory) : base(integrationTestFactory)
|
: base(integrationTestFactory) { }
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task should_create_new_user_to_db_and_publish_message_to_broker()
|
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);
|
(await Fixture.WaitForPublishing<UserCreated>()).Should().Be(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,9 +7,9 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
|
||||||
<PackageReference Include="xunit" Version="2.9.2" />
|
<PackageReference Include="xunit" Version="2.9.3" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|||||||
@ -1,47 +1,39 @@
|
|||||||
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS builder
|
# ---------- Build Stage ----------
|
||||||
WORKDIR /
|
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
|
||||||
|
WORKDIR /src
|
||||||
|
|
||||||
COPY ./.editorconfig ./
|
# Copy solution-level files
|
||||||
COPY ./global.json ./
|
COPY .editorconfig .
|
||||||
COPY ./Directory.Build.props ./
|
COPY global.json .
|
||||||
|
COPY Directory.Build.props .
|
||||||
|
|
||||||
# Setup working directory for the project
|
# Copy project files first (better Docker layer caching)
|
||||||
COPY ./src/BuildingBlocks/BuildingBlocks.csproj ./src/BuildingBlocks/
|
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/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/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
|
# Copy remaining source code
|
||||||
RUN dotnet restore ./src/Services/Passenger/src/Passenger.Api/Passenger.Api.csproj
|
COPY src ./src
|
||||||
|
|
||||||
# Copy project files
|
# Publish (build included)
|
||||||
COPY ./src/BuildingBlocks ./src/BuildingBlocks/
|
RUN dotnet publish src/Services/Passenger/src/Passenger.Api/Passenger.Api.csproj \
|
||||||
COPY ./src/Services/Passenger/src/Passenger/ ./src/Services/Passenger/src/Passenger/
|
-c Release \
|
||||||
COPY ./src/Services/Passenger/src/Passenger.Api/ ./src/Services/Passenger/src/Passenger.Api/
|
-o /app/publish \
|
||||||
|
--no-restore
|
||||||
|
|
||||||
# Build project with Release configuration
|
# ---------- Runtime Stage ----------
|
||||||
# and no restore, as we did it already
|
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
RUN ls
|
COPY --from=build /app/publish .
|
||||||
RUN dotnet build -c Release --no-restore ./src/Services/Passenger/src/Passenger.Api/Passenger.Api.csproj
|
|
||||||
|
|
||||||
WORKDIR /src/Services/Passenger/src/Passenger.Api
|
ENV ASPNETCORE_URLS=http://+:80
|
||||||
|
ENV ASPNETCORE_ENVIRONMENT=docker
|
||||||
# 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
|
|
||||||
|
|
||||||
EXPOSE 80
|
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;
|
using SmartCharging.Infrastructure.Exceptions;
|
||||||
|
|
||||||
namespace Passenger.Exceptions;
|
namespace Passenger.Exceptions;
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
public class InvalidPassengerIdException : DomainException
|
public class InvalidPassengerIdException : DomainException
|
||||||
|
|||||||
@ -12,6 +12,7 @@ using BuildingBlocks.PersistMessageProcessor;
|
|||||||
using BuildingBlocks.ProblemDetails;
|
using BuildingBlocks.ProblemDetails;
|
||||||
using BuildingBlocks.Web;
|
using BuildingBlocks.Web;
|
||||||
using Figgle;
|
using Figgle;
|
||||||
|
using Figgle.Fonts;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
@ -20,6 +21,7 @@ using Microsoft.Extensions.DependencyInjection;
|
|||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Passenger.Data;
|
using Passenger.Data;
|
||||||
using Passenger.GrpcServer.Services;
|
using Passenger.GrpcServer.Services;
|
||||||
|
using ServiceDefaults;
|
||||||
|
|
||||||
namespace Passenger.Extensions.Infrastructure;
|
namespace Passenger.Extensions.Infrastructure;
|
||||||
|
|
||||||
@ -30,26 +32,7 @@ public static class InfrastructureExtensions
|
|||||||
var configuration = builder.Configuration;
|
var configuration = builder.Configuration;
|
||||||
var env = builder.Environment;
|
var env = builder.Environment;
|
||||||
|
|
||||||
builder.Services.AddCustomHealthCheck();
|
builder.AddServiceDefaults();
|
||||||
|
|
||||||
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.Services.AddScoped<ICurrentUserProvider, CurrentUserProvider>();
|
builder.Services.AddScoped<ICurrentUserProvider, CurrentUserProvider>();
|
||||||
builder.Services.AddScoped<IEventMapper, PassengerEventMapper>();
|
builder.Services.AddScoped<IEventMapper, PassengerEventMapper>();
|
||||||
@ -97,8 +80,7 @@ public static class InfrastructureExtensions
|
|||||||
app.UseAuthentication();
|
app.UseAuthentication();
|
||||||
app.UseAuthorization();
|
app.UseAuthorization();
|
||||||
|
|
||||||
app.UseCustomHealthCheck();
|
app.UseServiceDefaults();
|
||||||
app.UseCustomObservability();
|
|
||||||
|
|
||||||
app.UseCustomProblemDetails();
|
app.UseCustomProblemDetails();
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Grpc.AspNetCore" Version="2.67.0" />
|
<PackageReference Include="Grpc.AspNetCore" 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>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
@ -17,6 +17,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\..\..\Aspire\src\ServiceDefaults\ServiceDefaults.csproj" />
|
||||||
<ProjectReference Include="..\..\..\..\BuildingBlocks\BuildingBlocks.csproj" />
|
<ProjectReference Include="..\..\..\..\BuildingBlocks\BuildingBlocks.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@ -1,2 +1,3 @@
|
|||||||
namespace Passenger.Passengers.Dtos;
|
namespace Passenger.Passengers.Dtos;
|
||||||
|
|
||||||
public record PassengerDto(Guid Id, string Name, string PassportNumber, Enums.PassengerType PassengerType, int Age);
|
public record PassengerDto(Guid Id, string Name, string PassportNumber, Enums.PassengerType PassengerType, int Age);
|
||||||
@ -1,4 +1,5 @@
|
|||||||
namespace Passenger.Passengers.Exceptions;
|
namespace Passenger.Passengers.Exceptions;
|
||||||
|
|
||||||
using BuildingBlocks.Exception;
|
using BuildingBlocks.Exception;
|
||||||
|
|
||||||
public class InvalidAgeException : BadRequestException
|
public class InvalidAgeException : BadRequestException
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
namespace Passenger.Passengers.Exceptions;
|
namespace Passenger.Passengers.Exceptions;
|
||||||
|
|
||||||
using BuildingBlocks.Exception;
|
using BuildingBlocks.Exception;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
namespace Passenger.Passengers.Exceptions;
|
namespace Passenger.Passengers.Exceptions;
|
||||||
|
|
||||||
using BuildingBlocks.Exception;
|
using BuildingBlocks.Exception;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -17,17 +17,23 @@ using Microsoft.AspNetCore.Builder;
|
|||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Passenger.Passengers.ValueObjects;
|
using ValueObjects;
|
||||||
|
|
||||||
public record CompleteRegisterPassenger
|
public record CompleteRegisterPassenger(string PassportNumber, Enums.PassengerType PassengerType, int Age)
|
||||||
(string PassportNumber, Enums.PassengerType PassengerType, int Age) : ICommand<CompleteRegisterPassengerResult>,
|
: ICommand<CompleteRegisterPassengerResult>,
|
||||||
IInternalCommand
|
IInternalCommand
|
||||||
{
|
{
|
||||||
public Guid Id { get; init; } = NewId.NextGuid();
|
public Guid Id { get; init; } = NewId.NextGuid();
|
||||||
}
|
}
|
||||||
|
|
||||||
public record PassengerRegistrationCompletedDomainEvent(Guid Id, string Name, string PassportNumber,
|
public record PassengerRegistrationCompletedDomainEvent(
|
||||||
Enums.PassengerType PassengerType, int Age, bool IsDeleted = false) : IDomainEvent;
|
Guid Id,
|
||||||
|
string Name,
|
||||||
|
string PassportNumber,
|
||||||
|
Enums.PassengerType PassengerType,
|
||||||
|
int Age,
|
||||||
|
bool IsDeleted = false
|
||||||
|
) : IDomainEvent;
|
||||||
|
|
||||||
public record CompleteRegisterPassengerResult(PassengerDto PassengerDto);
|
public record CompleteRegisterPassengerResult(PassengerDto PassengerDto);
|
||||||
|
|
||||||
@ -39,18 +45,25 @@ public class CompleteRegisterPassengerEndpoint : IMinimalEndpoint
|
|||||||
{
|
{
|
||||||
public IEndpointRouteBuilder MapEndpoint(IEndpointRouteBuilder builder)
|
public IEndpointRouteBuilder MapEndpoint(IEndpointRouteBuilder builder)
|
||||||
{
|
{
|
||||||
builder.MapPost($"{EndpointConfig.BaseApiPath}/passenger/complete-registration", async (
|
builder
|
||||||
CompleteRegisterPassengerRequestDto request, IMapper mapper,
|
.MapPost(
|
||||||
IMediator mediator, CancellationToken cancellationToken) =>
|
$"{EndpointConfig.BaseApiPath}/passenger/complete-registration",
|
||||||
{
|
async (
|
||||||
var command = mapper.Map<CompleteRegisterPassenger>(request);
|
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))
|
.RequireAuthorization(nameof(ApiScope))
|
||||||
.WithName("CompleteRegisterPassenger")
|
.WithName("CompleteRegisterPassenger")
|
||||||
.WithApiVersionSet(builder.NewApiVersionSet("Passenger").Build())
|
.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.PassportNumber).NotNull().WithMessage("The PassportNumber is required!");
|
||||||
RuleFor(x => x.Age).GreaterThan(0).WithMessage("The Age must be greater than 0!");
|
RuleFor(x => x.Age).GreaterThan(0).WithMessage("The Age must be greater than 0!");
|
||||||
RuleFor(x => x.PassengerType).Must(p => p.GetType().IsEnum &&
|
RuleFor(x => x.PassengerType)
|
||||||
p == Enums.PassengerType.Baby ||
|
.Must(p =>
|
||||||
p == Enums.PassengerType.Female ||
|
p.GetType().IsEnum && p == Enums.PassengerType.Baby
|
||||||
p == Enums.PassengerType.Male ||
|
|| p == Enums.PassengerType.Female
|
||||||
p == Enums.PassengerType.Unknown)
|
|| p == Enums.PassengerType.Male
|
||||||
|
|| p == Enums.PassengerType.Unknown
|
||||||
|
)
|
||||||
.WithMessage("PassengerType must be Male, Female, Baby or Unknown");
|
.WithMessage("PassengerType must be Male, Female, Baby or Unknown");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class CompleteRegisterPassengerCommandHandler : ICommandHandler<CompleteRegisterPassenger,
|
internal class CompleteRegisterPassengerCommandHandler
|
||||||
CompleteRegisterPassengerResult>
|
: ICommandHandler<CompleteRegisterPassenger, CompleteRegisterPassengerResult>
|
||||||
{
|
{
|
||||||
private readonly IMapper _mapper;
|
private readonly IMapper _mapper;
|
||||||
private readonly PassengerDbContext _passengerDbContext;
|
private readonly PassengerDbContext _passengerDbContext;
|
||||||
@ -92,21 +107,30 @@ internal class CompleteRegisterPassengerCommandHandler : ICommandHandler<Complet
|
|||||||
_passengerDbContext = passengerDbContext;
|
_passengerDbContext = passengerDbContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<CompleteRegisterPassengerResult> Handle(CompleteRegisterPassenger request,
|
public async Task<CompleteRegisterPassengerResult> Handle(
|
||||||
CancellationToken cancellationToken)
|
CompleteRegisterPassenger request,
|
||||||
|
CancellationToken cancellationToken
|
||||||
|
)
|
||||||
{
|
{
|
||||||
Guard.Against.Null(request, nameof(request));
|
Guard.Against.Null(request, nameof(request));
|
||||||
|
|
||||||
var passenger = await _passengerDbContext.Passengers.SingleOrDefaultAsync(
|
var passenger = await _passengerDbContext.Passengers.SingleOrDefaultAsync(
|
||||||
x => x.PassportNumber.Value == request.PassportNumber, cancellationToken);
|
x => x.PassportNumber.Value == request.PassportNumber,
|
||||||
|
cancellationToken
|
||||||
|
);
|
||||||
|
|
||||||
if (passenger is null)
|
if (passenger is null)
|
||||||
{
|
{
|
||||||
throw new PassengerNotExist();
|
throw new PassengerNotExist();
|
||||||
}
|
}
|
||||||
|
|
||||||
passenger.CompleteRegistrationPassenger(passenger.Id, passenger.Name,
|
passenger.CompleteRegistrationPassenger(
|
||||||
passenger.PassportNumber, request.PassengerType, Age.Of(request.Age));
|
passenger.Id,
|
||||||
|
passenger.Name,
|
||||||
|
passenger.PassportNumber,
|
||||||
|
request.PassengerType,
|
||||||
|
Age.Of(request.Age)
|
||||||
|
);
|
||||||
|
|
||||||
var updatePassenger = _passengerDbContext.Passengers.Update(passenger).Entity;
|
var updatePassenger = _passengerDbContext.Passengers.Update(passenger).Entity;
|
||||||
|
|
||||||
@ -114,4 +138,4 @@ internal class CompleteRegisterPassengerCommandHandler : ICommandHandler<Complet
|
|||||||
|
|
||||||
return new CompleteRegisterPassengerResult(passengerDto);
|
return new CompleteRegisterPassengerResult(passengerDto);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
namespace Passenger.Passengers.Models;
|
namespace Passenger.Passengers.Models;
|
||||||
|
|
||||||
public class PassengerReadModel
|
public class PassengerReadModel
|
||||||
{
|
{
|
||||||
public required Guid Id { get; init; }
|
public required Guid Id { get; init; }
|
||||||
|
|||||||
@ -4,15 +4,14 @@ using Passenger.Passengers.Enums;
|
|||||||
namespace Integration.Test.Fakes;
|
namespace Integration.Test.Fakes;
|
||||||
|
|
||||||
using global::Passenger.Passengers.Features.CompletingRegisterPassenger.V1;
|
using global::Passenger.Passengers.Features.CompletingRegisterPassenger.V1;
|
||||||
using MassTransit;
|
|
||||||
|
|
||||||
public sealed class FakeCompleteRegisterPassengerCommand : AutoFaker<CompleteRegisterPassenger>
|
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.PassportNumber, _ => passportNumber);
|
||||||
RuleFor(r => r.PassengerType, _ => PassengerType.Male);
|
RuleFor(r => r.PassengerType, _ => PassengerType.Male);
|
||||||
RuleFor(r => r.Age, _ => 30);
|
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>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
|
||||||
<PackageReference Include="xunit" Version="2.9.2" />
|
<PackageReference Include="xunit" Version="2.9.3" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|||||||
@ -1,41 +1,41 @@
|
|||||||
using System.Threading.Tasks;
|
|
||||||
using BuildingBlocks.Contracts.EventBus.Messages;
|
|
||||||
using BuildingBlocks.TestBase;
|
using BuildingBlocks.TestBase;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Integration.Test.Fakes;
|
using Integration.Test.Fakes;
|
||||||
using Passenger.Api;
|
|
||||||
using Passenger.Data;
|
using Passenger.Data;
|
||||||
|
using Passenger.Passengers.ValueObjects;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Integration.Test.Passenger.Features;
|
namespace Integration.Test.Passenger.Features;
|
||||||
|
|
||||||
public class CompleteRegisterPassengerTests : PassengerIntegrationTestBase
|
public class CompleteRegisterPassengerTests : PassengerIntegrationTestBase
|
||||||
{
|
{
|
||||||
|
|
||||||
public CompleteRegisterPassengerTests(
|
public CompleteRegisterPassengerTests(
|
||||||
TestFixture<Program, PassengerDbContext, PassengerReadDbContext> integrationTestFactory) : base(integrationTestFactory)
|
TestFixture<Program, PassengerDbContext, PassengerReadDbContext> integrationTestFactory
|
||||||
{
|
)
|
||||||
}
|
: base(integrationTestFactory) { }
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task should_complete_register_passenger_and_update_to_db()
|
public async Task should_complete_register_passenger_and_update_to_db()
|
||||||
{
|
{
|
||||||
// Arrange
|
// 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.InsertAsync(passenger);
|
||||||
(await Fixture.WaitForPublishing<UserCreated>()).Should().Be(true);
|
|
||||||
(await Fixture.WaitForConsuming<UserCreated>()).Should().Be(true);
|
|
||||||
|
|
||||||
var command = new FakeCompleteRegisterPassengerCommand(userCreated.PassportNumber).Generate();
|
var command = new FakeCompleteRegisterPassengerCommand(passenger.PassportNumber, passenger.Id).Generate();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var response = await Fixture.SendAsync(command);
|
var response = await Fixture.SendAsync(command);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
response.Should().NotBeNull();
|
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?.PassportNumber.Should().Be(command.PassportNumber);
|
||||||
response?.PassengerDto?.PassengerType.ToString().Should().Be(command.PassengerType.ToString());
|
response?.PassengerDto?.PassengerType.ToString().Should().Be(command.PassengerType.ToString());
|
||||||
response?.PassengerDto?.Age.Should().Be(command.Age);
|
response?.PassengerDto?.Age.Should().Be(command.Age);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user