diff --git a/.gitignore b/.gitignore
index ec39d478..d7fc5cb8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -30,4 +30,7 @@ _ReSharper*/
# JetBrains Rider
.idea/
-*.sln.iml
\ No newline at end of file
+*.sln.iml
+
+#nuke.build
+.nuke/temp
diff --git a/.nuke b/.nuke
deleted file mode 100644
index 3287f53f..00000000
--- a/.nuke
+++ /dev/null
@@ -1 +0,0 @@
-Backend.Fx.sln
\ No newline at end of file
diff --git a/.nuke/build.schema.json b/.nuke/build.schema.json
new file mode 100644
index 00000000..c172200f
--- /dev/null
+++ b/.nuke/build.schema.json
@@ -0,0 +1,120 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "title": "Build Schema",
+ "$ref": "#/definitions/build",
+ "definitions": {
+ "build": {
+ "type": "object",
+ "properties": {
+ "Configuration": {
+ "type": "string",
+ "description": "Configuration to build - Default is 'Debug' (local) or 'Release' (server)",
+ "enum": [
+ "Debug",
+ "Release"
+ ]
+ },
+ "Continue": {
+ "type": "boolean",
+ "description": "Indicates to continue a previously failed build attempt"
+ },
+ "Help": {
+ "type": "boolean",
+ "description": "Shows the help text for this build assembly"
+ },
+ "Host": {
+ "type": "string",
+ "description": "Host for execution. Default is 'automatic'",
+ "enum": [
+ "AppVeyor",
+ "AzurePipelines",
+ "Bamboo",
+ "Bitbucket",
+ "Bitrise",
+ "GitHubActions",
+ "GitLab",
+ "Jenkins",
+ "Rider",
+ "SpaceAutomation",
+ "TeamCity",
+ "Terminal",
+ "TravisCI",
+ "VisualStudio",
+ "VSCode"
+ ]
+ },
+ "NoLogo": {
+ "type": "boolean",
+ "description": "Disables displaying the NUKE logo"
+ },
+ "Partition": {
+ "type": "string",
+ "description": "Partition to use on CI"
+ },
+ "Plan": {
+ "type": "boolean",
+ "description": "Shows the execution plan (HTML)"
+ },
+ "Profile": {
+ "type": "array",
+ "description": "Defines the profiles to load",
+ "items": {
+ "type": "string"
+ }
+ },
+ "Root": {
+ "type": "string",
+ "description": "Root directory during build execution"
+ },
+ "Skip": {
+ "type": "array",
+ "description": "List of targets to be skipped. Empty list skips all dependencies",
+ "items": {
+ "type": "string",
+ "enum": [
+ "Clean",
+ "Compile",
+ "Pack",
+ "Publish",
+ "Restore",
+ "StartDependencies",
+ "StopDependencies",
+ "Test"
+ ]
+ }
+ },
+ "Solution": {
+ "type": "string",
+ "description": "Path to a solution file that is automatically loaded"
+ },
+ "Target": {
+ "type": "array",
+ "description": "List of targets to be invoked. Default is '{default_target}'",
+ "items": {
+ "type": "string",
+ "enum": [
+ "Clean",
+ "Compile",
+ "Pack",
+ "Publish",
+ "Restore",
+ "StartDependencies",
+ "StopDependencies",
+ "Test"
+ ]
+ }
+ },
+ "Verbosity": {
+ "type": "string",
+ "description": "Logging verbosity during build execution. Default is 'Normal'",
+ "enum": [
+ "Minimal",
+ "Normal",
+ "Quiet",
+ "Verbose"
+ ]
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/.nuke/parameters.json b/.nuke/parameters.json
new file mode 100644
index 00000000..b1a9a717
--- /dev/null
+++ b/.nuke/parameters.json
@@ -0,0 +1,4 @@
+{
+ "$schema": "./build.schema.json",
+ "Solution": "Backend.Fx.sln"
+}
\ No newline at end of file
diff --git a/.tmp/build-attempt.log b/.tmp/build-attempt.log
deleted file mode 100644
index c70b1d97..00000000
--- a/.tmp/build-attempt.log
+++ /dev/null
@@ -1,7 +0,0 @@
-884bc421701d4c4e7e62c0eca741ed63
-Clean
-Restore
-Compile
-Test
-Pack
-Publish
diff --git a/Backend.Fx.sln b/Backend.Fx.sln
index 8353da85..58f5ec2f 100644
--- a/Backend.Fx.sln
+++ b/Backend.Fx.sln
@@ -15,8 +15,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{C7885592
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Backend.Fx.Tests", "tests\Backend.Fx.Tests\Backend.Fx.Tests.csproj", "{3706F748-43F6-41BD-8875-81FA679220C7}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Backend.Fx.EfCorePersistence.Tests", "tests\Backend.Fx.EfCorePersistence.Tests\Backend.Fx.EfCorePersistence.Tests.csproj", "{4BB72B85-61F2-4C7F-9079-EA43492FCD44}"
-EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "abstractions", "abstractions", "{A742F814-725A-44ED-95E6-98E142738E9D}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "environments", "environments", "{56ACAE69-F7F0-4FF2-BEE6-4B079481CF9A}"
@@ -25,22 +23,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "implementations", "implemen
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Backend.Fx", "src\abstractions\Backend.Fx\Backend.Fx.csproj", "{581DCC00-9246-4A2E-AE31-206742B2746A}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Backend.Fx.EfCorePersistence", "src\implementations\Backend.Fx.EfCorePersistence\Backend.Fx.EfCorePersistence.csproj", "{A60B7952-D92C-403D-9710-65BE13963C7E}"
-EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Backend.Fx.RabbitMq", "src\implementations\Backend.Fx.RabbitMq\Backend.Fx.RabbitMq.csproj", "{2C826FC0-443A-4874-B213-C35BFDEA200A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Backend.Fx.SimpleInjectorDependencyInjection", "src\implementations\Backend.Fx.SimpleInjetorDependencyInjection\Backend.Fx.SimpleInjectorDependencyInjection.csproj", "{FF042FB5-BA44-4655-8903-2644FE549810}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Backend.Fx.AspNetCore", "src\environments\Backend.Fx.AspNetCore\Backend.Fx.AspNetCore.csproj", "{25746028-5116-4600-A0C4-35DE0C468A8F}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Backend.Fx.SimpleInjectorDependencyInjection.Tests", "tests\Backend.Fx.SimpleInjectorDependencyInjection.Tests\Backend.Fx.SimpleInjectorDependencyInjection.Tests.csproj", "{D98AED23-ABB8-4130-9612-54AEFE9D2272}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Backend.Fx.InMemoryPersistence", "src\implementations\Backend.Fx.InMemoryPersistence\Backend.Fx.InMemoryPersistence.csproj", "{0B8F13CA-1347-4655-9D41-AED21B1AFAC4}"
-EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Backend.Fx.RabbitMq.Tests", "tests\Backend.Fx.RabbitMq.Tests\Backend.Fx.RabbitMq.Tests.csproj", "{6D0A5E9D-2FA5-4CC9-96B0-C2C871335E3A}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Backend.Fx.AspNetCore.Tests", "tests\Backend.Fx.AspNetCore.Tests\Backend.Fx.AspNetCore.Tests.csproj", "{DF40E1E8-FB19-455E-9CED-212C544AA8BC}"
-EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{16EBBF6D-EA66-4E14-BE2D-1900CBC747F7}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{FD21EC43-FC48-433A-8C4F-5CCFC1A2B35E}"
@@ -58,6 +48,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Backend.Fx.EfCore6Persisten
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Backend.Fx.EfCore6Persistence.Tests", "tests\Backend.Fx.EfCore6Persistence.Tests\Backend.Fx.EfCore6Persistence.Tests.csproj", "{E50D7E8D-D012-4683-BA05-C877BAA25230}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Backend.Fx.MicrosoftDependencyInjection", "src\implementations\Backend.Fx.MicrosoftDependencyInjection\Backend.Fx.MicrosoftDependencyInjection.csproj", "{B4791DB0-F8DD-4248-86CB-407E46F55B13}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Backend.Fx.TestUtil", "tests\Backend.Fx.TestUtil\Backend.Fx.TestUtil.csproj", "{3AD4F223-DC1D-40B7-9DB9-DC88FDD0178D}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -71,18 +65,10 @@ Global
{3706F748-43F6-41BD-8875-81FA679220C7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3706F748-43F6-41BD-8875-81FA679220C7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3706F748-43F6-41BD-8875-81FA679220C7}.Release|Any CPU.Build.0 = Release|Any CPU
- {4BB72B85-61F2-4C7F-9079-EA43492FCD44}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {4BB72B85-61F2-4C7F-9079-EA43492FCD44}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {4BB72B85-61F2-4C7F-9079-EA43492FCD44}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {4BB72B85-61F2-4C7F-9079-EA43492FCD44}.Release|Any CPU.Build.0 = Release|Any CPU
{581DCC00-9246-4A2E-AE31-206742B2746A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{581DCC00-9246-4A2E-AE31-206742B2746A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{581DCC00-9246-4A2E-AE31-206742B2746A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{581DCC00-9246-4A2E-AE31-206742B2746A}.Release|Any CPU.Build.0 = Release|Any CPU
- {A60B7952-D92C-403D-9710-65BE13963C7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {A60B7952-D92C-403D-9710-65BE13963C7E}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {A60B7952-D92C-403D-9710-65BE13963C7E}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {A60B7952-D92C-403D-9710-65BE13963C7E}.Release|Any CPU.Build.0 = Release|Any CPU
{2C826FC0-443A-4874-B213-C35BFDEA200A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2C826FC0-443A-4874-B213-C35BFDEA200A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2C826FC0-443A-4874-B213-C35BFDEA200A}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -95,22 +81,10 @@ Global
{25746028-5116-4600-A0C4-35DE0C468A8F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{25746028-5116-4600-A0C4-35DE0C468A8F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{25746028-5116-4600-A0C4-35DE0C468A8F}.Release|Any CPU.Build.0 = Release|Any CPU
- {D98AED23-ABB8-4130-9612-54AEFE9D2272}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {D98AED23-ABB8-4130-9612-54AEFE9D2272}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {D98AED23-ABB8-4130-9612-54AEFE9D2272}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {D98AED23-ABB8-4130-9612-54AEFE9D2272}.Release|Any CPU.Build.0 = Release|Any CPU
- {0B8F13CA-1347-4655-9D41-AED21B1AFAC4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {0B8F13CA-1347-4655-9D41-AED21B1AFAC4}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {0B8F13CA-1347-4655-9D41-AED21B1AFAC4}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {0B8F13CA-1347-4655-9D41-AED21B1AFAC4}.Release|Any CPU.Build.0 = Release|Any CPU
{6D0A5E9D-2FA5-4CC9-96B0-C2C871335E3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6D0A5E9D-2FA5-4CC9-96B0-C2C871335E3A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6D0A5E9D-2FA5-4CC9-96B0-C2C871335E3A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6D0A5E9D-2FA5-4CC9-96B0-C2C871335E3A}.Release|Any CPU.Build.0 = Release|Any CPU
- {DF40E1E8-FB19-455E-9CED-212C544AA8BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {DF40E1E8-FB19-455E-9CED-212C544AA8BC}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {DF40E1E8-FB19-455E-9CED-212C544AA8BC}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {DF40E1E8-FB19-455E-9CED-212C544AA8BC}.Release|Any CPU.Build.0 = Release|Any CPU
{38034961-CE3B-4286-A9EB-496DECA39632}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{38034961-CE3B-4286-A9EB-496DECA39632}.Debug|Any CPU.Build.0 = Debug|Any CPU
{38034961-CE3B-4286-A9EB-496DECA39632}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -119,32 +93,37 @@ Global
{E50D7E8D-D012-4683-BA05-C877BAA25230}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E50D7E8D-D012-4683-BA05-C877BAA25230}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E50D7E8D-D012-4683-BA05-C877BAA25230}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B4791DB0-F8DD-4248-86CB-407E46F55B13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B4791DB0-F8DD-4248-86CB-407E46F55B13}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B4791DB0-F8DD-4248-86CB-407E46F55B13}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B4791DB0-F8DD-4248-86CB-407E46F55B13}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3AD4F223-DC1D-40B7-9DB9-DC88FDD0178D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3AD4F223-DC1D-40B7-9DB9-DC88FDD0178D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3AD4F223-DC1D-40B7-9DB9-DC88FDD0178D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3AD4F223-DC1D-40B7-9DB9-DC88FDD0178D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{3706F748-43F6-41BD-8875-81FA679220C7} = {C7885592-A4B8-4BA8-8D3A-1EDA4025D17A}
- {4BB72B85-61F2-4C7F-9079-EA43492FCD44} = {C7885592-A4B8-4BA8-8D3A-1EDA4025D17A}
{A742F814-725A-44ED-95E6-98E142738E9D} = {53D4501E-953C-4A7C-97C4-1F9DE04BD092}
{56ACAE69-F7F0-4FF2-BEE6-4B079481CF9A} = {53D4501E-953C-4A7C-97C4-1F9DE04BD092}
{739A7296-579F-4D9A-BC73-DCECD260D7A0} = {53D4501E-953C-4A7C-97C4-1F9DE04BD092}
{581DCC00-9246-4A2E-AE31-206742B2746A} = {A742F814-725A-44ED-95E6-98E142738E9D}
{25746028-5116-4600-A0C4-35DE0C468A8F} = {56ACAE69-F7F0-4FF2-BEE6-4B079481CF9A}
- {D98AED23-ABB8-4130-9612-54AEFE9D2272} = {C7885592-A4B8-4BA8-8D3A-1EDA4025D17A}
{6D0A5E9D-2FA5-4CC9-96B0-C2C871335E3A} = {C7885592-A4B8-4BA8-8D3A-1EDA4025D17A}
- {DF40E1E8-FB19-455E-9CED-212C544AA8BC} = {C7885592-A4B8-4BA8-8D3A-1EDA4025D17A}
{16EBBF6D-EA66-4E14-BE2D-1900CBC747F7} = {6B64354E-D95B-4711-BAF6-B32049C90CD9}
{FD21EC43-FC48-433A-8C4F-5CCFC1A2B35E} = {16EBBF6D-EA66-4E14-BE2D-1900CBC747F7}
{ADC35CAD-F5B1-42B6-A0CC-B96974C11F11} = {739A7296-579F-4D9A-BC73-DCECD260D7A0}
- {A60B7952-D92C-403D-9710-65BE13963C7E} = {ADC35CAD-F5B1-42B6-A0CC-B96974C11F11}
- {0B8F13CA-1347-4655-9D41-AED21B1AFAC4} = {ADC35CAD-F5B1-42B6-A0CC-B96974C11F11}
{8BC1C02F-0785-4161-BC37-7D462BD6F42D} = {739A7296-579F-4D9A-BC73-DCECD260D7A0}
{2C826FC0-443A-4874-B213-C35BFDEA200A} = {8BC1C02F-0785-4161-BC37-7D462BD6F42D}
{22E4DE95-C3E5-49E6-83BF-BF30905A746B} = {739A7296-579F-4D9A-BC73-DCECD260D7A0}
{FF042FB5-BA44-4655-8903-2644FE549810} = {22E4DE95-C3E5-49E6-83BF-BF30905A746B}
{38034961-CE3B-4286-A9EB-496DECA39632} = {ADC35CAD-F5B1-42B6-A0CC-B96974C11F11}
{E50D7E8D-D012-4683-BA05-C877BAA25230} = {C7885592-A4B8-4BA8-8D3A-1EDA4025D17A}
+ {B4791DB0-F8DD-4248-86CB-407E46F55B13} = {22E4DE95-C3E5-49E6-83BF-BF30905A746B}
+ {3AD4F223-DC1D-40B7-9DB9-DC88FDD0178D} = {C7885592-A4B8-4BA8-8D3A-1EDA4025D17A}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {45648557-C751-44AD-9C87-0F12EB673969}
diff --git a/Backend.Fx.sln.DotSettings b/Backend.Fx.sln.DotSettings
index afc9e37f..96ad1905 100644
--- a/Backend.Fx.sln.DotSettings
+++ b/Backend.Fx.sln.DotSettings
@@ -1,7 +1,10 @@
+ DI
True
True
+ True
True
True
True
+ True
True
\ No newline at end of file
diff --git a/build.cmd b/build.cmd
index 8b8b89dc..b08cc590 100755
--- a/build.cmd
+++ b/build.cmd
@@ -4,4 +4,4 @@
:; exit $?
@ECHO OFF
-powershell -ExecutionPolicy ByPass -NoProfile "%~dp0build.ps1" %*
+powershell -ExecutionPolicy ByPass -NoProfile -File "%~dp0build.ps1" %*
diff --git a/build.ps1 b/build.ps1
index e5c8a44e..8c52d631 100644
--- a/build.ps1
+++ b/build.ps1
@@ -14,7 +14,7 @@ $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent
###########################################################################
$BuildProjectFile = "$PSScriptRoot\build\_build.csproj"
-$TempDirectory = "$PSScriptRoot\\.tmp"
+$TempDirectory = "$PSScriptRoot\\.nuke\temp"
$DotNetGlobalFile = "$PSScriptRoot\\global.json"
$DotNetInstallUrl = "https://dot.net/v1/dotnet-install.ps1"
@@ -56,14 +56,14 @@ else {
# Install by channel or version
$DotNetDirectory = "$TempDirectory\dotnet-win"
if (!(Test-Path variable:DotNetVersion)) {
- ExecSafe { & $DotNetInstallFile -InstallDir $DotNetDirectory -Channel $DotNetChannel -NoPath }
+ ExecSafe { & powershell $DotNetInstallFile -InstallDir $DotNetDirectory -Channel $DotNetChannel -NoPath }
} else {
- ExecSafe { & $DotNetInstallFile -InstallDir $DotNetDirectory -Version $DotNetVersion -NoPath }
+ ExecSafe { & powershell $DotNetInstallFile -InstallDir $DotNetDirectory -Version $DotNetVersion -NoPath }
}
$env:DOTNET_EXE = "$DotNetDirectory\dotnet.exe"
}
-Write-Output "Microsoft (R) .NET Core SDK version $(& $env:DOTNET_EXE --version)"
+Write-Output "Microsoft (R) .NET SDK version $(& $env:DOTNET_EXE --version)"
ExecSafe { & $env:DOTNET_EXE build $BuildProjectFile /nodeReuse:false /p:UseSharedCompilation=false -nologo -clp:NoSummary --verbosity quiet }
ExecSafe { & $env:DOTNET_EXE run --project $BuildProjectFile --no-build -- $BuildArguments }
diff --git a/build.sh b/build.sh
index 3d526432..1f3ba09e 100755
--- a/build.sh
+++ b/build.sh
@@ -10,7 +10,7 @@ SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)
###########################################################################
BUILD_PROJECT_FILE="$SCRIPT_DIR/build/_build.csproj"
-TEMP_DIRECTORY="$SCRIPT_DIR//.tmp"
+TEMP_DIRECTORY="$SCRIPT_DIR//.nuke/temp"
DOTNET_GLOBAL_FILE="$SCRIPT_DIR//global.json"
DOTNET_INSTALL_URL="https://dot.net/v1/dotnet-install.sh"
@@ -56,7 +56,7 @@ else
export DOTNET_EXE="$DOTNET_DIRECTORY/dotnet"
fi
-echo "Microsoft (R) .NET Core SDK version $("$DOTNET_EXE" --version)"
+echo "Microsoft (R) .NET SDK version $("$DOTNET_EXE" --version)"
"$DOTNET_EXE" build "$BUILD_PROJECT_FILE" /nodeReuse:false /p:UseSharedCompilation=false -nologo -clp:NoSummary --verbosity quiet
"$DOTNET_EXE" run --project "$BUILD_PROJECT_FILE" --no-build -- "$@"
diff --git a/build/Build.cs b/build/Build.cs
index 8777529b..f58dd3cc 100644
--- a/build/Build.cs
+++ b/build/Build.cs
@@ -1,7 +1,6 @@
using System;
using Nuke.Common;
using Nuke.Common.CI;
-using Nuke.Common.Execution;
using Nuke.Common.Git;
using Nuke.Common.IO;
using Nuke.Common.ProjectModel;
@@ -11,9 +10,8 @@
using static Nuke.Common.IO.FileSystemTasks;
using static Nuke.Common.Tools.DotNet.DotNetTasks;
-[CheckBuildProjectConfigurations]
[ShutdownDotNetAfterServerBuild]
-class Build : NukeBuild
+partial class Build : NukeBuild
{
public static int Main() => Execute(x => x.Publish);
@@ -53,6 +51,7 @@ class Build : NukeBuild
.DependsOn(Restore)
.Executes(() =>
{
+ Serilog.Log.Information("Version {Version}", GitVersion?.SemVer ?? "no version!");
DotNetBuild(s => s
.SetProjectFile(Solution)
.SetConfiguration(Configuration)
@@ -63,14 +62,16 @@ class Build : NukeBuild
});
Target Test => _ => _
- .DependsOn(Compile)
+ .DependsOn(Compile, StartDependencies)
.Executes(() =>
{
DotNetTest(s => s
.SetProjectFile(Solution)
.SetConfiguration(Configuration)
.EnableNoRestore());
- });
+ })
+ .ProceedAfterFailure()
+ .Triggers(StopDependencies);
Target Pack => _ => _
.DependsOn(Test)
diff --git a/build/Build.deps.cs b/build/Build.deps.cs
new file mode 100644
index 00000000..89bbf082
--- /dev/null
+++ b/build/Build.deps.cs
@@ -0,0 +1,40 @@
+using System.Linq;
+using Nuke.Common;
+using Nuke.Common.Tools.Docker;
+using static Nuke.Common.Tools.Docker.DockerTasks;
+
+partial class Build
+{
+ const string RabbitMqImageName = "docker.io/library/rabbitmq:latest";
+ const string RabbitMqContainerName = "backendfx-rabbitmq";
+
+ Target StartDependencies
+ => _ => _
+ .Executes(() =>
+ {
+ var existingContainers = DockerPs(s => s
+ .SetFilter($"name={RabbitMqContainerName}")
+ .EnableAll()
+ .EnableQuiet());
+ if (existingContainers.Any())
+ {
+ DockerRm(s => s
+ .AddContainers(existingContainers.Select(c => c.Text))
+ .EnableForce());
+ }
+
+ DockerPull(x => x.SetName(RabbitMqImageName).EnableQuiet());
+
+ DockerRun(x => x
+ .SetImage(RabbitMqImageName)
+ .SetName(RabbitMqContainerName)
+ .SetEnv("RABBITMQ_DEFAULT_USER=test", "RABBITMQ_DEFAULT_PASS=password")
+ .SetPublish("5672:5672")
+ .EnableDetach());
+ });
+
+ Target StopDependencies
+ => _ => _
+ .AssuredAfterFailure()
+ .Executes(() => DockerStop(x => x.SetContainers(RabbitMqContainerName)));
+}
diff --git a/build/_build.csproj b/build/_build.csproj
index 565047d2..e7b7cf82 100644
--- a/build/_build.csproj
+++ b/build/_build.csproj
@@ -2,16 +2,17 @@
Exe
- net5.0
+ net6.0
CS0649;CS0169
..
..
+ 1
-
-
+
+
diff --git a/src/abstractions/Backend.Fx/Backend.Fx.csproj b/src/abstractions/Backend.Fx/Backend.Fx.csproj
index 8e4bc2c5..a798f7e7 100644
--- a/src/abstractions/Backend.Fx/Backend.Fx.csproj
+++ b/src/abstractions/Backend.Fx/Backend.Fx.csproj
@@ -7,7 +7,8 @@
false
false
false
- false
+ false
+ 9
@@ -24,21 +25,16 @@
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
+
\ No newline at end of file
diff --git a/src/abstractions/Backend.Fx/Backend.Fx.csproj.DotSettings b/src/abstractions/Backend.Fx/Backend.Fx.csproj.DotSettings
new file mode 100644
index 00000000..c2f4bbdb
--- /dev/null
+++ b/src/abstractions/Backend.Fx/Backend.Fx.csproj.DotSettings
@@ -0,0 +1,8 @@
+
+ Library
+
\ No newline at end of file
diff --git a/src/abstractions/Backend.Fx/BackendFxApplication.cs b/src/abstractions/Backend.Fx/BackendFxApplication.cs
new file mode 100644
index 00000000..8c7647b1
--- /dev/null
+++ b/src/abstractions/Backend.Fx/BackendFxApplication.cs
@@ -0,0 +1,169 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Threading;
+using System.Threading.Tasks;
+using Backend.Fx.DependencyInjection;
+using Backend.Fx.ExecutionPipeline;
+using Backend.Fx.Features;
+using Backend.Fx.Logging;
+using Backend.Fx.Util;
+using JetBrains.Annotations;
+using Microsoft.Extensions.Logging;
+
+namespace Backend.Fx
+{
+ ///
+ /// The root object of the whole backend fx application framework
+ ///
+ [PublicAPI]
+ public interface IBackendFxApplication : IDisposable
+ {
+ ///
+ /// The invoker runs a given action asynchronously in an application scope with injection facilities
+ ///
+ IBackendFxApplicationInvoker Invoker { get; }
+
+ ///
+ /// The composition root of the dependency injection framework
+ ///
+ ICompositionRoot CompositionRoot { get; }
+
+ ///
+ /// The global exception logger of this application
+ ///
+ IExceptionLogger ExceptionLogger { get; }
+
+ Assembly[] Assemblies { get; }
+
+ ///
+ /// allows synchronously awaiting application startup
+ ///
+ Task WaitForBootAsync(CancellationToken cancellationToken = default);
+
+ ///
+ /// Initializes and starts the application (async)
+ ///
+ ///
+ Task BootAsync(CancellationToken cancellationToken = default);
+
+ ///
+ /// Enables an optional feature. Must be done before calling .
+ ///
+ ///
+ void EnableFeature(Feature feature);
+
+ void RequireDependantFeature() where TFeature : Feature;
+ }
+
+
+ [PublicAPI]
+ public class BackendFxApplication : IBackendFxApplication
+ {
+ private readonly ILogger _logger = Log.Create();
+ private readonly List _features = new();
+ private readonly Lazy _bootAction;
+
+ ///
+ /// Initializes the application's runtime instance
+ ///
+ /// The composition root of the dependency injection framework
+ ///
+ ///
+ public BackendFxApplication(ICompositionRoot compositionRoot, IExceptionLogger exceptionLogger,
+ params Assembly[] assemblies)
+ {
+ assemblies ??= Array.Empty();
+
+ _logger.LogInformation(
+ "Initializing application with {CompositionRoot} providing services from [{Assemblies}]",
+ compositionRoot.GetType().GetDetailedTypeName(),
+ string.Join(", ", assemblies.Select(ass => ass.GetName().Name)));
+
+ var invoker = new BackendFxApplicationInvoker(this);
+
+ Invoker = new ExceptionLoggingInvoker(exceptionLogger, invoker);
+
+ CompositionRoot = new LogRegistrationsDecorator(compositionRoot);
+ ExceptionLogger = exceptionLogger;
+ Assemblies = assemblies;
+ CompositionRoot.RegisterModules(new ExecutionPipelineModule(withFrozenClockDuringExecution: true));
+
+ _bootAction = new Lazy(async () =>
+ {
+ _logger.LogInformation("Booting application");
+ CompositionRoot.Verify();
+
+ foreach (Feature feature in _features)
+ {
+ if (feature is IBootableFeature bootableFeature)
+ {
+ await bootableFeature.BootAsync(this).ConfigureAwait(false);
+ }
+ }
+ });
+ }
+
+ public Assembly[] Assemblies { get; }
+
+ public IBackendFxApplicationInvoker Invoker { get; }
+
+ public ICompositionRoot CompositionRoot { get; }
+
+ public IExceptionLogger ExceptionLogger { get; }
+
+ public virtual void EnableFeature(Feature feature)
+ {
+ if (_bootAction.IsValueCreated)
+ {
+ throw new InvalidOperationException("Features must be enabled before booting the application");
+ }
+
+ feature.Enable(this);
+ _features.Add(feature);
+ }
+
+ public void RequireDependantFeature() where TFeature : Feature
+ {
+ if (!_features.OfType().Any())
+ {
+ throw new InvalidOperationException(
+ $"This feature requires the {typeof(TFeature).Name} to be enabled first");
+ }
+ }
+
+ public async Task BootAsync(CancellationToken cancellationToken = default)
+ {
+ await _bootAction.Value.ConfigureAwait(false);
+ }
+
+ public async Task WaitForBootAsync(CancellationToken cancellationToken = default)
+ {
+ await Task.Run(async () =>
+ {
+ do
+ {
+ if (cancellationToken.IsCancellationRequested ||
+ _bootAction.IsValueCreated && _bootAction.Value.Status is TaskStatus.Canceled or TaskStatus.Faulted or TaskStatus.RanToCompletion)
+ {
+ return;
+ }
+
+ await Task.Delay(50, cancellationToken).ConfigureAwait(false);
+ } while (true);
+ }, cancellationToken).ConfigureAwait(false);
+ }
+
+ public void Dispose()
+ {
+ _logger.LogInformation("Application shut down initialized");
+ foreach (Feature feature in _features)
+ {
+ feature.Dispose();
+ }
+
+ CompositionRoot?.Dispose();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/abstractions/Backend.Fx/BuildingBlocks/AggregateRoot.cs b/src/abstractions/Backend.Fx/BuildingBlocks/AggregateRoot.cs
deleted file mode 100644
index e22101f1..00000000
--- a/src/abstractions/Backend.Fx/BuildingBlocks/AggregateRoot.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-namespace Backend.Fx.BuildingBlocks
-{
- ///
- /// A collection of objects that are bound together by a root entity
- /// See https://en.wikipedia.org/wiki/Domain-driven_design#Building_blocks
- ///
- public abstract class AggregateRoot : Entity
- {
- protected AggregateRoot()
- {
- }
-
- protected AggregateRoot(int id) : base(id)
- {
- }
-
- public int TenantId { get; set; }
- }
-}
\ No newline at end of file
diff --git a/src/abstractions/Backend.Fx/BuildingBlocks/Entity.cs b/src/abstractions/Backend.Fx/BuildingBlocks/Entity.cs
deleted file mode 100644
index 5ddc4d22..00000000
--- a/src/abstractions/Backend.Fx/BuildingBlocks/Entity.cs
+++ /dev/null
@@ -1,63 +0,0 @@
-using System;
-using System.ComponentModel.DataAnnotations;
-using Backend.Fx.Extensions;
-using JetBrains.Annotations;
-
-namespace Backend.Fx.BuildingBlocks
-{
- ///
- /// An object that is not defined by its attributes, but rather by a thread of continuity and its identity.
- /// https://en.wikipedia.org/wiki/Domain-driven_design#Building_blocks
- ///
- public abstract class Entity : Identified
- {
- protected Entity()
- {
- }
-
- protected Entity(int id)
- {
- Id = id;
- }
-
- public DateTime CreatedOn { get; protected set; }
-
- [StringLength(100)] public string CreatedBy { get; protected set; }
-
- public DateTime? ChangedOn { get; protected set; }
-
- [StringLength(100)] public string ChangedBy { get; protected set; }
-
- public void SetCreatedProperties([NotNull] string createdBy, DateTime createdOn)
- {
- if (createdBy == null)
- {
- throw new ArgumentNullException(nameof(createdBy));
- }
-
- if (createdBy == string.Empty)
- {
- throw new ArgumentException(nameof(createdBy));
- }
-
- CreatedBy = createdBy.Cut(100);
- CreatedOn = createdOn;
- }
-
- public void SetModifiedProperties([NotNull] string changedBy, DateTime changedOn)
- {
- if (changedBy == null)
- {
- throw new ArgumentNullException(nameof(changedBy));
- }
-
- if (changedBy == string.Empty)
- {
- throw new ArgumentException(nameof(changedBy));
- }
-
- ChangedBy = changedBy.Cut(100);
- ChangedOn = changedOn;
- }
- }
-}
\ No newline at end of file
diff --git a/src/abstractions/Backend.Fx/BuildingBlocks/IAsyncRepository.cs b/src/abstractions/Backend.Fx/BuildingBlocks/IAsyncRepository.cs
deleted file mode 100644
index 9365d5c0..00000000
--- a/src/abstractions/Backend.Fx/BuildingBlocks/IAsyncRepository.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace Backend.Fx.BuildingBlocks
-{
- ///
- /// Encapsulates methods for retrieving domain objects
- /// See https://en.wikipedia.org/wiki/Domain-driven_design#Building_blocks
- ///
- ///
- public interface IAsyncRepository where TAggregateRoot : AggregateRoot
- {
- Task SingleAsync(int id, CancellationToken cancellationToken = default);
- Task SingleOrDefaultAsync(int id, CancellationToken cancellationToken = default);
- Task GetAllAsync(CancellationToken cancellationToken = default);
- Task AnyAsync(CancellationToken cancellationToken = default);
- Task ResolveAsync(IEnumerable ids, CancellationToken cancellationToken = default);
- IQueryable AggregateQueryable { get; }
- }
-}
\ No newline at end of file
diff --git a/src/abstractions/Backend.Fx/BuildingBlocks/IRepository.cs b/src/abstractions/Backend.Fx/BuildingBlocks/IRepository.cs
deleted file mode 100644
index 19ba3187..00000000
--- a/src/abstractions/Backend.Fx/BuildingBlocks/IRepository.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-using System.Collections.Generic;
-using System.Linq;
-
-namespace Backend.Fx.BuildingBlocks
-{
- ///
- /// Encapsulates methods for retrieving domain objects
- /// See https://en.wikipedia.org/wiki/Domain-driven_design#Building_blocks
- ///
- ///
- public interface IRepository where TAggregateRoot : AggregateRoot
- {
- TAggregateRoot Single(int id);
- TAggregateRoot SingleOrDefault(int id);
- TAggregateRoot[] GetAll();
- void Delete(TAggregateRoot aggregateRoot);
- void Add(TAggregateRoot aggregateRoot);
- void AddRange(TAggregateRoot[] aggregateRoots);
- bool Any();
- TAggregateRoot[] Resolve(IEnumerable ids);
- IQueryable AggregateQueryable { get; }
- }
-}
\ No newline at end of file
diff --git a/src/abstractions/Backend.Fx/BuildingBlocks/IView.cs b/src/abstractions/Backend.Fx/BuildingBlocks/IView.cs
deleted file mode 100644
index 0121addb..00000000
--- a/src/abstractions/Backend.Fx/BuildingBlocks/IView.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.Linq;
-using System.Linq.Expressions;
-
-namespace Backend.Fx.BuildingBlocks
-{
- public interface IView : IQueryable
- {
- }
-
- public abstract class View : IView
- {
- private readonly IQueryable _viewImplementation;
-
- protected View(IQueryable viewImplementation)
- {
- _viewImplementation = viewImplementation;
- }
-
- public IEnumerator GetEnumerator()
- {
- return _viewImplementation.GetEnumerator();
- }
-
- IEnumerator IEnumerable.GetEnumerator()
- {
- return ((IEnumerable) _viewImplementation).GetEnumerator();
- }
-
- public Type ElementType => _viewImplementation.ElementType;
-
- public Expression Expression => _viewImplementation.Expression;
-
- public IQueryProvider Provider => _viewImplementation.Provider;
- }
-}
\ No newline at end of file
diff --git a/src/abstractions/Backend.Fx/BuildingBlocks/Identified.cs b/src/abstractions/Backend.Fx/BuildingBlocks/Identified.cs
deleted file mode 100644
index f37a3bde..00000000
--- a/src/abstractions/Backend.Fx/BuildingBlocks/Identified.cs
+++ /dev/null
@@ -1,59 +0,0 @@
-using System;
-using System.ComponentModel.DataAnnotations;
-using System.Diagnostics;
-using JetBrains.Annotations;
-
-namespace Backend.Fx.BuildingBlocks
-{
- [DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")]
- public abstract class Identified : IEquatable
- {
- [Key] public int Id { get; set; }
-
- [UsedImplicitly] public string DebuggerDisplay => $"{GetType().Name}[{Id}]";
-
- public bool Equals(Identified other)
- {
- if (other == null || other.GetType() != GetType())
- {
- return false;
- }
-
- return Id.Equals(other.Id);
- }
-
- public override bool Equals(object obj)
- {
- var other = obj as Identified;
- if (other == null)
- {
- return false;
- }
-
- return Equals(other);
- }
-
- public override int GetHashCode()
- {
- // ReSharper disable NonReadonlyMemberInGetHashCode
- if (Id != 0)
- {
- return Id.GetHashCode();
- }
- // ReSharper enable NonReadonlyMemberInGetHashCode
-
- // ReSharper disable once BaseObjectGetHashCodeCallInGetHashCode
- return base.GetHashCode();
- }
-
- public static bool operator ==(Identified x, Identified y)
- {
- return Equals(x, y);
- }
-
- public static bool operator !=(Identified x, Identified y)
- {
- return !(x == y);
- }
- }
-}
\ No newline at end of file
diff --git a/src/abstractions/Backend.Fx/BuildingBlocks/Repository.cs b/src/abstractions/Backend.Fx/BuildingBlocks/Repository.cs
deleted file mode 100644
index 66be9b8b..00000000
--- a/src/abstractions/Backend.Fx/BuildingBlocks/Repository.cs
+++ /dev/null
@@ -1,157 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using Backend.Fx.Environment.MultiTenancy;
-using Backend.Fx.Exceptions;
-using Backend.Fx.Extensions;
-using Backend.Fx.Logging;
-using Backend.Fx.Patterns.Authorization;
-using Backend.Fx.Patterns.DependencyInjection;
-using JetBrains.Annotations;
-using Microsoft.Extensions.Logging;
-using ILogger = Microsoft.Extensions.Logging.ILogger;
-
-namespace Backend.Fx.BuildingBlocks
-{
- public abstract class Repository : IRepository where TAggregateRoot : AggregateRoot
- {
- private static readonly ILogger Logger = Log.Create>();
- private readonly IAggregateAuthorization _aggregateAuthorization;
- private readonly ICurrentTHolder _tenantIdHolder;
-
- protected Repository(ICurrentTHolder tenantIdHolder, IAggregateAuthorization aggregateAuthorization)
- {
- Logger.LogTrace(
- "Instantiating a new Repository<{AggregateTypeName}> for tenant [{TenantId}]",
- AggregateTypeName,
- tenantIdHolder.Current.HasValue ? tenantIdHolder.Current.Value.ToString() : "null");
- _tenantIdHolder = tenantIdHolder;
- _aggregateAuthorization = aggregateAuthorization;
- }
-
- protected static string AggregateTypeName => typeof(TAggregateRoot).Name;
-
- protected abstract IQueryable RawAggregateQueryable { get; }
-
- public IQueryable AggregateQueryable
- {
- get
- {
- if (_tenantIdHolder.Current.HasValue)
- {
- return _aggregateAuthorization.Filter(RawAggregateQueryable
- .Where(agg => agg.TenantId == _tenantIdHolder.Current.Value));
- }
-
- return RawAggregateQueryable.Where(agg => false);
- }
- }
-
- public TAggregateRoot Single(int id)
- {
- Logger.LogDebug("Getting single {AggregateTypeName}[{Id}]", AggregateTypeName, id);
- TAggregateRoot aggregateRoot = AggregateQueryable.FirstOrDefault(agg => agg.Id.Equals(id));
- if (aggregateRoot == null)
- {
- throw new NotFoundException(id);
- }
-
- return aggregateRoot;
- }
-
- public TAggregateRoot SingleOrDefault(int id)
- {
- Logger.LogDebug("Getting single or default {AggregateTypeName}[{Id}]", AggregateTypeName, id);
- return AggregateQueryable.FirstOrDefault(agg => agg.Id.Equals(id));
- }
-
- public TAggregateRoot[] GetAll()
- {
- return AggregateQueryable.ToArray();
- }
-
- public void Delete([NotNull] TAggregateRoot aggregateRoot)
- {
- if (aggregateRoot == null)
- {
- throw new ArgumentNullException(nameof(aggregateRoot));
- }
-
- Logger.LogDebug("Deleting {AggregateTypeName}[{Id}]", AggregateTypeName, aggregateRoot.Id);
- if (aggregateRoot.TenantId != _tenantIdHolder.Current.Value || !_aggregateAuthorization.CanDelete(aggregateRoot))
- {
- throw new ForbiddenException($"You are not allowed to delete {typeof(TAggregateRoot).Name}[{aggregateRoot.Id}]");
- }
-
- DeletePersistent(aggregateRoot);
- }
-
- public void Add([NotNull] TAggregateRoot aggregateRoot)
- {
- if (aggregateRoot == null)
- {
- throw new ArgumentNullException(nameof(aggregateRoot));
- }
-
- if (_aggregateAuthorization.CanCreate(aggregateRoot))
- {
- Logger.LogDebug("Adding {AggregateTypeName}[{Id}]", AggregateTypeName, aggregateRoot.Id);
- aggregateRoot.TenantId = _tenantIdHolder.Current.Value;
- AddPersistent(aggregateRoot);
- }
- else
- {
- throw new ForbiddenException($"You are not allowed to create records of type {typeof(TAggregateRoot).Name}");
- }
- }
-
- public void AddRange([NotNull] TAggregateRoot[] aggregateRoots)
- {
- if (aggregateRoots == null)
- {
- throw new ArgumentNullException(nameof(aggregateRoots));
- }
-
- aggregateRoots.ForAll(agg =>
- {
- if (!_aggregateAuthorization.CanCreate(agg))
- {
- throw new ForbiddenException($"You are not allowed to create records of type {typeof(TAggregateRoot).Name}");
- }
- });
-
- Logger.LogDebug("Adding {Count} items of type {AggregateTypeName}", aggregateRoots.Length, AggregateTypeName);
-
- aggregateRoots.ForAll(agg => agg.TenantId = _tenantIdHolder.Current.Value);
-
- AddRangePersistent(aggregateRoots);
- }
-
- public bool Any()
- {
- return AggregateQueryable.Any();
- }
-
- public TAggregateRoot[] Resolve(IEnumerable ids)
- {
- if (ids == null)
- {
- return Array.Empty();
- }
-
- int[] idsToResolve = ids as int[] ?? ids.ToArray();
- TAggregateRoot[] resolved = AggregateQueryable.Where(agg => idsToResolve.Contains(agg.Id)).ToArray();
- if (resolved.Length != idsToResolve.Length)
- {
- throw new ArgumentException($"The following {AggregateTypeName} ids could not be resolved: {string.Join(", ", idsToResolve.Except(resolved.Select(agg => agg.Id)))}");
- }
- return resolved;
- }
-
- protected abstract void AddPersistent(TAggregateRoot aggregateRoot);
-
- protected abstract void AddRangePersistent(TAggregateRoot[] aggregateRoots);
-
- protected abstract void DeletePersistent(TAggregateRoot aggregateRoot);
- }
-}
\ No newline at end of file
diff --git a/src/abstractions/Backend.Fx/ConfigurationSettings/ISettingSerializer.cs b/src/abstractions/Backend.Fx/ConfigurationSettings/ISettingSerializer.cs
deleted file mode 100644
index 4fc9868d..00000000
--- a/src/abstractions/Backend.Fx/ConfigurationSettings/ISettingSerializer.cs
+++ /dev/null
@@ -1,86 +0,0 @@
-using System;
-using System.Globalization;
-using JetBrains.Annotations;
-
-namespace Backend.Fx.ConfigurationSettings
-{
- public interface ISettingSerializer
- {
- }
-
- public interface ISettingSerializer : ISettingSerializer
- {
- string Serialize(T setting);
- T Deserialize(string value);
- }
-
- [UsedImplicitly]
- public class StringSerializer : ISettingSerializer
- {
- public string Serialize(string setting)
- {
- return setting;
- }
-
- public string Deserialize(string value)
- {
- return value;
- }
- }
-
- [UsedImplicitly]
- public class IntegerSerializer : ISettingSerializer
- {
- public string Serialize(int? setting)
- {
- return setting?.ToString(CultureInfo.InvariantCulture);
- }
-
- public int? Deserialize(string value)
- {
- return string.IsNullOrWhiteSpace(value) ? (int?) null : int.Parse(value, CultureInfo.InvariantCulture);
- }
- }
-
- [UsedImplicitly]
- public class DoubleSerializer : ISettingSerializer
- {
- public string Serialize(double? setting)
- {
- return setting?.ToString("r", CultureInfo.InvariantCulture);
- }
-
- public double? Deserialize(string value)
- {
- return string.IsNullOrWhiteSpace(value) ? (double?) null : double.Parse(value, CultureInfo.InvariantCulture);
- }
- }
-
- [UsedImplicitly]
- public class BooleanSerializer : ISettingSerializer
- {
- public string Serialize(bool? setting)
- {
- return setting?.ToString();
- }
-
- public bool? Deserialize(string value)
- {
- return string.IsNullOrWhiteSpace(value) ? (bool?) null : bool.Parse(value);
- }
- }
-
- [UsedImplicitly]
- public class DateTimeSerializer : ISettingSerializer
- {
- public string Serialize(DateTime? setting)
- {
- return setting?.ToString("O", CultureInfo.InvariantCulture);
- }
-
- public DateTime? Deserialize(string value)
- {
- return string.IsNullOrWhiteSpace(value) ? (DateTime?) null : DateTime.Parse(value, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind);
- }
- }
-}
\ No newline at end of file
diff --git a/src/abstractions/Backend.Fx/ConfigurationSettings/Setting.cs b/src/abstractions/Backend.Fx/ConfigurationSettings/Setting.cs
deleted file mode 100644
index c1e55a92..00000000
--- a/src/abstractions/Backend.Fx/ConfigurationSettings/Setting.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-using Backend.Fx.BuildingBlocks;
-using JetBrains.Annotations;
-
-namespace Backend.Fx.ConfigurationSettings
-{
- public class Setting : AggregateRoot
- {
- [UsedImplicitly]
- private Setting()
- {
- }
-
- public Setting(int id, string key) : base(id)
- {
- Key = key;
- }
-
- public string Key { get; [UsedImplicitly] private set; }
- public string SerializedValue { get; private set; }
-
- public T GetValue(ISettingSerializer serializer)
- {
- return serializer.Deserialize(SerializedValue);
- }
-
- public void SetValue(ISettingSerializer serializer, T value)
- {
- SerializedValue = serializer.Serialize(value);
- }
- }
-}
\ No newline at end of file
diff --git a/src/abstractions/Backend.Fx/ConfigurationSettings/SettingsService.cs b/src/abstractions/Backend.Fx/ConfigurationSettings/SettingsService.cs
deleted file mode 100644
index 26476479..00000000
--- a/src/abstractions/Backend.Fx/ConfigurationSettings/SettingsService.cs
+++ /dev/null
@@ -1,49 +0,0 @@
-using System.Linq;
-using Backend.Fx.BuildingBlocks;
-using Backend.Fx.Patterns.IdGeneration;
-
-namespace Backend.Fx.ConfigurationSettings
-{
- public abstract class SettingsService
- {
- private readonly string _category;
- private readonly IEntityIdGenerator _idGenerator;
- private readonly IRepository _settingRepository;
- private readonly ISettingSerializerFactory _settingSerializerFactory;
-
- protected SettingsService(string category, IEntityIdGenerator idGenerator, IRepository settingRepository, ISettingSerializerFactory settingSerializerFactory)
- {
- _category = category;
- _idGenerator = idGenerator;
- _settingRepository = settingRepository;
- _settingSerializerFactory = settingSerializerFactory;
- }
-
- protected T ReadSetting(string key)
- {
- var categoryKey = _category + "." + key;
- var setting = _settingRepository.AggregateQueryable.SingleOrDefault(s => s.Key == categoryKey);
- if (setting == null)
- {
- return default(T);
- }
-
- var serializer = _settingSerializerFactory.GetSerializer();
- return setting.GetValue(serializer);
- }
-
- protected void WriteSetting(string key, T value)
- {
- var categoryKey = _category + "." + key;
- var setting = _settingRepository.AggregateQueryable.SingleOrDefault(s => s.Key == categoryKey);
- if (setting == null)
- {
- setting = new Setting(_idGenerator.NextId(), categoryKey);
- _settingRepository.Add(setting);
- }
-
- var serializer = _settingSerializerFactory.GetSerializer();
- setting.SetValue(serializer, value);
- }
- }
-}
\ No newline at end of file
diff --git a/src/abstractions/Backend.Fx/DependencyInjection/CompositionRoot.cs b/src/abstractions/Backend.Fx/DependencyInjection/CompositionRoot.cs
new file mode 100644
index 00000000..2acc5663
--- /dev/null
+++ b/src/abstractions/Backend.Fx/DependencyInjection/CompositionRoot.cs
@@ -0,0 +1,71 @@
+using System;
+using System.Collections.Generic;
+using Backend.Fx.Logging;
+using JetBrains.Annotations;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace Backend.Fx.DependencyInjection
+{
+ ///
+ /// Encapsulates the injection framework of choice. The implementation follows the Register/Resolve/Release pattern.
+ /// Usage of this interface is only allowed for framework integration (or tests). NEVER (!) access the injector from
+ /// the domain or application logic, this would result in the Service Locator anti pattern, described here:
+ /// http://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/
+ ///
+ [PublicAPI]
+ public interface ICompositionRoot : IDisposable
+ {
+ void Verify();
+
+ void RegisterModules(params IModule[] modules);
+
+ void Register(ServiceDescriptor serviceDescriptor);
+
+ void RegisterDecorator(ServiceDescriptor serviceDescriptor);
+
+ void RegisterCollection(IEnumerable serviceDescriptors);
+
+ IServiceScope BeginScope();
+
+ ///
+ /// Access to the container's resolution functionality
+ ///
+ IServiceProvider ServiceProvider { get; }
+ }
+
+ [PublicAPI]
+ public abstract class CompositionRoot : ICompositionRoot
+ {
+ private readonly ILogger _logger = Log.Create();
+
+ public abstract IServiceProvider ServiceProvider { get; }
+
+ public abstract void Verify();
+
+ public virtual void RegisterModules(params IModule[] modules)
+ {
+ foreach (IModule module in modules)
+ {
+ _logger.LogInformation("Registering {@Module}", module);
+ module.Register(this);
+ }
+ }
+
+ public abstract void Register(ServiceDescriptor serviceDescriptor);
+
+ public abstract void RegisterDecorator(ServiceDescriptor serviceDescriptor);
+
+ public abstract void RegisterCollection(IEnumerable serviceDescriptors);
+
+ public abstract IServiceScope BeginScope();
+
+ protected abstract void Dispose(bool disposing);
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/IModule.cs b/src/abstractions/Backend.Fx/DependencyInjection/IModule.cs
similarity index 67%
rename from src/abstractions/Backend.Fx/Patterns/DependencyInjection/IModule.cs
rename to src/abstractions/Backend.Fx/DependencyInjection/IModule.cs
index 762f2042..97647018 100644
--- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/IModule.cs
+++ b/src/abstractions/Backend.Fx/DependencyInjection/IModule.cs
@@ -1,8 +1,11 @@
-namespace Backend.Fx.Patterns.DependencyInjection
+using JetBrains.Annotations;
+
+namespace Backend.Fx.DependencyInjection
{
///
/// A logically cohesive bunch of services
///
+ [PublicAPI]
public interface IModule
{
void Register(ICompositionRoot compositionRoot);
diff --git a/src/abstractions/Backend.Fx/DependencyInjection/LogRegistrationsDecorator.cs b/src/abstractions/Backend.Fx/DependencyInjection/LogRegistrationsDecorator.cs
new file mode 100644
index 00000000..0c8ac4a7
--- /dev/null
+++ b/src/abstractions/Backend.Fx/DependencyInjection/LogRegistrationsDecorator.cs
@@ -0,0 +1,88 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Backend.Fx.Logging;
+using Backend.Fx.Util;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace Backend.Fx.DependencyInjection
+{
+ public class LogRegistrationsDecorator : ICompositionRoot
+ {
+ private readonly ILogger _logger = Log.Create();
+ private readonly ICompositionRoot _compositionRoot;
+
+ public LogRegistrationsDecorator(ICompositionRoot compositionRoot)
+ {
+ _compositionRoot = compositionRoot;
+ }
+
+ public void Dispose()
+ {
+ _compositionRoot.Dispose();
+ }
+
+ public void Verify()
+ {
+ _compositionRoot.Verify();
+ }
+
+ public void RegisterModules(params IModule[] modules)
+ {
+ _compositionRoot.RegisterModules(modules);
+ }
+
+ public void Register(ServiceDescriptor serviceDescriptor)
+ {
+ LogDetails("Adding", "registration", serviceDescriptor);
+ _compositionRoot.Register(serviceDescriptor);
+ }
+
+ public void RegisterDecorator(ServiceDescriptor serviceDescriptor)
+ {
+ LogDetails("Adding", "decorator", serviceDescriptor);
+ _compositionRoot.RegisterDecorator(serviceDescriptor);
+ }
+
+ public void RegisterCollection(IEnumerable serviceDescriptors)
+ {
+ serviceDescriptors = serviceDescriptors as ServiceDescriptor[] ?? serviceDescriptors.ToArray();
+ LogAddCollectionRegistration(serviceDescriptors);
+ if (serviceDescriptors.GroupBy(sd => sd.ServiceType).Count() > 1)
+ {
+ _logger.LogError("Attempt to register a collection of services for different service types");
+ }
+ _compositionRoot.RegisterCollection(serviceDescriptors);
+ }
+
+ private void LogAddCollectionRegistration(IEnumerable serviceDescriptors)
+ {
+ serviceDescriptors = serviceDescriptors as ServiceDescriptor[] ?? serviceDescriptors.ToArray();
+ _logger.LogDebug("{Verb} {Lifetime} {RegistrationType} for {ServiceType}: {ImplementationType}",
+ "Adding",
+ serviceDescriptors.First().Lifetime.ToString().ToLowerInvariant(),
+ "collection registration",
+ serviceDescriptors.First().ServiceType.GetDetailedTypeName(),
+ string.Join(", ", serviceDescriptors.Select(sd => sd.GetImplementationTypeDescription())));
+ }
+
+ public IServiceScope BeginScope()
+ {
+ return _compositionRoot.BeginScope();
+ }
+
+ public IServiceProvider ServiceProvider => _compositionRoot.ServiceProvider;
+
+
+ private void LogDetails(string verb, string registrationType, ServiceDescriptor serviceDescriptor)
+ {
+ _logger.LogDebug("{Verb} {Lifetime} {RegistrationType} for {ServiceType}: {ImplementationType}",
+ verb,
+ serviceDescriptor.Lifetime.ToString().ToLowerInvariant(),
+ registrationType,
+ serviceDescriptor.ServiceType.GetDetailedTypeName(),
+ serviceDescriptor.GetImplementationTypeDescription());
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/abstractions/Backend.Fx/DependencyInjection/ServiceDescriptorEx.cs b/src/abstractions/Backend.Fx/DependencyInjection/ServiceDescriptorEx.cs
new file mode 100644
index 00000000..cfa36738
--- /dev/null
+++ b/src/abstractions/Backend.Fx/DependencyInjection/ServiceDescriptorEx.cs
@@ -0,0 +1,30 @@
+using Backend.Fx.Util;
+using JetBrains.Annotations;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Backend.Fx.DependencyInjection
+{
+ [PublicAPI]
+ public static class ServiceDescriptorEx
+ {
+ public static string GetImplementationTypeDescription(this ServiceDescriptor serviceDescriptor)
+ {
+ if (serviceDescriptor.ImplementationFactory != null)
+ {
+ return serviceDescriptor.ImplementationFactory.GetType().GetDetailedTypeName();
+ }
+
+ if (serviceDescriptor.ImplementationType != null)
+ {
+ return serviceDescriptor.ImplementationType.GetDetailedTypeName();
+ }
+
+ if (serviceDescriptor.ImplementationInstance != null)
+ {
+ return serviceDescriptor.ImplementationInstance.GetType().GetDetailedTypeName();
+ }
+
+ return "Unknown";
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/abstractions/Backend.Fx/Domain/IAggregateRoot.cs b/src/abstractions/Backend.Fx/Domain/IAggregateRoot.cs
new file mode 100644
index 00000000..11e7725a
--- /dev/null
+++ b/src/abstractions/Backend.Fx/Domain/IAggregateRoot.cs
@@ -0,0 +1,18 @@
+using System;
+
+namespace Backend.Fx.Domain
+{
+ ///
+ /// The root of an aggregate.
+ ///
+ public interface IAggregateRoot {}
+
+ ///
+ /// The root of an aggregate, identified by an id of type .
+ ///
+ public interface IAggregateRoot : IAggregateRoot
+ where TId : IEquatable
+ {
+ public TId Id { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/abstractions/Backend.Fx/Domain/Identified.cs b/src/abstractions/Backend.Fx/Domain/Identified.cs
new file mode 100644
index 00000000..1eee10d3
--- /dev/null
+++ b/src/abstractions/Backend.Fx/Domain/Identified.cs
@@ -0,0 +1,59 @@
+using System;
+using System.Diagnostics;
+using JetBrains.Annotations;
+
+namespace Backend.Fx.Domain
+{
+ [PublicAPI]
+ [DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")]
+ public abstract class Identified : IEquatable>
+ {
+ public TId Id { get; init; }
+
+ ///
+ /// DON'T USE!
+ /// This ctor is only here to allow O/R-Mappers to materialize an object coming from a persistent
+ /// store using reflection.
+ ///
+ protected Identified()
+ {
+ }
+
+ protected Identified(TId id)
+ {
+ Id = id;
+ }
+
+ [UsedImplicitly] public string DebuggerDisplay => $"{GetType().Name}[{Id}]";
+
+ public bool Equals(Identified other)
+ {
+ return other != null && Id.Equals(other.Id);
+ }
+
+ public override bool Equals(object obj)
+ {
+ var other = obj as Identified;
+ return other != null && Id.Equals(other.Id);
+ }
+
+ public override int GetHashCode()
+ {
+ return Id.GetHashCode();
+ }
+
+ public static bool operator ==(Identified left, Identified right)
+ {
+ if (ReferenceEquals(left, null) && ReferenceEquals(right, null)) return true;
+ if (ReferenceEquals(left, null) || ReferenceEquals(right, null)) return false;
+
+ return ReferenceEquals(left, right) || right.Id.Equals(left.Id);
+
+ }
+
+ public static bool operator !=(Identified left, Identified right)
+ {
+ return !(left == right);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/abstractions/Backend.Fx/BuildingBlocks/ValueObject.cs b/src/abstractions/Backend.Fx/Domain/ValueObject.cs
similarity index 68%
rename from src/abstractions/Backend.Fx/BuildingBlocks/ValueObject.cs
rename to src/abstractions/Backend.Fx/Domain/ValueObject.cs
index f4d5a5f3..50757d5a 100644
--- a/src/abstractions/Backend.Fx/BuildingBlocks/ValueObject.cs
+++ b/src/abstractions/Backend.Fx/Domain/ValueObject.cs
@@ -2,13 +2,13 @@
using System.Collections.Generic;
using System.Linq;
-namespace Backend.Fx.BuildingBlocks
+namespace Backend.Fx.Domain
{
///
/// An object that contains attributes but has no conceptual identity.
/// https://en.wikipedia.org/wiki/Domain-driven_design#Building_blocks
///
- public abstract class ValueObject
+ public abstract class ValueObject : IEquatable
{
///
/// When overriden in a derived class, returns all components of a value objects which constitute its identity.
@@ -16,6 +16,14 @@ public abstract class ValueObject
/// An ordered list of equality components.
protected abstract IEnumerable