diff --git a/.github/actions/dotnet/action.yaml b/.github/actions/dotnet/action.yaml index 9fc1ec7..ea150fb 100644 --- a/.github/actions/dotnet/action.yaml +++ b/.github/actions/dotnet/action.yaml @@ -11,9 +11,9 @@ inputs: runs: using: "composite" steps: - - uses: actions/setup-dotnet@v3 + - uses: actions/setup-dotnet@v4 with: - dotnet-version: 6.0.x + dotnet-version: 8.0.x - name: Restore dependencies working-directory: src shell: bash diff --git a/src/Wemogy.Cqrs.sln b/src/Wemogy.Cqrs.sln index a3d24c3..22f168c 100644 --- a/src/Wemogy.Cqrs.sln +++ b/src/Wemogy.Cqrs.sln @@ -25,113 +25,176 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wemogy.CQRS.Extensions.Azur EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wemogy.CQRS.UnitTests.AssemblyA", "core\Wemogy.CQRS.UnitTests.AssemblyA\Wemogy.CQRS.UnitTests.AssemblyA.csproj", "{3475DA24-0233-4B56-BE6A-3A5CA3B6010A}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "fastEndpoints", "fastEndpoints", "{2F0AC891-A63E-4467-ADE4-73251BB8AF59}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wemogy.CQRS.Extensions.FastEndpoints", "extensions\fastEndpoints\Wemogy.CQRS.Extensions.FastEndpoints\Wemogy.CQRS.Extensions.FastEndpoints.csproj", "{143DE5FA-114E-46D4-B9B9-11B57FB3546C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wemogy.CQRS.Extensions.FastEndpoints.UnitTests", "extensions\fastEndpoints\Wemogy.CQRS.Extensions.FastEndpoints.UnitTests\Wemogy.CQRS.Extensions.FastEndpoints.UnitTests.csproj", "{4F113940-017F-4468-ACCD-AF3781B98BA1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp", "extensions\fastEndpoints\Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp\Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp.csproj", "{119E0731-0006-403D-B6EB-07917562FD81}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wemogy.CQRS.UnitTests.AssemblyB", "core\Wemogy.CQRS.UnitTests.AssemblyB\Wemogy.CQRS.UnitTests.AssemblyB.csproj", "{D7B4C2FD-50BB-4DEE-AC8C-4FCCC4685CD4}" +EndProject Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {AFAE7B9D-A464-408B-B3F7-40BCA0F5FA11}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AFAE7B9D-A464-408B-B3F7-40BCA0F5FA11}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AFAE7B9D-A464-408B-B3F7-40BCA0F5FA11}.Debug|x64.ActiveCfg = Debug|Any CPU - {AFAE7B9D-A464-408B-B3F7-40BCA0F5FA11}.Debug|x64.Build.0 = Debug|Any CPU - {AFAE7B9D-A464-408B-B3F7-40BCA0F5FA11}.Debug|x86.ActiveCfg = Debug|Any CPU - {AFAE7B9D-A464-408B-B3F7-40BCA0F5FA11}.Debug|x86.Build.0 = Debug|Any CPU - {AFAE7B9D-A464-408B-B3F7-40BCA0F5FA11}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AFAE7B9D-A464-408B-B3F7-40BCA0F5FA11}.Release|Any CPU.Build.0 = Release|Any CPU - {AFAE7B9D-A464-408B-B3F7-40BCA0F5FA11}.Release|x64.ActiveCfg = Release|Any CPU - {AFAE7B9D-A464-408B-B3F7-40BCA0F5FA11}.Release|x64.Build.0 = Release|Any CPU - {AFAE7B9D-A464-408B-B3F7-40BCA0F5FA11}.Release|x86.ActiveCfg = Release|Any CPU - {AFAE7B9D-A464-408B-B3F7-40BCA0F5FA11}.Release|x86.Build.0 = Release|Any CPU - {54845A61-CB13-4B26-82E2-45AFEC4836D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {54845A61-CB13-4B26-82E2-45AFEC4836D4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {54845A61-CB13-4B26-82E2-45AFEC4836D4}.Debug|x64.ActiveCfg = Debug|Any CPU - {54845A61-CB13-4B26-82E2-45AFEC4836D4}.Debug|x64.Build.0 = Debug|Any CPU - {54845A61-CB13-4B26-82E2-45AFEC4836D4}.Debug|x86.ActiveCfg = Debug|Any CPU - {54845A61-CB13-4B26-82E2-45AFEC4836D4}.Debug|x86.Build.0 = Debug|Any CPU - {54845A61-CB13-4B26-82E2-45AFEC4836D4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {54845A61-CB13-4B26-82E2-45AFEC4836D4}.Release|Any CPU.Build.0 = Release|Any CPU - {54845A61-CB13-4B26-82E2-45AFEC4836D4}.Release|x64.ActiveCfg = Release|Any CPU - {54845A61-CB13-4B26-82E2-45AFEC4836D4}.Release|x64.Build.0 = Release|Any CPU - {54845A61-CB13-4B26-82E2-45AFEC4836D4}.Release|x86.ActiveCfg = Release|Any CPU - {54845A61-CB13-4B26-82E2-45AFEC4836D4}.Release|x86.Build.0 = Release|Any CPU - {EE542E0A-21AE-4944-A355-25AC6C2D8652}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EE542E0A-21AE-4944-A355-25AC6C2D8652}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EE542E0A-21AE-4944-A355-25AC6C2D8652}.Debug|x64.ActiveCfg = Debug|Any CPU - {EE542E0A-21AE-4944-A355-25AC6C2D8652}.Debug|x64.Build.0 = Debug|Any CPU - {EE542E0A-21AE-4944-A355-25AC6C2D8652}.Debug|x86.ActiveCfg = Debug|Any CPU - {EE542E0A-21AE-4944-A355-25AC6C2D8652}.Debug|x86.Build.0 = Debug|Any CPU - {EE542E0A-21AE-4944-A355-25AC6C2D8652}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EE542E0A-21AE-4944-A355-25AC6C2D8652}.Release|Any CPU.Build.0 = Release|Any CPU - {EE542E0A-21AE-4944-A355-25AC6C2D8652}.Release|x64.ActiveCfg = Release|Any CPU - {EE542E0A-21AE-4944-A355-25AC6C2D8652}.Release|x64.Build.0 = Release|Any CPU - {EE542E0A-21AE-4944-A355-25AC6C2D8652}.Release|x86.ActiveCfg = Release|Any CPU - {EE542E0A-21AE-4944-A355-25AC6C2D8652}.Release|x86.Build.0 = Release|Any CPU - {AACF455B-C8CB-4033-BA3B-F0C87326835C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AACF455B-C8CB-4033-BA3B-F0C87326835C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AACF455B-C8CB-4033-BA3B-F0C87326835C}.Debug|x64.ActiveCfg = Debug|Any CPU - {AACF455B-C8CB-4033-BA3B-F0C87326835C}.Debug|x64.Build.0 = Debug|Any CPU - {AACF455B-C8CB-4033-BA3B-F0C87326835C}.Debug|x86.ActiveCfg = Debug|Any CPU - {AACF455B-C8CB-4033-BA3B-F0C87326835C}.Debug|x86.Build.0 = Debug|Any CPU - {AACF455B-C8CB-4033-BA3B-F0C87326835C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AACF455B-C8CB-4033-BA3B-F0C87326835C}.Release|Any CPU.Build.0 = Release|Any CPU - {AACF455B-C8CB-4033-BA3B-F0C87326835C}.Release|x64.ActiveCfg = Release|Any CPU - {AACF455B-C8CB-4033-BA3B-F0C87326835C}.Release|x64.Build.0 = Release|Any CPU - {AACF455B-C8CB-4033-BA3B-F0C87326835C}.Release|x86.ActiveCfg = Release|Any CPU - {AACF455B-C8CB-4033-BA3B-F0C87326835C}.Release|x86.Build.0 = Release|Any CPU - {72D23525-0519-4655-8BC5-E9158CD27E10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {72D23525-0519-4655-8BC5-E9158CD27E10}.Debug|Any CPU.Build.0 = Debug|Any CPU - {72D23525-0519-4655-8BC5-E9158CD27E10}.Debug|x64.ActiveCfg = Debug|Any CPU - {72D23525-0519-4655-8BC5-E9158CD27E10}.Debug|x64.Build.0 = Debug|Any CPU - {72D23525-0519-4655-8BC5-E9158CD27E10}.Debug|x86.ActiveCfg = Debug|Any CPU - {72D23525-0519-4655-8BC5-E9158CD27E10}.Debug|x86.Build.0 = Debug|Any CPU - {72D23525-0519-4655-8BC5-E9158CD27E10}.Release|Any CPU.ActiveCfg = Release|Any CPU - {72D23525-0519-4655-8BC5-E9158CD27E10}.Release|Any CPU.Build.0 = Release|Any CPU - {72D23525-0519-4655-8BC5-E9158CD27E10}.Release|x64.ActiveCfg = Release|Any CPU - {72D23525-0519-4655-8BC5-E9158CD27E10}.Release|x64.Build.0 = Release|Any CPU - {72D23525-0519-4655-8BC5-E9158CD27E10}.Release|x86.ActiveCfg = Release|Any CPU - {72D23525-0519-4655-8BC5-E9158CD27E10}.Release|x86.Build.0 = Release|Any CPU - {9F66FFF6-E802-4495-AAD8-5199FF7BACB2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9F66FFF6-E802-4495-AAD8-5199FF7BACB2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9F66FFF6-E802-4495-AAD8-5199FF7BACB2}.Debug|x64.ActiveCfg = Debug|Any CPU - {9F66FFF6-E802-4495-AAD8-5199FF7BACB2}.Debug|x64.Build.0 = Debug|Any CPU - {9F66FFF6-E802-4495-AAD8-5199FF7BACB2}.Debug|x86.ActiveCfg = Debug|Any CPU - {9F66FFF6-E802-4495-AAD8-5199FF7BACB2}.Debug|x86.Build.0 = Debug|Any CPU - {9F66FFF6-E802-4495-AAD8-5199FF7BACB2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9F66FFF6-E802-4495-AAD8-5199FF7BACB2}.Release|Any CPU.Build.0 = Release|Any CPU - {9F66FFF6-E802-4495-AAD8-5199FF7BACB2}.Release|x64.ActiveCfg = Release|Any CPU - {9F66FFF6-E802-4495-AAD8-5199FF7BACB2}.Release|x64.Build.0 = Release|Any CPU - {9F66FFF6-E802-4495-AAD8-5199FF7BACB2}.Release|x86.ActiveCfg = Release|Any CPU - {9F66FFF6-E802-4495-AAD8-5199FF7BACB2}.Release|x86.Build.0 = Release|Any CPU - {3475DA24-0233-4B56-BE6A-3A5CA3B6010A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3475DA24-0233-4B56-BE6A-3A5CA3B6010A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3475DA24-0233-4B56-BE6A-3A5CA3B6010A}.Debug|x64.ActiveCfg = Debug|Any CPU - {3475DA24-0233-4B56-BE6A-3A5CA3B6010A}.Debug|x64.Build.0 = Debug|Any CPU - {3475DA24-0233-4B56-BE6A-3A5CA3B6010A}.Debug|x86.ActiveCfg = Debug|Any CPU - {3475DA24-0233-4B56-BE6A-3A5CA3B6010A}.Debug|x86.Build.0 = Debug|Any CPU - {3475DA24-0233-4B56-BE6A-3A5CA3B6010A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3475DA24-0233-4B56-BE6A-3A5CA3B6010A}.Release|Any CPU.Build.0 = Release|Any CPU - {3475DA24-0233-4B56-BE6A-3A5CA3B6010A}.Release|x64.ActiveCfg = Release|Any CPU - {3475DA24-0233-4B56-BE6A-3A5CA3B6010A}.Release|x64.Build.0 = Release|Any CPU - {3475DA24-0233-4B56-BE6A-3A5CA3B6010A}.Release|x86.ActiveCfg = Release|Any CPU - {3475DA24-0233-4B56-BE6A-3A5CA3B6010A}.Release|x86.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {AFAE7B9D-A464-408B-B3F7-40BCA0F5FA11} = {E6804802-3416-4223-92CD-12B350ADCBF6} - {54845A61-CB13-4B26-82E2-45AFEC4836D4} = {E6804802-3416-4223-92CD-12B350ADCBF6} - {DC4B4FAD-C380-4F1E-9916-01D72C086112} = {A42B1288-6E03-4461-89AF-992C53858FCE} - {EE542E0A-21AE-4944-A355-25AC6C2D8652} = {DC4B4FAD-C380-4F1E-9916-01D72C086112} - {AACF455B-C8CB-4033-BA3B-F0C87326835C} = {DC4B4FAD-C380-4F1E-9916-01D72C086112} - {86B10300-5C83-4584-9A0D-4AD83B329F42} = {A42B1288-6E03-4461-89AF-992C53858FCE} - {72D23525-0519-4655-8BC5-E9158CD27E10} = {86B10300-5C83-4584-9A0D-4AD83B329F42} - {9F66FFF6-E802-4495-AAD8-5199FF7BACB2} = {86B10300-5C83-4584-9A0D-4AD83B329F42} - {3475DA24-0233-4B56-BE6A-3A5CA3B6010A} = {E6804802-3416-4223-92CD-12B350ADCBF6} - EndGlobalSection + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {AFAE7B9D-A464-408B-B3F7-40BCA0F5FA11}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AFAE7B9D-A464-408B-B3F7-40BCA0F5FA11}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AFAE7B9D-A464-408B-B3F7-40BCA0F5FA11}.Debug|x64.ActiveCfg = Debug|Any CPU + {AFAE7B9D-A464-408B-B3F7-40BCA0F5FA11}.Debug|x64.Build.0 = Debug|Any CPU + {AFAE7B9D-A464-408B-B3F7-40BCA0F5FA11}.Debug|x86.ActiveCfg = Debug|Any CPU + {AFAE7B9D-A464-408B-B3F7-40BCA0F5FA11}.Debug|x86.Build.0 = Debug|Any CPU + {AFAE7B9D-A464-408B-B3F7-40BCA0F5FA11}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AFAE7B9D-A464-408B-B3F7-40BCA0F5FA11}.Release|Any CPU.Build.0 = Release|Any CPU + {AFAE7B9D-A464-408B-B3F7-40BCA0F5FA11}.Release|x64.ActiveCfg = Release|Any CPU + {AFAE7B9D-A464-408B-B3F7-40BCA0F5FA11}.Release|x64.Build.0 = Release|Any CPU + {AFAE7B9D-A464-408B-B3F7-40BCA0F5FA11}.Release|x86.ActiveCfg = Release|Any CPU + {AFAE7B9D-A464-408B-B3F7-40BCA0F5FA11}.Release|x86.Build.0 = Release|Any CPU + {54845A61-CB13-4B26-82E2-45AFEC4836D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {54845A61-CB13-4B26-82E2-45AFEC4836D4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {54845A61-CB13-4B26-82E2-45AFEC4836D4}.Debug|x64.ActiveCfg = Debug|Any CPU + {54845A61-CB13-4B26-82E2-45AFEC4836D4}.Debug|x64.Build.0 = Debug|Any CPU + {54845A61-CB13-4B26-82E2-45AFEC4836D4}.Debug|x86.ActiveCfg = Debug|Any CPU + {54845A61-CB13-4B26-82E2-45AFEC4836D4}.Debug|x86.Build.0 = Debug|Any CPU + {54845A61-CB13-4B26-82E2-45AFEC4836D4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {54845A61-CB13-4B26-82E2-45AFEC4836D4}.Release|Any CPU.Build.0 = Release|Any CPU + {54845A61-CB13-4B26-82E2-45AFEC4836D4}.Release|x64.ActiveCfg = Release|Any CPU + {54845A61-CB13-4B26-82E2-45AFEC4836D4}.Release|x64.Build.0 = Release|Any CPU + {54845A61-CB13-4B26-82E2-45AFEC4836D4}.Release|x86.ActiveCfg = Release|Any CPU + {54845A61-CB13-4B26-82E2-45AFEC4836D4}.Release|x86.Build.0 = Release|Any CPU + {EE542E0A-21AE-4944-A355-25AC6C2D8652}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EE542E0A-21AE-4944-A355-25AC6C2D8652}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EE542E0A-21AE-4944-A355-25AC6C2D8652}.Debug|x64.ActiveCfg = Debug|Any CPU + {EE542E0A-21AE-4944-A355-25AC6C2D8652}.Debug|x64.Build.0 = Debug|Any CPU + {EE542E0A-21AE-4944-A355-25AC6C2D8652}.Debug|x86.ActiveCfg = Debug|Any CPU + {EE542E0A-21AE-4944-A355-25AC6C2D8652}.Debug|x86.Build.0 = Debug|Any CPU + {EE542E0A-21AE-4944-A355-25AC6C2D8652}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EE542E0A-21AE-4944-A355-25AC6C2D8652}.Release|Any CPU.Build.0 = Release|Any CPU + {EE542E0A-21AE-4944-A355-25AC6C2D8652}.Release|x64.ActiveCfg = Release|Any CPU + {EE542E0A-21AE-4944-A355-25AC6C2D8652}.Release|x64.Build.0 = Release|Any CPU + {EE542E0A-21AE-4944-A355-25AC6C2D8652}.Release|x86.ActiveCfg = Release|Any CPU + {EE542E0A-21AE-4944-A355-25AC6C2D8652}.Release|x86.Build.0 = Release|Any CPU + {AACF455B-C8CB-4033-BA3B-F0C87326835C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AACF455B-C8CB-4033-BA3B-F0C87326835C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AACF455B-C8CB-4033-BA3B-F0C87326835C}.Debug|x64.ActiveCfg = Debug|Any CPU + {AACF455B-C8CB-4033-BA3B-F0C87326835C}.Debug|x64.Build.0 = Debug|Any CPU + {AACF455B-C8CB-4033-BA3B-F0C87326835C}.Debug|x86.ActiveCfg = Debug|Any CPU + {AACF455B-C8CB-4033-BA3B-F0C87326835C}.Debug|x86.Build.0 = Debug|Any CPU + {AACF455B-C8CB-4033-BA3B-F0C87326835C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AACF455B-C8CB-4033-BA3B-F0C87326835C}.Release|Any CPU.Build.0 = Release|Any CPU + {AACF455B-C8CB-4033-BA3B-F0C87326835C}.Release|x64.ActiveCfg = Release|Any CPU + {AACF455B-C8CB-4033-BA3B-F0C87326835C}.Release|x64.Build.0 = Release|Any CPU + {AACF455B-C8CB-4033-BA3B-F0C87326835C}.Release|x86.ActiveCfg = Release|Any CPU + {AACF455B-C8CB-4033-BA3B-F0C87326835C}.Release|x86.Build.0 = Release|Any CPU + {72D23525-0519-4655-8BC5-E9158CD27E10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {72D23525-0519-4655-8BC5-E9158CD27E10}.Debug|Any CPU.Build.0 = Debug|Any CPU + {72D23525-0519-4655-8BC5-E9158CD27E10}.Debug|x64.ActiveCfg = Debug|Any CPU + {72D23525-0519-4655-8BC5-E9158CD27E10}.Debug|x64.Build.0 = Debug|Any CPU + {72D23525-0519-4655-8BC5-E9158CD27E10}.Debug|x86.ActiveCfg = Debug|Any CPU + {72D23525-0519-4655-8BC5-E9158CD27E10}.Debug|x86.Build.0 = Debug|Any CPU + {72D23525-0519-4655-8BC5-E9158CD27E10}.Release|Any CPU.ActiveCfg = Release|Any CPU + {72D23525-0519-4655-8BC5-E9158CD27E10}.Release|Any CPU.Build.0 = Release|Any CPU + {72D23525-0519-4655-8BC5-E9158CD27E10}.Release|x64.ActiveCfg = Release|Any CPU + {72D23525-0519-4655-8BC5-E9158CD27E10}.Release|x64.Build.0 = Release|Any CPU + {72D23525-0519-4655-8BC5-E9158CD27E10}.Release|x86.ActiveCfg = Release|Any CPU + {72D23525-0519-4655-8BC5-E9158CD27E10}.Release|x86.Build.0 = Release|Any CPU + {9F66FFF6-E802-4495-AAD8-5199FF7BACB2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9F66FFF6-E802-4495-AAD8-5199FF7BACB2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9F66FFF6-E802-4495-AAD8-5199FF7BACB2}.Debug|x64.ActiveCfg = Debug|Any CPU + {9F66FFF6-E802-4495-AAD8-5199FF7BACB2}.Debug|x64.Build.0 = Debug|Any CPU + {9F66FFF6-E802-4495-AAD8-5199FF7BACB2}.Debug|x86.ActiveCfg = Debug|Any CPU + {9F66FFF6-E802-4495-AAD8-5199FF7BACB2}.Debug|x86.Build.0 = Debug|Any CPU + {9F66FFF6-E802-4495-AAD8-5199FF7BACB2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9F66FFF6-E802-4495-AAD8-5199FF7BACB2}.Release|Any CPU.Build.0 = Release|Any CPU + {9F66FFF6-E802-4495-AAD8-5199FF7BACB2}.Release|x64.ActiveCfg = Release|Any CPU + {9F66FFF6-E802-4495-AAD8-5199FF7BACB2}.Release|x64.Build.0 = Release|Any CPU + {9F66FFF6-E802-4495-AAD8-5199FF7BACB2}.Release|x86.ActiveCfg = Release|Any CPU + {9F66FFF6-E802-4495-AAD8-5199FF7BACB2}.Release|x86.Build.0 = Release|Any CPU + {3475DA24-0233-4B56-BE6A-3A5CA3B6010A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3475DA24-0233-4B56-BE6A-3A5CA3B6010A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3475DA24-0233-4B56-BE6A-3A5CA3B6010A}.Debug|x64.ActiveCfg = Debug|Any CPU + {3475DA24-0233-4B56-BE6A-3A5CA3B6010A}.Debug|x64.Build.0 = Debug|Any CPU + {3475DA24-0233-4B56-BE6A-3A5CA3B6010A}.Debug|x86.ActiveCfg = Debug|Any CPU + {3475DA24-0233-4B56-BE6A-3A5CA3B6010A}.Debug|x86.Build.0 = Debug|Any CPU + {3475DA24-0233-4B56-BE6A-3A5CA3B6010A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3475DA24-0233-4B56-BE6A-3A5CA3B6010A}.Release|Any CPU.Build.0 = Release|Any CPU + {3475DA24-0233-4B56-BE6A-3A5CA3B6010A}.Release|x64.ActiveCfg = Release|Any CPU + {3475DA24-0233-4B56-BE6A-3A5CA3B6010A}.Release|x64.Build.0 = Release|Any CPU + {3475DA24-0233-4B56-BE6A-3A5CA3B6010A}.Release|x86.ActiveCfg = Release|Any CPU + {3475DA24-0233-4B56-BE6A-3A5CA3B6010A}.Release|x86.Build.0 = Release|Any CPU + {143DE5FA-114E-46D4-B9B9-11B57FB3546C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {143DE5FA-114E-46D4-B9B9-11B57FB3546C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {143DE5FA-114E-46D4-B9B9-11B57FB3546C}.Debug|x64.ActiveCfg = Debug|Any CPU + {143DE5FA-114E-46D4-B9B9-11B57FB3546C}.Debug|x64.Build.0 = Debug|Any CPU + {143DE5FA-114E-46D4-B9B9-11B57FB3546C}.Debug|x86.ActiveCfg = Debug|Any CPU + {143DE5FA-114E-46D4-B9B9-11B57FB3546C}.Debug|x86.Build.0 = Debug|Any CPU + {143DE5FA-114E-46D4-B9B9-11B57FB3546C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {143DE5FA-114E-46D4-B9B9-11B57FB3546C}.Release|Any CPU.Build.0 = Release|Any CPU + {143DE5FA-114E-46D4-B9B9-11B57FB3546C}.Release|x64.ActiveCfg = Release|Any CPU + {143DE5FA-114E-46D4-B9B9-11B57FB3546C}.Release|x64.Build.0 = Release|Any CPU + {143DE5FA-114E-46D4-B9B9-11B57FB3546C}.Release|x86.ActiveCfg = Release|Any CPU + {143DE5FA-114E-46D4-B9B9-11B57FB3546C}.Release|x86.Build.0 = Release|Any CPU + {4F113940-017F-4468-ACCD-AF3781B98BA1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4F113940-017F-4468-ACCD-AF3781B98BA1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4F113940-017F-4468-ACCD-AF3781B98BA1}.Debug|x64.ActiveCfg = Debug|Any CPU + {4F113940-017F-4468-ACCD-AF3781B98BA1}.Debug|x64.Build.0 = Debug|Any CPU + {4F113940-017F-4468-ACCD-AF3781B98BA1}.Debug|x86.ActiveCfg = Debug|Any CPU + {4F113940-017F-4468-ACCD-AF3781B98BA1}.Debug|x86.Build.0 = Debug|Any CPU + {4F113940-017F-4468-ACCD-AF3781B98BA1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4F113940-017F-4468-ACCD-AF3781B98BA1}.Release|Any CPU.Build.0 = Release|Any CPU + {4F113940-017F-4468-ACCD-AF3781B98BA1}.Release|x64.ActiveCfg = Release|Any CPU + {4F113940-017F-4468-ACCD-AF3781B98BA1}.Release|x64.Build.0 = Release|Any CPU + {4F113940-017F-4468-ACCD-AF3781B98BA1}.Release|x86.ActiveCfg = Release|Any CPU + {4F113940-017F-4468-ACCD-AF3781B98BA1}.Release|x86.Build.0 = Release|Any CPU + {119E0731-0006-403D-B6EB-07917562FD81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {119E0731-0006-403D-B6EB-07917562FD81}.Debug|Any CPU.Build.0 = Debug|Any CPU + {119E0731-0006-403D-B6EB-07917562FD81}.Debug|x64.ActiveCfg = Debug|Any CPU + {119E0731-0006-403D-B6EB-07917562FD81}.Debug|x64.Build.0 = Debug|Any CPU + {119E0731-0006-403D-B6EB-07917562FD81}.Debug|x86.ActiveCfg = Debug|Any CPU + {119E0731-0006-403D-B6EB-07917562FD81}.Debug|x86.Build.0 = Debug|Any CPU + {119E0731-0006-403D-B6EB-07917562FD81}.Release|Any CPU.ActiveCfg = Release|Any CPU + {119E0731-0006-403D-B6EB-07917562FD81}.Release|Any CPU.Build.0 = Release|Any CPU + {119E0731-0006-403D-B6EB-07917562FD81}.Release|x64.ActiveCfg = Release|Any CPU + {119E0731-0006-403D-B6EB-07917562FD81}.Release|x64.Build.0 = Release|Any CPU + {119E0731-0006-403D-B6EB-07917562FD81}.Release|x86.ActiveCfg = Release|Any CPU + {119E0731-0006-403D-B6EB-07917562FD81}.Release|x86.Build.0 = Release|Any CPU + {D7B4C2FD-50BB-4DEE-AC8C-4FCCC4685CD4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D7B4C2FD-50BB-4DEE-AC8C-4FCCC4685CD4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D7B4C2FD-50BB-4DEE-AC8C-4FCCC4685CD4}.Debug|x64.ActiveCfg = Debug|Any CPU + {D7B4C2FD-50BB-4DEE-AC8C-4FCCC4685CD4}.Debug|x64.Build.0 = Debug|Any CPU + {D7B4C2FD-50BB-4DEE-AC8C-4FCCC4685CD4}.Debug|x86.ActiveCfg = Debug|Any CPU + {D7B4C2FD-50BB-4DEE-AC8C-4FCCC4685CD4}.Debug|x86.Build.0 = Debug|Any CPU + {D7B4C2FD-50BB-4DEE-AC8C-4FCCC4685CD4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D7B4C2FD-50BB-4DEE-AC8C-4FCCC4685CD4}.Release|Any CPU.Build.0 = Release|Any CPU + {D7B4C2FD-50BB-4DEE-AC8C-4FCCC4685CD4}.Release|x64.ActiveCfg = Release|Any CPU + {D7B4C2FD-50BB-4DEE-AC8C-4FCCC4685CD4}.Release|x64.Build.0 = Release|Any CPU + {D7B4C2FD-50BB-4DEE-AC8C-4FCCC4685CD4}.Release|x86.ActiveCfg = Release|Any CPU + {D7B4C2FD-50BB-4DEE-AC8C-4FCCC4685CD4}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {AFAE7B9D-A464-408B-B3F7-40BCA0F5FA11} = {E6804802-3416-4223-92CD-12B350ADCBF6} + {54845A61-CB13-4B26-82E2-45AFEC4836D4} = {E6804802-3416-4223-92CD-12B350ADCBF6} + {DC4B4FAD-C380-4F1E-9916-01D72C086112} = {A42B1288-6E03-4461-89AF-992C53858FCE} + {EE542E0A-21AE-4944-A355-25AC6C2D8652} = {DC4B4FAD-C380-4F1E-9916-01D72C086112} + {AACF455B-C8CB-4033-BA3B-F0C87326835C} = {DC4B4FAD-C380-4F1E-9916-01D72C086112} + {86B10300-5C83-4584-9A0D-4AD83B329F42} = {A42B1288-6E03-4461-89AF-992C53858FCE} + {72D23525-0519-4655-8BC5-E9158CD27E10} = {86B10300-5C83-4584-9A0D-4AD83B329F42} + {9F66FFF6-E802-4495-AAD8-5199FF7BACB2} = {86B10300-5C83-4584-9A0D-4AD83B329F42} + {3475DA24-0233-4B56-BE6A-3A5CA3B6010A} = {E6804802-3416-4223-92CD-12B350ADCBF6} + {2F0AC891-A63E-4467-ADE4-73251BB8AF59} = {A42B1288-6E03-4461-89AF-992C53858FCE} + {143DE5FA-114E-46D4-B9B9-11B57FB3546C} = {2F0AC891-A63E-4467-ADE4-73251BB8AF59} + {4F113940-017F-4468-ACCD-AF3781B98BA1} = {2F0AC891-A63E-4467-ADE4-73251BB8AF59} + {119E0731-0006-403D-B6EB-07917562FD81} = {2F0AC891-A63E-4467-ADE4-73251BB8AF59} + {D7B4C2FD-50BB-4DEE-AC8C-4FCCC4685CD4} = {E6804802-3416-4223-92CD-12B350ADCBF6} + EndGlobalSection EndGlobal diff --git a/src/core/Wemogy.CQRS.UnitTests.AssemblyA/Commands/PrintHelloAssemblyA/PrintHelloAssemblyACommand.cs b/src/core/Wemogy.CQRS.UnitTests.AssemblyA/Commands/PrintHelloAssemblyA/PrintHelloAssemblyACommand.cs new file mode 100644 index 0000000..05c683c --- /dev/null +++ b/src/core/Wemogy.CQRS.UnitTests.AssemblyA/Commands/PrintHelloAssemblyA/PrintHelloAssemblyACommand.cs @@ -0,0 +1,7 @@ +using Wemogy.CQRS.Commands.Abstractions; + +namespace Wemogy.CQRS.UnitTests.AssemblyA.Commands.PrintHelloAssemblyA; + +public class PrintHelloAssemblyACommand : ICommand +{ +} diff --git a/src/core/Wemogy.CQRS.UnitTests.AssemblyA/Commands/PrintHelloAssemblyA/PrintHelloAssemblyACommandHandler.cs b/src/core/Wemogy.CQRS.UnitTests.AssemblyA/Commands/PrintHelloAssemblyA/PrintHelloAssemblyACommandHandler.cs new file mode 100644 index 0000000..0119bcd --- /dev/null +++ b/src/core/Wemogy.CQRS.UnitTests.AssemblyA/Commands/PrintHelloAssemblyA/PrintHelloAssemblyACommandHandler.cs @@ -0,0 +1,17 @@ +using System; +using System.Threading.Tasks; +using Wemogy.CQRS.Commands.Abstractions; + +namespace Wemogy.CQRS.UnitTests.AssemblyA.Commands.PrintHelloAssemblyA; + +public class PrintHelloAssemblyACommandHandler : ICommandHandler +{ + public static int CallCount { get; private set; } + + public Task HandleAsync(PrintHelloAssemblyACommand command) + { + Console.WriteLine("Hello from Assembly A!"); + CallCount++; + return Task.CompletedTask; + } +} diff --git a/src/core/Wemogy.CQRS.UnitTests.AssemblyA/DependencyInjection.cs b/src/core/Wemogy.CQRS.UnitTests.AssemblyA/DependencyInjection.cs new file mode 100644 index 0000000..2f8a68a --- /dev/null +++ b/src/core/Wemogy.CQRS.UnitTests.AssemblyA/DependencyInjection.cs @@ -0,0 +1,11 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace Wemogy.CQRS.UnitTests.AssemblyA; + +public static class DependencyInjection +{ + public static void AddAssemblyA(this IServiceCollection services) + { + services.AddCQRS(); + } +} diff --git a/src/core/Wemogy.CQRS.UnitTests.AssemblyB/Commands/PrintHelloAssemblyB/PrintHelloAssemblyBCommand.cs b/src/core/Wemogy.CQRS.UnitTests.AssemblyB/Commands/PrintHelloAssemblyB/PrintHelloAssemblyBCommand.cs new file mode 100644 index 0000000..8bcb3b1 --- /dev/null +++ b/src/core/Wemogy.CQRS.UnitTests.AssemblyB/Commands/PrintHelloAssemblyB/PrintHelloAssemblyBCommand.cs @@ -0,0 +1,7 @@ +using Wemogy.CQRS.Commands.Abstractions; + +namespace Wemogy.CQRS.UnitTests.AssemblyB.Commands.PrintHelloAssemblyB; + +public class PrintHelloAssemblyBCommand : ICommand +{ +} diff --git a/src/core/Wemogy.CQRS.UnitTests.AssemblyB/Commands/PrintHelloAssemblyB/PrintHelloAssemblyBCommandHandler.cs b/src/core/Wemogy.CQRS.UnitTests.AssemblyB/Commands/PrintHelloAssemblyB/PrintHelloAssemblyBCommandHandler.cs new file mode 100644 index 0000000..daad8fa --- /dev/null +++ b/src/core/Wemogy.CQRS.UnitTests.AssemblyB/Commands/PrintHelloAssemblyB/PrintHelloAssemblyBCommandHandler.cs @@ -0,0 +1,17 @@ +using System; +using System.Threading.Tasks; +using Wemogy.CQRS.Commands.Abstractions; + +namespace Wemogy.CQRS.UnitTests.AssemblyB.Commands.PrintHelloAssemblyB; + +public class PrintHelloAssemblyBCommandHandler : ICommandHandler +{ + public static int CallCount { get; private set; } + + public Task HandleAsync(PrintHelloAssemblyBCommand command) + { + Console.WriteLine("Hello from Assembly B!"); + CallCount++; + return Task.CompletedTask; + } +} diff --git a/src/core/Wemogy.CQRS.UnitTests.AssemblyB/DependencyInjection.cs b/src/core/Wemogy.CQRS.UnitTests.AssemblyB/DependencyInjection.cs new file mode 100644 index 0000000..62ccf4f --- /dev/null +++ b/src/core/Wemogy.CQRS.UnitTests.AssemblyB/DependencyInjection.cs @@ -0,0 +1,11 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace Wemogy.CQRS.UnitTests.AssemblyB; + +public static class DependencyInjection +{ + public static void AddAssemblyB(this IServiceCollection services) + { + services.AddCQRS(); + } +} diff --git a/src/core/Wemogy.CQRS.UnitTests.AssemblyB/Wemogy.CQRS.UnitTests.AssemblyB.csproj b/src/core/Wemogy.CQRS.UnitTests.AssemblyB/Wemogy.CQRS.UnitTests.AssemblyB.csproj new file mode 100644 index 0000000..f72c608 --- /dev/null +++ b/src/core/Wemogy.CQRS.UnitTests.AssemblyB/Wemogy.CQRS.UnitTests.AssemblyB.csproj @@ -0,0 +1,11 @@ + + + + net6.0 + + + + + + + diff --git a/src/core/Wemogy.CQRS.UnitTests/DependencyInjectionTests.cs b/src/core/Wemogy.CQRS.UnitTests/DependencyInjectionTests.cs new file mode 100644 index 0000000..97c0aec --- /dev/null +++ b/src/core/Wemogy.CQRS.UnitTests/DependencyInjectionTests.cs @@ -0,0 +1,43 @@ +using System.Threading.Tasks; +using FluentAssertions; +using Microsoft.Extensions.DependencyInjection; +using Wemogy.CQRS.Commands.Abstractions; +using Wemogy.CQRS.UnitTests.AssemblyA; +using Wemogy.CQRS.UnitTests.AssemblyA.Commands.PrintHelloAssemblyA; +using Wemogy.CQRS.UnitTests.AssemblyB; +using Wemogy.CQRS.UnitTests.AssemblyB.Commands.PrintHelloAssemblyB; +using Wemogy.CQRS.UnitTests.TestApplication.Commands.TrackUserLogin; +using Xunit; + +namespace Wemogy.CQRS.UnitTests; + +public class DependencyInjectionTests +{ + [Fact] + public async Task CallingAddCQRSMultipleTimesInDifferentAssembliesShouldWork() + { + // Arrange + var serviceCollection = new ServiceCollection(); + serviceCollection.AddCQRS(); + serviceCollection.AddAssemblyA(); + serviceCollection.AddAssemblyB(); + var serviceProvider = serviceCollection.BuildServiceProvider(); + var commands = serviceProvider.GetRequiredService(); + var helloAssemblyACommand = new PrintHelloAssemblyACommand(); + var helloAssemblyBCommand = new PrintHelloAssemblyBCommand(); + var trackUserLoginCommand = new TrackUserLoginCommand("test-user-id"); + + // Act + var trackUserLoginCommandException = await Record.ExceptionAsync(() => commands.RunAsync(trackUserLoginCommand)); + var helloAssemblyACommandException = await Record.ExceptionAsync(() => commands.RunAsync(helloAssemblyACommand)); + var helloAssemblyBCommandException = await Record.ExceptionAsync(() => commands.RunAsync(helloAssemblyBCommand)); + + // Assert + TrackUserLoginCommandHandler.CallCount.Should().Be(1); + trackUserLoginCommandException.Should().BeNull(); + PrintHelloAssemblyACommandHandler.CallCount.Should().Be(1); + helloAssemblyACommandException.Should().BeNull(); + PrintHelloAssemblyBCommandHandler.CallCount.Should().Be(1); + helloAssemblyBCommandException.Should().BeNull(); + } +} diff --git a/src/core/Wemogy.CQRS.UnitTests/Extensions/ServiceCollectionExtensionsTests.cs b/src/core/Wemogy.CQRS.UnitTests/Extensions/ServiceCollectionExtensionsTests.cs new file mode 100644 index 0000000..678f74d --- /dev/null +++ b/src/core/Wemogy.CQRS.UnitTests/Extensions/ServiceCollectionExtensionsTests.cs @@ -0,0 +1,96 @@ +using System.Linq; +using System.Reflection; +using FluentAssertions; +using Microsoft.Extensions.DependencyInjection; +using Wemogy.CQRS.Commands.Abstractions; +using Wemogy.CQRS.Extensions; +using Wemogy.CQRS.UnitTests.TestApplication.Commands.CreateUser; +using Wemogy.CQRS.UnitTests.TestApplication.Commands.KitchenSinkWithoutResult; +using Wemogy.CQRS.UnitTests.TestApplication.Commands.KitchenSinkWithResult; +using Wemogy.CQRS.UnitTests.TestApplication.Commands.RecalculateTotalUserCount; +using Xunit; + +namespace Wemogy.CQRS.UnitTests.Extensions; + +public class ServiceCollectionExtensionsTests +{ + [Fact] + public void AddImplementationsOfInterfaceScoped_ShouldWork() + { + // Arrange + var serviceCollection = new ServiceCollection(); + var assemblies = new[] + { + Assembly.GetExecutingAssembly(), + Assembly.GetCallingAssembly() + }.ToList(); + + // Act + serviceCollection.AddImplementationsOfGenericInterfaceScoped( + typeof(ICommandPreProcessor<>), + assemblies); + + // Assert + var serviceProvider = serviceCollection.BuildServiceProvider(); + serviceProvider.GetServices>() + .Should().HaveCount(2); + serviceProvider.GetServices>() + .Should().HaveCount(1); + serviceProvider.GetServices>() + .Should().HaveCount(0); + } + + [Fact] + public void AddImplementationsOfInterfaceScoped_ShouldAddOnlyOnceIfCalledManyTimes() + { + // Arrange + var serviceCollection = new ServiceCollection(); + var assemblies = new[] + { + Assembly.GetExecutingAssembly(), + Assembly.GetCallingAssembly() + }.ToList(); + + // Act + serviceCollection.AddImplementationsOfGenericInterfaceScoped( + typeof(ICommandPreProcessor<>), + assemblies); + serviceCollection.AddImplementationsOfGenericInterfaceScoped( + typeof(ICommandPreProcessor<>), + assemblies); + serviceCollection.AddImplementationsOfGenericInterfaceScoped( + typeof(ICommandPreProcessor<>), + assemblies); + + // Assert + var serviceProvider = serviceCollection.BuildServiceProvider(); + serviceProvider.GetServices>() + .Should().HaveCount(2); + serviceProvider.GetServices>() + .Should().HaveCount(1); + serviceProvider.GetServices>() + .Should().HaveCount(0); + } + + [Fact] + public void AddImplementationsOfInterfaceScoped_ShouldWorkForInterfaceWithTwoGenerics() + { + // Arrange + var serviceCollection = new ServiceCollection(); + var assemblies = new[] + { + Assembly.GetExecutingAssembly(), + Assembly.GetCallingAssembly() + }.ToList(); + + // Act + serviceCollection.AddImplementationsOfGenericInterfaceScoped( + typeof(ICommandHandler<,>), + assemblies); + + // Assert + var serviceProvider = serviceCollection.BuildServiceProvider(); + serviceProvider.GetServices>() + .Should().ContainSingle(); + } +} diff --git a/src/core/Wemogy.CQRS.UnitTests/Queries/Mediators/QueriesMediatorTests.cs b/src/core/Wemogy.CQRS.UnitTests/Queries/Mediators/QueriesMediatorTests.cs index aa13543..6e3e062 100644 --- a/src/core/Wemogy.CQRS.UnitTests/Queries/Mediators/QueriesMediatorTests.cs +++ b/src/core/Wemogy.CQRS.UnitTests/Queries/Mediators/QueriesMediatorTests.cs @@ -4,6 +4,7 @@ using Microsoft.Extensions.DependencyInjection; using Wemogy.Core.Errors.Exceptions; using Wemogy.CQRS.Queries.Abstractions; +using Wemogy.CQRS.UnitTests.AssemblyA; using Wemogy.CQRS.UnitTests.AssemblyA.Queries; using Wemogy.CQRS.UnitTests.TestApplication; using Wemogy.CQRS.UnitTests.TestApplication.Queries.GetUser; @@ -75,7 +76,8 @@ public async Task QueryAsync_ShouldSupportMultipleAssemblyDefinitions() { // Arrange var serviceCollection = new ServiceCollection(); - serviceCollection.AddTestApplication(); + serviceCollection.AddCQRS(); + serviceCollection.AddAssemblyA(); var serviceProvider = serviceCollection.BuildServiceProvider(); var queries = serviceProvider.GetRequiredService(); var getUserQuery = new GetUserActivityQuery(); diff --git a/src/core/Wemogy.CQRS.UnitTests/SetupTests.cs b/src/core/Wemogy.CQRS.UnitTests/SetupTests.cs index e11ef9d..b52a78d 100644 --- a/src/core/Wemogy.CQRS.UnitTests/SetupTests.cs +++ b/src/core/Wemogy.CQRS.UnitTests/SetupTests.cs @@ -28,10 +28,10 @@ public void SetupShould_WorkCorrectly() var serviceProvider = serviceCollection.BuildServiceProvider(); // Commands - serviceProvider.GetRequiredService>>().Should().HaveCount(1); - serviceProvider.GetRequiredService>>().Should().HaveCount(1); - serviceProvider.GetRequiredService>>().Should().HaveCount(1); - serviceProvider.GetRequiredService>>().Should() + serviceProvider.GetRequiredService>>().Should().HaveCount(1); + serviceProvider.GetRequiredService>>().Should().HaveCount(1); + serviceProvider.GetRequiredService>>().Should().HaveCount(1); + serviceProvider.GetRequiredService>>().Should() .HaveCount(1); serviceProvider.GetService>().Should().NotBeNull(); serviceProvider.GetService>().Should().NotBeNull(); @@ -40,8 +40,8 @@ public void SetupShould_WorkCorrectly() serviceProvider.GetService().Should().NotBeNull(); // Queries - serviceProvider.GetRequiredService>>().Should().HaveCount(1); - serviceProvider.GetRequiredService>>().Should().HaveCount(1); + serviceProvider.GetRequiredService>>().Should().HaveCount(1); + serviceProvider.GetRequiredService>>().Should().HaveCount(1); serviceProvider.GetService().Should().NotBeNull(); } } diff --git a/src/core/Wemogy.CQRS.UnitTests/TestApplication/Commands/KitchenSinkWithResult/KitchenSinkWithResultCommand.cs b/src/core/Wemogy.CQRS.UnitTests/TestApplication/Commands/KitchenSinkWithResult/KitchenSinkWithResultCommand.cs new file mode 100644 index 0000000..b7f637f --- /dev/null +++ b/src/core/Wemogy.CQRS.UnitTests/TestApplication/Commands/KitchenSinkWithResult/KitchenSinkWithResultCommand.cs @@ -0,0 +1,7 @@ +using Wemogy.CQRS.Commands.Abstractions; + +namespace Wemogy.CQRS.UnitTests.TestApplication.Commands.KitchenSinkWithResult; + +public class KitchenSinkWithResultCommand : ICommand +{ +} diff --git a/src/core/Wemogy.CQRS.UnitTests/TestApplication/Commands/KitchenSinkWithResult/KitchenSinkWithResultCommandHandler.cs b/src/core/Wemogy.CQRS.UnitTests/TestApplication/Commands/KitchenSinkWithResult/KitchenSinkWithResultCommandHandler.cs new file mode 100644 index 0000000..4bc1c38 --- /dev/null +++ b/src/core/Wemogy.CQRS.UnitTests/TestApplication/Commands/KitchenSinkWithResult/KitchenSinkWithResultCommandHandler.cs @@ -0,0 +1,15 @@ +using System.Threading.Tasks; +using Wemogy.CQRS.Commands.Abstractions; + +namespace Wemogy.CQRS.UnitTests.TestApplication.Commands.KitchenSinkWithResult; + +public class KitchenSinkWithResultCommandHandler : ICommandHandler +{ + public int CalledCount { get; private set; } + + public Task HandleAsync(KitchenSinkWithResultCommand command) + { + CalledCount++; + return Task.FromResult(42); + } +} diff --git a/src/core/Wemogy.CQRS.UnitTests/TestApplication/Commands/KitchenSinkWithoutResult/KitchenSinkWithoutResultCommand.cs b/src/core/Wemogy.CQRS.UnitTests/TestApplication/Commands/KitchenSinkWithoutResult/KitchenSinkWithoutResultCommand.cs new file mode 100644 index 0000000..f7241ec --- /dev/null +++ b/src/core/Wemogy.CQRS.UnitTests/TestApplication/Commands/KitchenSinkWithoutResult/KitchenSinkWithoutResultCommand.cs @@ -0,0 +1,7 @@ +using Wemogy.CQRS.Commands.Abstractions; + +namespace Wemogy.CQRS.UnitTests.TestApplication.Commands.KitchenSinkWithoutResult; + +public class KitchenSinkWithoutResultCommand : ICommand +{ +} diff --git a/src/core/Wemogy.CQRS.UnitTests/TestApplication/Commands/KitchenSinkWithoutResult/KitchenSinkWithoutResultCommandHandler.cs b/src/core/Wemogy.CQRS.UnitTests/TestApplication/Commands/KitchenSinkWithoutResult/KitchenSinkWithoutResultCommandHandler.cs new file mode 100644 index 0000000..61ca8f8 --- /dev/null +++ b/src/core/Wemogy.CQRS.UnitTests/TestApplication/Commands/KitchenSinkWithoutResult/KitchenSinkWithoutResultCommandHandler.cs @@ -0,0 +1,15 @@ +using System.Threading.Tasks; +using Wemogy.CQRS.Commands.Abstractions; + +namespace Wemogy.CQRS.UnitTests.TestApplication.Commands.KitchenSinkWithoutResult; + +public class KitchenSinkWithoutResultCommandHandler : ICommandHandler +{ + public int CalledCount { get; private set; } + + public Task HandleAsync(KitchenSinkWithoutResultCommand command) + { + CalledCount++; + return Task.CompletedTask; + } +} diff --git a/src/core/Wemogy.CQRS.UnitTests/TestApplication/Commands/KitchenSinkWithoutResult/KitchenSinkWithoutResultCommandPreProcessor1.cs b/src/core/Wemogy.CQRS.UnitTests/TestApplication/Commands/KitchenSinkWithoutResult/KitchenSinkWithoutResultCommandPreProcessor1.cs new file mode 100644 index 0000000..29ee0a4 --- /dev/null +++ b/src/core/Wemogy.CQRS.UnitTests/TestApplication/Commands/KitchenSinkWithoutResult/KitchenSinkWithoutResultCommandPreProcessor1.cs @@ -0,0 +1,14 @@ +using System.Threading.Tasks; +using Wemogy.CQRS.Commands.Abstractions; + +namespace Wemogy.CQRS.UnitTests.TestApplication.Commands.KitchenSinkWithoutResult; + +public class KitchenSinkWithoutResultCommandPreProcessor1 : ICommandPreProcessor +{ + public int CalledCount { get; private set; } + public Task ProcessAsync(KitchenSinkWithoutResultCommand command) + { + CalledCount++; + return Task.CompletedTask; + } +} diff --git a/src/core/Wemogy.CQRS.UnitTests/TestApplication/Commands/KitchenSinkWithoutResult/KitchenSinkWithoutResultCommandPreProcessor2.cs b/src/core/Wemogy.CQRS.UnitTests/TestApplication/Commands/KitchenSinkWithoutResult/KitchenSinkWithoutResultCommandPreProcessor2.cs new file mode 100644 index 0000000..e82ea2d --- /dev/null +++ b/src/core/Wemogy.CQRS.UnitTests/TestApplication/Commands/KitchenSinkWithoutResult/KitchenSinkWithoutResultCommandPreProcessor2.cs @@ -0,0 +1,14 @@ +using System.Threading.Tasks; +using Wemogy.CQRS.Commands.Abstractions; + +namespace Wemogy.CQRS.UnitTests.TestApplication.Commands.KitchenSinkWithoutResult; + +public class KitchenSinkWithoutResultCommandPreProcessor2 : ICommandPreProcessor +{ + public int CalledCount { get; private set; } + public Task ProcessAsync(KitchenSinkWithoutResultCommand command) + { + CalledCount++; + return Task.CompletedTask; + } +} diff --git a/src/core/Wemogy.CQRS.UnitTests/TestApplication/Commands/TrackUserLogin/TrackUserLoginCommandHandler.cs b/src/core/Wemogy.CQRS.UnitTests/TestApplication/Commands/TrackUserLogin/TrackUserLoginCommandHandler.cs index d52f273..0bebf31 100644 --- a/src/core/Wemogy.CQRS.UnitTests/TestApplication/Commands/TrackUserLogin/TrackUserLoginCommandHandler.cs +++ b/src/core/Wemogy.CQRS.UnitTests/TestApplication/Commands/TrackUserLogin/TrackUserLoginCommandHandler.cs @@ -5,8 +5,11 @@ namespace Wemogy.CQRS.UnitTests.TestApplication.Commands.TrackUserLogin; public class TrackUserLoginCommandHandler : ICommandHandler { + public static int CallCount { get; private set; } + public Task HandleAsync(TrackUserLoginCommand command) { + CallCount++; return Task.CompletedTask; } } diff --git a/src/core/Wemogy.CQRS.UnitTests/Wemogy.CQRS.UnitTests.csproj b/src/core/Wemogy.CQRS.UnitTests/Wemogy.CQRS.UnitTests.csproj index 5f41eef..f6bd44a 100644 --- a/src/core/Wemogy.CQRS.UnitTests/Wemogy.CQRS.UnitTests.csproj +++ b/src/core/Wemogy.CQRS.UnitTests/Wemogy.CQRS.UnitTests.csproj @@ -24,5 +24,6 @@ + diff --git a/src/core/Wemogy.CQRS/Abstractions/ICommandQueryDependencyResolver.cs b/src/core/Wemogy.CQRS/Abstractions/ICommandQueryDependencyResolver.cs new file mode 100644 index 0000000..2e0a1ad --- /dev/null +++ b/src/core/Wemogy.CQRS/Abstractions/ICommandQueryDependencyResolver.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; +using Wemogy.CQRS.Common.ValueObjects; + +namespace Wemogy.CQRS.Abstractions; + +public interface ICommandQueryDependencyResolver +{ + List ResolveDependencies(); +} diff --git a/src/core/Wemogy.CQRS/Abstractions/IRemoteCommandRunner`1.cs b/src/core/Wemogy.CQRS/Abstractions/IRemoteCommandRunner`1.cs new file mode 100644 index 0000000..79b676e --- /dev/null +++ b/src/core/Wemogy.CQRS/Abstractions/IRemoteCommandRunner`1.cs @@ -0,0 +1,11 @@ +using System.Threading.Tasks; +using Wemogy.CQRS.Commands.Abstractions; +using Wemogy.CQRS.Common.ValueObjects; + +namespace Wemogy.CQRS.Abstractions; + +public interface IRemoteCommandRunner + where TCommand : ICommandBase +{ + Task RunAsync(CommandRequest request); +} diff --git a/src/core/Wemogy.CQRS/Abstractions/IRemoteCommandRunner`2.cs b/src/core/Wemogy.CQRS/Abstractions/IRemoteCommandRunner`2.cs new file mode 100644 index 0000000..a5aa130 --- /dev/null +++ b/src/core/Wemogy.CQRS/Abstractions/IRemoteCommandRunner`2.cs @@ -0,0 +1,11 @@ +using System.Threading.Tasks; +using Wemogy.CQRS.Commands.Abstractions; +using Wemogy.CQRS.Common.ValueObjects; + +namespace Wemogy.CQRS.Abstractions; + +public interface IRemoteCommandRunner + where TCommand : ICommand +{ + Task RunAsync(CommandRequest command); +} diff --git a/src/core/Wemogy.CQRS/Abstractions/IRemoteQueryRunner.cs b/src/core/Wemogy.CQRS/Abstractions/IRemoteQueryRunner.cs new file mode 100644 index 0000000..92009fe --- /dev/null +++ b/src/core/Wemogy.CQRS/Abstractions/IRemoteQueryRunner.cs @@ -0,0 +1,12 @@ +using System.Threading; +using System.Threading.Tasks; +using Wemogy.CQRS.Common.ValueObjects; +using Wemogy.CQRS.Queries.Abstractions; + +namespace Wemogy.CQRS.Abstractions; + +public interface IRemoteQueryRunner + where TQuery : IQuery +{ + Task QueryAsync(QueryRequest query, CancellationToken cancellationToken); +} diff --git a/src/core/Wemogy.CQRS/Commands/Abstractions/IScheduledCommand.cs b/src/core/Wemogy.CQRS/Commands/Abstractions/IScheduledCommand.cs index 3e0f7ca..04eadf2 100644 --- a/src/core/Wemogy.CQRS/Commands/Abstractions/IScheduledCommand.cs +++ b/src/core/Wemogy.CQRS/Commands/Abstractions/IScheduledCommand.cs @@ -1,8 +1,5 @@ -using System; - namespace Wemogy.CQRS.Commands.Abstractions; public interface IScheduledCommand { - public object? GetDependency(Type type); } diff --git a/src/core/Wemogy.CQRS/Commands/Abstractions/IScheduledCommandDependencyResolver.cs b/src/core/Wemogy.CQRS/Commands/Abstractions/IScheduledCommandDependencyResolver.cs deleted file mode 100644 index 276fdba..0000000 --- a/src/core/Wemogy.CQRS/Commands/Abstractions/IScheduledCommandDependencyResolver.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Collections.Generic; -using Wemogy.CQRS.Common.ValueObjects; - -namespace Wemogy.CQRS.Commands.Abstractions; - -public interface IScheduledCommandDependencyResolver -{ - List ResolveDependencies(); -} diff --git a/src/core/Wemogy.CQRS/Commands/Resolvers/ScheduledCommandDependencyResolver.cs b/src/core/Wemogy.CQRS/Commands/Resolvers/ScheduledCommandDependencyResolver.cs deleted file mode 100644 index 0af1200..0000000 --- a/src/core/Wemogy.CQRS/Commands/Resolvers/ScheduledCommandDependencyResolver.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.Collections.Generic; -using Microsoft.Extensions.DependencyInjection; -using Wemogy.CQRS.Commands.Abstractions; -using Wemogy.CQRS.Common.ValueObjects; - -namespace Wemogy.CQRS.Commands.Resolvers; - -public class ScheduledCommandDependencyResolver : IScheduledCommandDependencyResolver -{ - private readonly IServiceProvider _serviceProvider; - private readonly Dictionary _dependencyTypesMap; - - public ScheduledCommandDependencyResolver(IServiceProvider serviceProvider, Dictionary dependencyTypesMap) - { - _serviceProvider = serviceProvider; - _dependencyTypesMap = dependencyTypesMap; - } - - public List ResolveDependencies() - { - var result = new List(); - - foreach (var dependencyTypeMap in _dependencyTypesMap) - { - var dependency = _serviceProvider.GetRequiredService(dependencyTypeMap.Key); - result.Add(ScheduledCommandDependency.Create(dependencyTypeMap.Key, dependencyTypeMap.Value, dependency)); - } - - return result; - } -} diff --git a/src/core/Wemogy.CQRS/Commands/Runners/CommandRunner.cs b/src/core/Wemogy.CQRS/Commands/Runners/CommandRunner.cs index ab46403..d4d9f67 100644 --- a/src/core/Wemogy.CQRS/Commands/Runners/CommandRunner.cs +++ b/src/core/Wemogy.CQRS/Commands/Runners/CommandRunner.cs @@ -1,39 +1,60 @@ using System.Threading.Tasks; +using Wemogy.CQRS.Abstractions; using Wemogy.CQRS.Commands.Abstractions; +using Wemogy.CQRS.Common.ValueObjects; namespace Wemogy.CQRS.Commands.Runners; public class CommandRunner where TCommand : ICommand { + private readonly ICommandQueryDependencyResolver _commandQueryDependencyResolver; private readonly PreProcessingRunner _preProcessingRunner; private readonly ICommandHandler _commandHandler; private readonly PostProcessingRunner _postProcessingRunner; + private readonly IRemoteCommandRunner? _remoteCommandRunner; public CommandRunner( + ICommandQueryDependencyResolver commandQueryDependencyResolver, PreProcessingRunner preProcessingRunner, ICommandHandler commandHandler, - PostProcessingRunner postProcessingRunner) + PostProcessingRunner postProcessingRunner, + IRemoteCommandRunner? remoteCommandRunner = null) { + _commandQueryDependencyResolver = commandQueryDependencyResolver; _preProcessingRunner = preProcessingRunner; _commandHandler = commandHandler; _postProcessingRunner = postProcessingRunner; + _remoteCommandRunner = remoteCommandRunner; } public async Task RunAsync(TCommand command) { using var activity = Observability.DefaultActivities.StartActivity($"{typeof(TCommand).Name} Run"); - - // pre-processing - await _preProcessingRunner.RunAsync(command); - - // processing - using var commandHandlerActivity = Observability.DefaultActivities.StartActivity($"{typeof(TCommand).Name} CommandHandler"); - var result = await _commandHandler.HandleAsync(command); - commandHandlerActivity?.Stop(); - - // post-processing - await _postProcessingRunner.RunAsync(command, result); + TResult result; + + if (_remoteCommandRunner == null) + { + // pre-processing + await _preProcessingRunner.RunAsync(command); + + // processing + using var commandHandlerActivity = Observability.DefaultActivities.StartActivity($"{typeof(TCommand).Name} CommandHandler"); + result = await _commandHandler.HandleAsync(command); + commandHandlerActivity?.Stop(); + + // post-processing + await _postProcessingRunner.RunAsync(command, result); + } + else + { + // build the CommandRequest + var deps = _commandQueryDependencyResolver.ResolveDependencies(); + var commandRequest = new CommandRequest( + command, + deps); + result = await _remoteCommandRunner.RunAsync(commandRequest); + } return result; } diff --git a/src/core/Wemogy.CQRS/Commands/Runners/PostProcessingRunner.cs b/src/core/Wemogy.CQRS/Commands/Runners/PostProcessingRunner.cs index 5fe08e4..b011ff1 100644 --- a/src/core/Wemogy.CQRS/Commands/Runners/PostProcessingRunner.cs +++ b/src/core/Wemogy.CQRS/Commands/Runners/PostProcessingRunner.cs @@ -7,9 +7,9 @@ namespace Wemogy.CQRS.Commands.Runners; public class PostProcessingRunner where TCommand : ICommand { - private readonly List> _postProcessors; + private readonly IEnumerable> _postProcessors; - public PostProcessingRunner(List> postProcessors) + public PostProcessingRunner(IEnumerable> postProcessors) { _postProcessors = postProcessors; } diff --git a/src/core/Wemogy.CQRS/Commands/Runners/PreProcessingRunner.cs b/src/core/Wemogy.CQRS/Commands/Runners/PreProcessingRunner.cs index a3381d6..9450cf6 100644 --- a/src/core/Wemogy.CQRS/Commands/Runners/PreProcessingRunner.cs +++ b/src/core/Wemogy.CQRS/Commands/Runners/PreProcessingRunner.cs @@ -7,14 +7,14 @@ namespace Wemogy.CQRS.Commands.Runners; public class PreProcessingRunner where TCommand : ICommandBase { - private readonly List> _commandValidators; - private readonly List> _commandAuthorizations; - private readonly List> _commandPreProcessors; + private readonly IEnumerable> _commandValidators; + private readonly IEnumerable> _commandAuthorizations; + private readonly IEnumerable> _commandPreProcessors; public PreProcessingRunner( - List> commandValidators, - List> commandAuthorizations, - List> commandPreProcessors) + IEnumerable> commandValidators, + IEnumerable> commandAuthorizations, + IEnumerable> commandPreProcessors) { _commandValidators = commandValidators; _commandAuthorizations = commandAuthorizations; diff --git a/src/core/Wemogy.CQRS/Commands/Runners/ScheduledCommandRunner.cs b/src/core/Wemogy.CQRS/Commands/Runners/ScheduledCommandRunner.cs index b6102b6..14c80b8 100644 --- a/src/core/Wemogy.CQRS/Commands/Runners/ScheduledCommandRunner.cs +++ b/src/core/Wemogy.CQRS/Commands/Runners/ScheduledCommandRunner.cs @@ -1,5 +1,6 @@ using System.Threading.Tasks; using Wemogy.Core.Errors; +using Wemogy.CQRS.Abstractions; using Wemogy.CQRS.Commands.Abstractions; using Wemogy.CQRS.Commands.ValueObjects; using Wemogy.CQRS.Common.ValueObjects; @@ -9,24 +10,23 @@ namespace Wemogy.CQRS.Commands.Runners; public class ScheduledCommandRunner : IScheduledCommandRunner where TCommand : ICommand { - private readonly IScheduledCommandDependencyResolver _scheduledCommandDependencyResolver; + private readonly ICommandQueryDependencyResolver _commandQueryDependencyResolver; private readonly PreProcessingRunner _preProcessingRunner; private readonly ICommandHandler _commandHandler; private readonly PostProcessingRunner _postProcessingRunner; private readonly IScheduledCommandService? _scheduledCommandService; public ScheduledCommandRunner( - IScheduledCommandDependencyResolver scheduledCommandDependencyResolver, + ICommandQueryDependencyResolver commandQueryDependencyResolver, PreProcessingRunner preProcessingRunner, ICommandHandler commandHandler, PostProcessingRunner postProcessingRunner, IScheduledCommandService? scheduledCommandService = null) { - _scheduledCommandDependencyResolver = scheduledCommandDependencyResolver; + _commandQueryDependencyResolver = commandQueryDependencyResolver; _preProcessingRunner = preProcessingRunner; _commandHandler = commandHandler; _postProcessingRunner = postProcessingRunner; - _scheduledCommandDependencyResolver = scheduledCommandDependencyResolver; _scheduledCommandService = scheduledCommandService; } @@ -45,7 +45,7 @@ public async Task ScheduleAsync(TCommand command, ScheduleOptions( deps, command); diff --git a/src/core/Wemogy.CQRS/Commands/Runners/VoidCommandRunner.cs b/src/core/Wemogy.CQRS/Commands/Runners/VoidCommandRunner.cs index b5ce7f2..9e34a11 100644 --- a/src/core/Wemogy.CQRS/Commands/Runners/VoidCommandRunner.cs +++ b/src/core/Wemogy.CQRS/Commands/Runners/VoidCommandRunner.cs @@ -1,38 +1,58 @@ using System.Threading.Tasks; +using Wemogy.CQRS.Abstractions; using Wemogy.CQRS.Commands.Abstractions; +using Wemogy.CQRS.Common.ValueObjects; namespace Wemogy.CQRS.Commands.Runners; public class VoidCommandRunner where TCommand : ICommand { + private readonly ICommandQueryDependencyResolver _commandQueryDependencyResolver; private readonly PreProcessingRunner _preProcessingRunner; private readonly ICommandHandler _commandHandler; private readonly VoidPostProcessingRunner _postProcessingRunner; + private readonly IRemoteCommandRunner? _remoteCommandRunner; public VoidCommandRunner( + ICommandQueryDependencyResolver commandQueryDependencyResolver, PreProcessingRunner preProcessingRunner, ICommandHandler commandHandler, - VoidPostProcessingRunner postProcessingRunner) + VoidPostProcessingRunner postProcessingRunner, + IRemoteCommandRunner? remoteCommandRunner = null) { + _commandQueryDependencyResolver = commandQueryDependencyResolver; _preProcessingRunner = preProcessingRunner; _commandHandler = commandHandler; _postProcessingRunner = postProcessingRunner; + _remoteCommandRunner = remoteCommandRunner; } public async Task RunAsync(TCommand command) { using var activity = Observability.DefaultActivities.StartActivity($"{typeof(TCommand).Name} Run"); - // pre-processing - await _preProcessingRunner.RunAsync(command); + if (_remoteCommandRunner == null) + { + // pre-processing + await _preProcessingRunner.RunAsync(command); - // processing - using var commandHandlerActivity = Observability.DefaultActivities.StartActivity($"{typeof(TCommand).Name} CommandHandler"); - await _commandHandler.HandleAsync(command); - commandHandlerActivity?.Stop(); + // processing + using var commandHandlerActivity = Observability.DefaultActivities.StartActivity($"{typeof(TCommand).Name} CommandHandler"); + await _commandHandler.HandleAsync(command); + commandHandlerActivity?.Stop(); - // post-processing - await _postProcessingRunner.RunAsync(command); + // post-processing + await _postProcessingRunner.RunAsync(command); + } + else + { + // build the CommandRequest + var deps = _commandQueryDependencyResolver.ResolveDependencies(); + var commandRequest = new CommandRequest( + command, + deps); + await _remoteCommandRunner.RunAsync(commandRequest); + } } } diff --git a/src/core/Wemogy.CQRS/Commands/Runners/VoidPostProcessingRunner.cs b/src/core/Wemogy.CQRS/Commands/Runners/VoidPostProcessingRunner.cs index e1181a1..ea255fb 100644 --- a/src/core/Wemogy.CQRS/Commands/Runners/VoidPostProcessingRunner.cs +++ b/src/core/Wemogy.CQRS/Commands/Runners/VoidPostProcessingRunner.cs @@ -7,9 +7,9 @@ namespace Wemogy.CQRS.Commands.Runners; public class VoidPostProcessingRunner where TCommand : ICommand { - private readonly List> _postProcessors; + private readonly IEnumerable> _postProcessors; - public VoidPostProcessingRunner(List> postProcessors) + public VoidPostProcessingRunner(IEnumerable> postProcessors) { _postProcessors = postProcessors; } diff --git a/src/core/Wemogy.CQRS/Commands/Runners/VoidRecurringCommandRunner.cs b/src/core/Wemogy.CQRS/Commands/Runners/VoidRecurringCommandRunner.cs index 7489a86..9f551bf 100644 --- a/src/core/Wemogy.CQRS/Commands/Runners/VoidRecurringCommandRunner.cs +++ b/src/core/Wemogy.CQRS/Commands/Runners/VoidRecurringCommandRunner.cs @@ -1,5 +1,6 @@ using System.Threading.Tasks; using Wemogy.Core.Errors; +using Wemogy.CQRS.Abstractions; using Wemogy.CQRS.Commands.Abstractions; using Wemogy.CQRS.Common.ValueObjects; @@ -8,23 +9,23 @@ namespace Wemogy.CQRS.Commands.Runners; public class VoidRecurringCommandRunner where TCommand : ICommand { + private readonly ICommandQueryDependencyResolver _commandQueryDependencyResolver; private readonly PreProcessingRunner _preProcessingRunner; private readonly ICommandHandler _commandHandler; private readonly VoidPostProcessingRunner _postProcessingRunner; private readonly IRecurringCommandService? _recurringCommandService; - private readonly IScheduledCommandDependencyResolver _scheduledCommandDependencyResolver; public VoidRecurringCommandRunner( + ICommandQueryDependencyResolver commandQueryDependencyResolver, PreProcessingRunner preProcessingRunner, ICommandHandler commandHandler, VoidPostProcessingRunner postProcessingRunner, - IScheduledCommandDependencyResolver scheduledCommandDependencyResolver, IRecurringCommandService? recurringCommandService = null) { + _commandQueryDependencyResolver = commandQueryDependencyResolver; _preProcessingRunner = preProcessingRunner; _commandHandler = commandHandler; _postProcessingRunner = postProcessingRunner; - _scheduledCommandDependencyResolver = scheduledCommandDependencyResolver; _recurringCommandService = recurringCommandService; } @@ -41,7 +42,7 @@ public async Task ScheduleAsync(string recurringCommandId, TCommand command, str await _preProcessingRunner.RunPreChecksAsync(command); // schedule recurring command - var deps = _scheduledCommandDependencyResolver.ResolveDependencies(); + var deps = _commandQueryDependencyResolver.ResolveDependencies(); var helper = new ScheduledCommand( deps, command); diff --git a/src/core/Wemogy.CQRS/Commands/Runners/VoidScheduledCommandRunner.cs b/src/core/Wemogy.CQRS/Commands/Runners/VoidScheduledCommandRunner.cs index 84218e2..cc10a21 100644 --- a/src/core/Wemogy.CQRS/Commands/Runners/VoidScheduledCommandRunner.cs +++ b/src/core/Wemogy.CQRS/Commands/Runners/VoidScheduledCommandRunner.cs @@ -1,5 +1,6 @@ using System.Threading.Tasks; using Wemogy.Core.Errors; +using Wemogy.CQRS.Abstractions; using Wemogy.CQRS.Commands.Abstractions; using Wemogy.CQRS.Commands.ValueObjects; using Wemogy.CQRS.Common.ValueObjects; @@ -9,24 +10,23 @@ namespace Wemogy.CQRS.Commands.Runners; public class VoidScheduledCommandRunner : IScheduledCommandRunner where TCommand : ICommand { - private readonly IScheduledCommandDependencyResolver _scheduledCommandDependencyResolver; + private readonly ICommandQueryDependencyResolver _commandQueryDependencyResolver; private readonly PreProcessingRunner _preProcessingRunner; private readonly ICommandHandler _commandHandler; private readonly VoidPostProcessingRunner _postProcessingRunner; private readonly IScheduledCommandService? _scheduledCommandService; public VoidScheduledCommandRunner( - IScheduledCommandDependencyResolver scheduledCommandDependencyResolver, + ICommandQueryDependencyResolver commandQueryDependencyResolver, PreProcessingRunner preProcessingRunner, ICommandHandler commandHandler, VoidPostProcessingRunner postProcessingRunner, IScheduledCommandService? scheduledCommandService = null) { - _scheduledCommandDependencyResolver = scheduledCommandDependencyResolver; + _commandQueryDependencyResolver = commandQueryDependencyResolver; _preProcessingRunner = preProcessingRunner; _commandHandler = commandHandler; _postProcessingRunner = postProcessingRunner; - _scheduledCommandDependencyResolver = scheduledCommandDependencyResolver; _scheduledCommandService = scheduledCommandService; } @@ -45,7 +45,7 @@ public async Task ScheduleAsync(TCommand command, ScheduleOptions( deps, command); diff --git a/src/core/Wemogy.CQRS/Commands/Structs/Void.cs b/src/core/Wemogy.CQRS/Commands/Structs/Void.cs deleted file mode 100644 index 5c6a20f..0000000 --- a/src/core/Wemogy.CQRS/Commands/Structs/Void.cs +++ /dev/null @@ -1,135 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace Wemogy.CQRS.Commands.Structs; - -/// -/// Represents a void type, since is not a valid return type in C#. -/// -public readonly struct Void : IEquatable, IComparable, IComparable -{ - private static readonly Void _value = new (); - - /// - /// Default and only value of the type. - /// - public static ref readonly Void Value => ref _value; - - /// - /// Task from a type. - /// - public static Task Task { get; } = System.Threading.Tasks.Task.FromResult(_value); - - /// - /// Compares the current object with another object of the same type. - /// - /// An object to compare with this object. - /// - /// A value that indicates the relative order of the objects being compared. - /// The return value has the following meanings: - /// - Less than zero: This object is less than the parameter. - /// - Zero: This object is equal to . - /// - Greater than zero: This object is greater than . - /// - public int CompareTo(Void other) - { - return 0; - } - - /// - /// Compares the current instance with another object of the same type and returns an integer that indicates whether - /// the current instance precedes, follows, or occurs in the same position in the sort order as the other object. - /// - /// An object to compare with this instance. - /// - /// A value that indicates the relative order of the objects being compared. - /// The return value has these meanings: - /// - Less than zero: This instance precedes in the sort order. - /// - Zero: This instance occurs in the same position in the sort order as . - /// - Greater than zero: This instance follows in the sort order. - /// - int IComparable.CompareTo(object? obj) - { - return 0; - } - - /// - /// Returns a hash code for this instance. - /// - /// - /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. - /// - public override int GetHashCode() - { - return 0; - } - - /// - /// Determines whether the current object is equal to another object of the same type. - /// - /// An object to compare with this object. - /// - /// true if the current object is equal to the parameter; otherwise, false. - /// - public bool Equals(Void other) - { - return true; - } - - /// - /// Determines whether the specified is equal to this instance. - /// - /// The object to compare with the current instance. - /// - /// true if the specified is equal to this instance; otherwise, false. - /// - public override bool Equals(object? obj) - { - return obj is Void; - } - - /// - /// Determines whether the object is equal to the object. - /// - /// The first object. - /// The second object. - /// true - /// if the - /// - /// object is equal to the - /// - /// object; otherwise, - /// false - /// . - public static bool operator ==(Void first, Void second) - { - return true; - } - - /// - /// Determines whether the object is not equal to the object. - /// - /// The first object. - /// The second object. - /// true - /// if the - /// - /// object is not equal to the - /// - /// object; otherwise, - /// false - /// . - public static bool operator !=(Void first, Void second) - { - return false; - } - - /// - /// Returns a that represents this instance. - /// - /// A that represents this instance. - public override string ToString() - { - return "()"; - } -} diff --git a/src/core/Wemogy.CQRS/Common/ValueObjects/CommandQueryDependencies.cs b/src/core/Wemogy.CQRS/Common/ValueObjects/CommandQueryDependencies.cs new file mode 100644 index 0000000..b1666b8 --- /dev/null +++ b/src/core/Wemogy.CQRS/Common/ValueObjects/CommandQueryDependencies.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; + +namespace Wemogy.CQRS.Common.ValueObjects; + +public class CommandQueryDependencies : Dictionary +{ + public CommandQueryDependencies(Dictionary entries) + : base(entries) + { + } +} diff --git a/src/core/Wemogy.CQRS/Common/ValueObjects/ScheduledCommandDependency.cs b/src/core/Wemogy.CQRS/Common/ValueObjects/CommandQueryDependency.cs similarity index 51% rename from src/core/Wemogy.CQRS/Common/ValueObjects/ScheduledCommandDependency.cs rename to src/core/Wemogy.CQRS/Common/ValueObjects/CommandQueryDependency.cs index 516c44b..81dea0b 100644 --- a/src/core/Wemogy.CQRS/Common/ValueObjects/ScheduledCommandDependency.cs +++ b/src/core/Wemogy.CQRS/Common/ValueObjects/CommandQueryDependency.cs @@ -3,7 +3,7 @@ namespace Wemogy.CQRS.Common.ValueObjects; -public class ScheduledCommandDependency +public class CommandQueryDependency { public Type DependencyType { get; set; } @@ -11,15 +11,15 @@ public class ScheduledCommandDependency public string Value { get; set; } - public ScheduledCommandDependency(Type dependencyType, Type implementationType, string value) + public CommandQueryDependency(Type dependencyType, Type implementationType, string value) { DependencyType = dependencyType; ImplementationType = implementationType; Value = value; } - public static ScheduledCommandDependency Create(Type dependencyType, Type implementationType, object value) + public static CommandQueryDependency Create(Type dependencyType, Type implementationType, object value) { - return new ScheduledCommandDependency(dependencyType, implementationType, value.ToJson()); + return new CommandQueryDependency(dependencyType, implementationType, value.ToJson()); } } diff --git a/src/core/Wemogy.CQRS/Common/ValueObjects/CommandRequest.cs b/src/core/Wemogy.CQRS/Common/ValueObjects/CommandRequest.cs new file mode 100644 index 0000000..affdf5a --- /dev/null +++ b/src/core/Wemogy.CQRS/Common/ValueObjects/CommandRequest.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Wemogy.CQRS.Commands.Abstractions; + +namespace Wemogy.CQRS.Common.ValueObjects; + +public class CommandRequest + where TCommand : ICommandBase +{ + public TCommand Command { get; set; } + + public List Dependencies { get; set; } + + public CommandRequest( + TCommand command, + List dependencies) + { + Command = command; + Dependencies = dependencies; + } +} diff --git a/src/core/Wemogy.CQRS/Common/ValueObjects/QueryRequest.cs b/src/core/Wemogy.CQRS/Common/ValueObjects/QueryRequest.cs new file mode 100644 index 0000000..93ddae5 --- /dev/null +++ b/src/core/Wemogy.CQRS/Common/ValueObjects/QueryRequest.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using Wemogy.CQRS.Queries.Abstractions; + +namespace Wemogy.CQRS.Common.ValueObjects; + +public class QueryRequest + where TQuery : IQueryBase +{ + public TQuery Query { get; set; } + + public List Dependencies { get; set; } + + public QueryRequest( + TQuery query, + List dependencies) + { + Query = query; + Dependencies = dependencies; + } +} diff --git a/src/core/Wemogy.CQRS/Common/ValueObjects/ScheduledCommand.cs b/src/core/Wemogy.CQRS/Common/ValueObjects/ScheduledCommand.cs index fa3fafe..ae281e8 100644 --- a/src/core/Wemogy.CQRS/Common/ValueObjects/ScheduledCommand.cs +++ b/src/core/Wemogy.CQRS/Common/ValueObjects/ScheduledCommand.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Text.Json.Serialization; -using Wemogy.Core.Extensions; using Wemogy.CQRS.Commands.Abstractions; namespace Wemogy.CQRS.Common.ValueObjects; @@ -11,32 +9,17 @@ public class ScheduledCommand : IScheduledCommand where TCommand : notnull { [JsonInclude] - public List Dependencies { get; private set; } + public List Dependencies { get; private set; } [JsonInclude] public Type CommandType { get; private set; } public TCommand Command { get; set; } - public ScheduledCommand(List dependencies, TCommand command) + public ScheduledCommand(List dependencies, TCommand command) { Dependencies = dependencies; CommandType = command.GetType(); Command = command; } - - public object? GetDependency(Type type) - { - var dependency = Dependencies - .FirstOrDefault(x => x.DependencyType == type); - - var json = dependency?.Value; - - if (dependency is null || json is null) - { - return null; - } - - return json.FromJson(dependency.ImplementationType); - } } diff --git a/src/core/Wemogy.CQRS/Common/ValueObjects/ScheduledCommandDependencies.cs b/src/core/Wemogy.CQRS/Common/ValueObjects/ScheduledCommandDependencies.cs deleted file mode 100644 index 3e9e642..0000000 --- a/src/core/Wemogy.CQRS/Common/ValueObjects/ScheduledCommandDependencies.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Wemogy.CQRS.Common.ValueObjects; - -public class ScheduledCommandDependencies : Dictionary -{ - public ScheduledCommandDependencies(Dictionary entries) - : base(entries) - { - } -} diff --git a/src/core/Wemogy.CQRS/DependencyInjection.cs b/src/core/Wemogy.CQRS/DependencyInjection.cs index 04f00ca..acdba75 100644 --- a/src/core/Wemogy.CQRS/DependencyInjection.cs +++ b/src/core/Wemogy.CQRS/DependencyInjection.cs @@ -3,12 +3,14 @@ using System.Linq; using System.Reflection; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Wemogy.Core.Errors; using Wemogy.Core.Extensions; using Wemogy.Core.Monitoring; +using Wemogy.CQRS.Abstractions; using Wemogy.CQRS.Commands.Abstractions; using Wemogy.CQRS.Commands.Mediators; using Wemogy.CQRS.Commands.Registries; -using Wemogy.CQRS.Commands.Resolvers; using Wemogy.CQRS.Commands.Runners; using Wemogy.CQRS.Common.ValueObjects; using Wemogy.CQRS.Extensions; @@ -17,7 +19,9 @@ using Wemogy.CQRS.Queries.Mediators; using Wemogy.CQRS.Queries.Registries; using Wemogy.CQRS.Queries.Runners; +using Wemogy.CQRS.Resolvers; using Wemogy.CQRS.Setup; +using Exception = System.Exception; namespace Wemogy.CQRS; @@ -51,12 +55,25 @@ public static CQRSSetupEnvironment AddCQRS( assemblies = assemblies.Distinct().ToList(); dependencies ??= new Dictionary(); - serviceCollection.AddCommands(assemblies, dependencies); + serviceCollection.AddCommandQueryDependencies(dependencies); + + serviceCollection.AddCommands(assemblies); serviceCollection.AddQueries(assemblies); - return new CQRSSetupEnvironment(serviceCollection); + + var setupEnvironment = new CQRSSetupEnvironment(serviceCollection); + serviceCollection.TryAddSingleton(setupEnvironment); + return setupEnvironment; } - private static void AddCommands(this IServiceCollection serviceCollection, List assemblies, Dictionary dependencies) + private static void AddCommandQueryDependencies(this IServiceCollection serviceCollection, Dictionary dependencies) + { + var commandQueryDependencies = new CommandQueryDependencies(dependencies); + serviceCollection.TryAddScoped( + provider => + new CommandQueryDependencyResolver(provider, commandQueryDependencies)); + } + + private static void AddCommands(this IServiceCollection serviceCollection, List assemblies) { var commandTypes = assemblies.GetClassTypesWhichImplementInterface(typeof(ICommand<>)); commandTypes.AddRange(assemblies.GetClassTypesWhichImplementInterface(typeof(ICommand))); @@ -67,60 +84,43 @@ private static void AddCommands(this IServiceCollection serviceCollection, List< { if (!commandType.InheritsOrImplements(typeof(ICommand), out genericCommandType) || genericCommandType == null) { - throw new Exception("Command type must inherit from ICommand<> or ICommand"); + throw Error.Unexpected( + "InvalidCommandType", + $"The command type {commandType.FullName} must inherit from ICommand<> or ICommand"); } } var resultType = genericCommandType.GenericTypeArguments.ElementAtOrDefault(0); - // pre-processing - serviceCollection.AddPreProcessing(assemblies, commandType); - if (resultType == null) { - // command handler - serviceCollection.AddScopedGenericTypeWithImplementationFromAssembly( - assemblies, - typeof(ICommandHandler<>), - commandType); - // command runners serviceCollection.AddCommandRunners(commandType); - - // post-processing - serviceCollection.AddPostProcessing(assemblies, commandType); } else { - // command handler - serviceCollection.AddScopedGenericTypeWithImplementationFromAssembly( - assemblies, - typeof(ICommandHandler<,>), - commandType, - resultType); - // command runners serviceCollection.AddCommandRunners(commandType, resultType); - - // post-processing - serviceCollection.AddPostProcessing(assemblies, commandType, resultType); } } - // ScheduledCommandDependencyResolver - serviceCollection.AddSingleton( - new ScheduledCommandDependencies(dependencies)); - serviceCollection.AddScoped( - provider => - new ScheduledCommandDependencyResolver(provider, dependencies)); + // PreProcessing + serviceCollection.AddPreProcessingServices(assemblies); + + // CommandHandlers + serviceCollection.AddImplementationsOfGenericInterfaceScoped(typeof(ICommandHandler<>), assemblies); + serviceCollection.AddImplementationsOfGenericInterfaceScoped(typeof(ICommandHandler<,>), assemblies); + + // Post-Processing + serviceCollection.AddPostProcessingServices(assemblies); // Add ICommands mediator - serviceCollection.AddScoped(); + serviceCollection.TryAddScoped(); // Add Registries - serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); + serviceCollection.TryAddSingleton(); + serviceCollection.TryAddSingleton(); + serviceCollection.TryAddSingleton(); } private static void AddQueries(this IServiceCollection serviceCollection, List assemblies) @@ -136,34 +136,25 @@ private static void AddQueries(this IServiceCollection serviceCollection, List)); - - // authorization - serviceCollection.AddImplementationCollection(assemblies, queryType, typeof(IQueryAuthorization<>)); - - // handlers - var queryHandlerType = typeof(IQueryHandler<,>).MakeGenericType(queryType, resultType); - var queryHandlers = assemblies.GetClassTypesWhichImplementInterface(queryHandlerType); - if (queryHandlers.Count != 1) - { - throw new Exception( - $"There must be exactly one IQueryHandler registered for query type {queryType.FullName}"); - } - - var queryHandlerImplementationType = queryHandlers[0]; - serviceCollection.AddScoped(queryHandlerType, queryHandlerImplementationType); - // query runner var queryRunnerType = typeof(QueryRunner<,>).MakeGenericType(queryType, resultType); serviceCollection.AddScoped(queryRunnerType); } + // IQueryValidator implementations + serviceCollection.AddImplementationsOfGenericInterfaceScoped(typeof(IQueryValidator<>), assemblies); + + // IQueryAuthorization implementations + serviceCollection.AddImplementationsOfGenericInterfaceScoped(typeof(IQueryAuthorization<>), assemblies); + + // IQueryHandlers implementations + serviceCollection.AddImplementationsOfGenericInterfaceScoped(typeof(IQueryHandler<,>), assemblies); + // Add IQueries mediator - serviceCollection.AddScoped(); + serviceCollection.TryAddScoped(); // Add QueryRunnerRegistry - serviceCollection.AddSingleton(); + serviceCollection.TryAddSingleton(); } private static void AddImplementation( @@ -185,54 +176,33 @@ private static void AddImplementation( serviceCollection.AddScoped(implementationType); } - private static void AddPreProcessing( + private static void AddPreProcessingServices( this IServiceCollection serviceCollection, - List assemblies, - Type commandType) + List assemblies) { // validators - serviceCollection.AddImplementationCollection(assemblies, commandType, typeof(ICommandValidator<>)); + serviceCollection.AddImplementationsOfGenericInterfaceScoped(typeof(ICommandValidator<>), assemblies); // authorization - serviceCollection.AddImplementationCollection(assemblies, commandType, typeof(ICommandAuthorization<>)); + serviceCollection.AddImplementationsOfGenericInterfaceScoped(typeof(ICommandAuthorization<>), assemblies); // pre-processors - serviceCollection.AddImplementationCollection(assemblies, commandType, typeof(ICommandPreProcessor<>)); - - // PreProcessingRunner - serviceCollection.AddImplementation(typeof(PreProcessingRunner<>), commandType); + serviceCollection.AddImplementationsOfGenericInterfaceScoped(typeof(ICommandPreProcessor<>), assemblies); } - private static void AddPostProcessing( + private static void AddPostProcessingServices( this IServiceCollection serviceCollection, - List assemblies, - Type commandType, - Type resultType) + List assemblies) { - // validators - serviceCollection.AddImplementationCollection( - assemblies, - commandType, - resultType, - typeof(ICommandPostProcessor<,>)); - - // PostProcessingRunner - serviceCollection.AddImplementation(typeof(PostProcessingRunner<,>), commandType, resultType); - } - - private static void AddPostProcessing( - this IServiceCollection serviceCollection, - List assemblies, - Type commandType) - { - // validators - serviceCollection.AddImplementationCollection( - assemblies, - commandType, - typeof(ICommandPostProcessor<>)); - - // PostProcessingRunner - serviceCollection.AddImplementation(typeof(VoidPostProcessingRunner<>), commandType); + // Post-processing of commands without result + serviceCollection.AddImplementationsOfGenericInterfaceScoped( + typeof(ICommandPostProcessor<>), + assemblies); + + // Post-processing of commands with result + serviceCollection.AddImplementationsOfGenericInterfaceScoped( + typeof(ICommandPostProcessor<,>), + assemblies); } private static void AddCommandRunners( @@ -241,7 +211,7 @@ private static void AddCommandRunners( Type resultType) { // command runner - serviceCollection.AddScopedGenericType( + serviceCollection.TryAddScopedGenericType( typeof(CommandRunner<,>), commandType, resultType); @@ -254,10 +224,16 @@ private static void AddCommandRunners( scheduledCommandRunnerType); // recurring command runner - serviceCollection.AddScopedGenericType( + serviceCollection.TryAddScopedGenericType( typeof(RecurringCommandRunner<,>), commandType, resultType); + + // PreProcessingRunner + serviceCollection.AddImplementation(typeof(PreProcessingRunner<>), commandType); + + // PostProcessingRunner + serviceCollection.AddImplementation(typeof(PostProcessingRunner<,>), commandType, resultType); } private static void AddCommandRunners( @@ -265,7 +241,7 @@ private static void AddCommandRunners( Type commandType) { // command runner - serviceCollection.AddScopedGenericType( + serviceCollection.TryAddScopedGenericType( typeof(VoidCommandRunner<>), commandType); @@ -277,52 +253,15 @@ private static void AddCommandRunners( scheduledCommandRunnerType); // recurring command runner - serviceCollection.AddScopedGenericType( + serviceCollection.TryAddScopedGenericType( typeof(VoidRecurringCommandRunner<>), commandType); - } - private static void AddImplementationCollection( - this IServiceCollection serviceCollection, - List assemblies, - Type commandType, - Type genericInterfaceType) - { - var interfaceType = genericInterfaceType.MakeGenericType(commandType); - var implementationTypes = assemblies.GetClassTypesWhichImplementInterface(interfaceType); - var implementationCollectionType = typeof(List<>).MakeGenericType(interfaceType); - serviceCollection.AddScoped( - implementationCollectionType, - serviceProvider => - { - var implementationInstances = - implementationTypes - .Select(x => ActivatorUtilities.CreateInstance(serviceProvider, x)) - .ToListOfType(implementationCollectionType); - return implementationInstances; - }); - } + // PreProcessingRunner + serviceCollection.AddImplementation(typeof(PreProcessingRunner<>), commandType); - private static void AddImplementationCollection( - this IServiceCollection serviceCollection, - List assemblies, - Type commandType, - Type resultType, - Type genericInterfaceType) - { - var interfaceType = genericInterfaceType.MakeGenericType(commandType, resultType); - var implementationTypes = assemblies.GetClassTypesWhichImplementInterface(interfaceType); - var implementationCollectionType = typeof(List<>).MakeGenericType(interfaceType); - serviceCollection.AddScoped( - implementationCollectionType, - serviceProvider => - { - var implementationInstances = - implementationTypes - .Select(x => ActivatorUtilities.CreateInstance(serviceProvider, x)) - .ToListOfType(implementationCollectionType); - return implementationInstances; - }); + // PostProcessingRunner + serviceCollection.AddImplementation(typeof(VoidPostProcessingRunner<>), commandType); } /// diff --git a/src/core/Wemogy.CQRS/Extensions/ServiceCollectionExtensions.cs b/src/core/Wemogy.CQRS/Extensions/ServiceCollectionExtensions.cs index e291d28..6908d25 100644 --- a/src/core/Wemogy.CQRS/Extensions/ServiceCollectionExtensions.cs +++ b/src/core/Wemogy.CQRS/Extensions/ServiceCollectionExtensions.cs @@ -3,40 +3,70 @@ using System.Linq; using System.Reflection; using Microsoft.Extensions.DependencyInjection; -using Wemogy.Core.Errors; +using Microsoft.Extensions.DependencyInjection.Extensions; using Wemogy.Core.Extensions; +using Wemogy.CQRS.Common.ValueObjects; namespace Wemogy.CQRS.Extensions; public static class ServiceCollectionExtensions { - public static void AddScopedGenericType( + public static void TryAddScopedGenericType( this IServiceCollection serviceCollection, Type genericType, params Type[] genericTypeArguments) { var serviceType = genericType.MakeGenericType(genericTypeArguments); - serviceCollection.AddScoped(serviceType); + serviceCollection.TryAddScoped(serviceType); } - public static void AddScopedGenericTypeWithImplementationFromAssembly( - this IServiceCollection serviceCollection, - List assemblies, - Type genericType, - params Type[] genericTypeArguments) + public static void AddCommandQueryDependencies( + this IServiceCollection services, + List commandQueryDependencies) { - var serviceType = genericType.MakeGenericType(genericTypeArguments); - var serviceImplementations = assemblies.GetClassTypesWhichImplementInterface(serviceType); + foreach (var commandQueryDependency in commandQueryDependencies) + { + services.AddScoped( + commandQueryDependency.DependencyType, + _ => commandQueryDependency.Value.FromJson(commandQueryDependency.ImplementationType) !); + } + } - if (serviceImplementations.Count != 1) + public static void AddImplementationsOfGenericInterfaceScoped( + this IServiceCollection serviceCollection, + Type genericInterfaceType, + List assemblies) + { + var implementationTypes = assemblies.GetClassTypesWhichImplementInterface(genericInterfaceType); + foreach (var implementationType in implementationTypes) { - throw Error.Unexpected( - "InvalidServiceImplementation", - $"There must be exactly one {serviceType.FullName} declared in the assemblies."); + // find interface of implementation type that is of type interfaceType + var interfaceTypeOfImplementation = implementationType.GetInterfaces() + .First(i => i.IsGenericType && i.GetGenericTypeDefinition() == genericInterfaceType); + + serviceCollection.TryAddScopedExact(interfaceTypeOfImplementation, implementationType); } + } - var serviceImplementationType = serviceImplementations[0]; + /// + /// The difference between this method and Microsoft.Extensions.DependencyInjection.Extensions.TryAddScoped is that this method will check the service type AND implementation type. + /// The TryAddScoped method only checks the service type, which means that if the service type is the same but the implementation type is different, it will add only the first one. + /// + /// The service collection to add the service to. + /// The type of the service to add. + /// The type of the implementation to add. + public static void TryAddScopedExact( + this IServiceCollection serviceCollection, + Type serviceType, + Type implementationType) + { + // skip if already registered a service with the same interface AND implementation + if (serviceCollection.Any( + x => x.ServiceType == serviceType && x.ImplementationType == implementationType)) + { + return; + } - serviceCollection.AddScoped(serviceType, serviceImplementationType); + serviceCollection.AddScoped(serviceType, implementationType); } } diff --git a/src/core/Wemogy.CQRS/Queries/Runners/QueryRunner.cs b/src/core/Wemogy.CQRS/Queries/Runners/QueryRunner.cs index 03d44df..8c9eb01 100644 --- a/src/core/Wemogy.CQRS/Queries/Runners/QueryRunner.cs +++ b/src/core/Wemogy.CQRS/Queries/Runners/QueryRunner.cs @@ -1,6 +1,9 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Wemogy.Core.Errors; +using Wemogy.CQRS.Abstractions; +using Wemogy.CQRS.Common.ValueObjects; using Wemogy.CQRS.Queries.Abstractions; namespace Wemogy.CQRS.Queries.Runners; @@ -8,39 +11,66 @@ namespace Wemogy.CQRS.Queries.Runners; public class QueryRunner where TQuery : IQuery { - private readonly IQueryHandler _queryHandler; - private readonly List> _queryValidators; - private readonly List> _queryAuthorizations; + private readonly ICommandQueryDependencyResolver _commandQueryDependencyResolver; + private readonly IQueryHandler? _queryHandler; + private readonly IEnumerable> _queryValidators; + private readonly IEnumerable> _queryAuthorizations; + private readonly IRemoteQueryRunner? _remoteQueryRunner; public QueryRunner( - IQueryHandler queryHandler, - List> queryValidators, - List> queryAuthorizations) + ICommandQueryDependencyResolver commandQueryDependencyResolver, + IEnumerable> queryValidators, + IEnumerable> queryAuthorizations, + IQueryHandler? queryHandler = null, + IRemoteQueryRunner? remoteQueryRunner = null) { + _commandQueryDependencyResolver = commandQueryDependencyResolver; _queryHandler = queryHandler; _queryValidators = queryValidators; _queryAuthorizations = queryAuthorizations; + _remoteQueryRunner = remoteQueryRunner; } public async Task QueryAsync(TQuery query, CancellationToken cancellationToken) { using var activity = Observability.DefaultActivities.StartActivity($"{typeof(TQuery).Name} Query"); - foreach (var validator in _queryValidators) + TResult result; + + if (_remoteQueryRunner == null) { - using var validatorActivity = Observability.DefaultActivities.StartActivity($"{typeof(TQuery).Name} QueryValidator"); - validator.Validate(query); - } + foreach (var validator in _queryValidators) + { + using var validatorActivity = Observability.DefaultActivities.StartActivity($"{typeof(TQuery).Name} QueryValidator"); + validator.Validate(query); + } + + foreach (var authorization in _queryAuthorizations) + { + using var authorizationActivity = Observability.DefaultActivities.StartActivity($"{typeof(TQuery).Name} QueryAuthorization"); + await authorization.AuthorizeAsync(query); + } - foreach (var authorization in _queryAuthorizations) + if (_queryHandler == null) + { + throw Error.Unexpected( + "QueryHandlerNotRegistered", + $"QueryHandler for {typeof(TQuery).Name} is not registered."); + } + + using var queryHandlerActivity = Observability.DefaultActivities.StartActivity($"{typeof(TQuery).Name} QueryHandler"); + result = await _queryHandler.HandleAsync(query, cancellationToken); + queryHandlerActivity?.Stop(); + } + else { - using var authorizationActivity = Observability.DefaultActivities.StartActivity($"{typeof(TQuery).Name} QueryAuthorization"); - await authorization.AuthorizeAsync(query); + // build the QueryRequest + var deps = _commandQueryDependencyResolver.ResolveDependencies(); + var queryRequest = new QueryRequest( + query, + deps); + result = await _remoteQueryRunner.QueryAsync(queryRequest, cancellationToken); } - using var queryHandlerActivity = Observability.DefaultActivities.StartActivity($"{typeof(TQuery).Name} QueryHandler"); - var result = await _queryHandler.HandleAsync(query, cancellationToken); - queryHandlerActivity?.Stop(); - return result; } } diff --git a/src/core/Wemogy.CQRS/Resolvers/CommandQueryDependencyResolver.cs b/src/core/Wemogy.CQRS/Resolvers/CommandQueryDependencyResolver.cs new file mode 100644 index 0000000..50df2cf --- /dev/null +++ b/src/core/Wemogy.CQRS/Resolvers/CommandQueryDependencyResolver.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using Microsoft.Extensions.DependencyInjection; +using Wemogy.CQRS.Abstractions; +using Wemogy.CQRS.Common.ValueObjects; + +namespace Wemogy.CQRS.Resolvers; + +public class CommandQueryDependencyResolver : ICommandQueryDependencyResolver +{ + private readonly IServiceProvider _serviceProvider; + private readonly CommandQueryDependencies _dependencyTypesMap; + + public CommandQueryDependencyResolver(IServiceProvider serviceProvider, CommandQueryDependencies dependencyTypesMap) + { + _serviceProvider = serviceProvider; + _dependencyTypesMap = dependencyTypesMap; + } + + public List ResolveDependencies() + { + var result = new List(); + + foreach (var dependencyTypeMap in _dependencyTypesMap) + { + var dependency = _serviceProvider.GetRequiredService(dependencyTypeMap.Key); + result.Add(CommandQueryDependency.Create(dependencyTypeMap.Key, dependencyTypeMap.Value, dependency)); + } + + return result; + } +} diff --git a/src/core/Wemogy.CQRS/Setup/CQRSSetupEnvironment.cs b/src/core/Wemogy.CQRS/Setup/CQRSSetupEnvironment.cs index 2f86a1d..7d23a2c 100644 --- a/src/core/Wemogy.CQRS/Setup/CQRSSetupEnvironment.cs +++ b/src/core/Wemogy.CQRS/Setup/CQRSSetupEnvironment.cs @@ -10,4 +10,9 @@ public CQRSSetupEnvironment(IServiceCollection serviceCollection) { ServiceCollection = serviceCollection; } + + protected CQRSSetupEnvironment(CQRSSetupEnvironment setupEnvironment) + { + ServiceCollection = setupEnvironment.ServiceCollection; + } } diff --git a/src/core/Wemogy.CQRS/Wemogy.CQRS.csproj b/src/core/Wemogy.CQRS/Wemogy.CQRS.csproj index b1e76f4..0e2ed63 100644 --- a/src/core/Wemogy.CQRS/Wemogy.CQRS.csproj +++ b/src/core/Wemogy.CQRS/Wemogy.CQRS.csproj @@ -21,6 +21,6 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - + diff --git a/src/extensions/azureServiceBus/Wemogy.CQRS.Extensions.AzureServiceBus.UnitTests/Testing/Commands/PrintContext/PrintContextCommandHandler.cs b/src/extensions/azureServiceBus/Wemogy.CQRS.Extensions.AzureServiceBus.UnitTests/Testing/Commands/PrintContext/PrintContextCommandHandler.cs index d0b82de..5e46f49 100644 --- a/src/extensions/azureServiceBus/Wemogy.CQRS.Extensions.AzureServiceBus.UnitTests/Testing/Commands/PrintContext/PrintContextCommandHandler.cs +++ b/src/extensions/azureServiceBus/Wemogy.CQRS.Extensions.AzureServiceBus.UnitTests/Testing/Commands/PrintContext/PrintContextCommandHandler.cs @@ -3,7 +3,6 @@ using System.Threading.Tasks; using Wemogy.CQRS.Commands.Abstractions; using Wemogy.CQRS.UnitTests.TestApplication.Common.Contexts; -using Void = Wemogy.CQRS.Commands.Structs.Void; namespace Wemogy.CQRS.Extensions.AzureServiceBus.UnitTests.Testing.Commands.PrintContext; @@ -30,6 +29,6 @@ public Task HandleAsync(PrintContextCommand command) } Console.WriteLine($"Context: {_myTestingContext.TenantId}"); - return Task.FromResult(Void.Value); + return Task.CompletedTask; } } diff --git a/src/extensions/azureServiceBus/Wemogy.CQRS.Extensions.AzureServiceBus/Processors/AzureServiceBusCommandProcessor`1.cs b/src/extensions/azureServiceBus/Wemogy.CQRS.Extensions.AzureServiceBus/Processors/AzureServiceBusCommandProcessor`1.cs index 77174f1..780c535 100644 --- a/src/extensions/azureServiceBus/Wemogy.CQRS.Extensions.AzureServiceBus/Processors/AzureServiceBusCommandProcessor`1.cs +++ b/src/extensions/azureServiceBus/Wemogy.CQRS.Extensions.AzureServiceBus/Processors/AzureServiceBusCommandProcessor`1.cs @@ -19,7 +19,6 @@ public class AzureServiceBusCommandProcessor : IAzureServiceBusCommand { private readonly ServiceBusProcessor _serviceBusProcessor; private readonly IServiceCollection _serviceCollection; - private readonly ScheduledCommandDependencies _scheduledCommandDependencies; private readonly string _handleMessageActivityName; private bool _isStarted; @@ -40,9 +39,6 @@ public AzureServiceBusCommandProcessor( Console.WriteLine(args.Exception); return Task.CompletedTask; }; - _scheduledCommandDependencies = serviceCollection - .BuildServiceProvider() - .GetRequiredService(); _handleMessageActivityName = $"HandleMessageOf{typeof(TCommand).Name}"; } @@ -64,22 +60,7 @@ public async Task HandleMessageAsync(ProcessMessageEventArgs arg) "Scheduled command is null"); } - foreach (var scheduledCommandDependency in _scheduledCommandDependencies) - { - services.AddScoped( - scheduledCommandDependency.Key, - _ => - { - var dependency = scheduledCommand.GetDependency(scheduledCommandDependency.Key); - - if (dependency == null) - { - throw Error.Unexpected("DependencyNotFound", $"{scheduledCommandDependency.Key} dependency not found"); - } - - return dependency; - }); - } + services.AddCommandQueryDependencies(scheduledCommand.Dependencies); try { diff --git a/src/extensions/azureServiceBus/Wemogy.CQRS.Extensions.AzureServiceBus/Processors/AzureServiceBusCommandSessionProcessor`1.cs b/src/extensions/azureServiceBus/Wemogy.CQRS.Extensions.AzureServiceBus/Processors/AzureServiceBusCommandSessionProcessor`1.cs index c49417f..e22cb21 100644 --- a/src/extensions/azureServiceBus/Wemogy.CQRS.Extensions.AzureServiceBus/Processors/AzureServiceBusCommandSessionProcessor`1.cs +++ b/src/extensions/azureServiceBus/Wemogy.CQRS.Extensions.AzureServiceBus/Processors/AzureServiceBusCommandSessionProcessor`1.cs @@ -19,7 +19,6 @@ public class AzureServiceBusCommandSessionProcessor : IAzureServiceBus { private readonly ServiceBusSessionProcessor _serviceBusSessionProcessor; private readonly IServiceCollection _serviceCollection; - private readonly ScheduledCommandDependencies _scheduledCommandDependencies; private bool _isStarted; /// @@ -41,9 +40,6 @@ public AzureServiceBusCommandSessionProcessor( Console.WriteLine(args.Exception); return Task.CompletedTask; }; - _scheduledCommandDependencies = serviceCollection - .BuildServiceProvider() - .GetRequiredService(); _handleMessageActivityName = $"HandleMessageOf{typeof(TCommand).Name}"; } @@ -65,22 +61,7 @@ public async Task HandleMessageAsync(ProcessSessionMessageEventArgs arg) "Scheduled command is null"); } - foreach (var scheduledCommandDependency in _scheduledCommandDependencies) - { - services.AddScoped( - scheduledCommandDependency.Key, - _ => - { - var dependency = scheduledCommand.GetDependency(scheduledCommandDependency.Key); - - if (dependency == null) - { - throw Error.Unexpected("DependencyNotFound", $"{scheduledCommandDependency.Key} dependency not found"); - } - - return dependency; - }); - } + services.AddCommandQueryDependencies(scheduledCommand.Dependencies); var scopeFactory = services.BuildServiceProvider().GetRequiredService(); var scope = scopeFactory.CreateScope(); diff --git a/src/extensions/azureServiceBus/Wemogy.CQRS.Extensions.AzureServiceBus/Setup/AzureServiceBusSetupEnvironment.cs b/src/extensions/azureServiceBus/Wemogy.CQRS.Extensions.AzureServiceBus/Setup/AzureServiceBusSetupEnvironment.cs index 1489fcd..4c9a72f 100644 --- a/src/extensions/azureServiceBus/Wemogy.CQRS.Extensions.AzureServiceBus/Setup/AzureServiceBusSetupEnvironment.cs +++ b/src/extensions/azureServiceBus/Wemogy.CQRS.Extensions.AzureServiceBus/Setup/AzureServiceBusSetupEnvironment.cs @@ -8,20 +8,20 @@ using Wemogy.CQRS.Commands.Abstractions; using Wemogy.CQRS.Extensions.AzureServiceBus.Config; using Wemogy.CQRS.Extensions.AzureServiceBus.Processors; +using Wemogy.CQRS.Setup; namespace Wemogy.CQRS.Extensions.AzureServiceBus.Setup { - public class AzureServiceBusSetupEnvironment + public class AzureServiceBusSetupEnvironment : CQRSSetupEnvironment { private readonly ServiceBusClient _serviceBusClient; - private readonly IServiceCollection _serviceCollection; private readonly Dictionary _delayedProcessingOptions; private readonly HashSet _commandTypesWithRegisteredProcessor; public AzureServiceBusSetupEnvironment(ServiceBusClient serviceBusClient, IServiceCollection serviceCollection) + : base(serviceCollection) { _serviceBusClient = serviceBusClient; - _serviceCollection = serviceCollection; _delayedProcessingOptions = new Dictionary(); _commandTypesWithRegisteredProcessor = new HashSet(); } @@ -36,7 +36,7 @@ public AzureServiceBusSetupEnvironment AddDelayedProcessor( { var queueName = GetQueueName(); - _serviceCollection.AddHostedService>(_ => + ServiceCollection.AddHostedService>(_ => { var serviceBusProcessorOptions = new ServiceBusProcessorOptions() { @@ -46,7 +46,7 @@ public AzureServiceBusSetupEnvironment AddDelayedProcessor( configureServiceBusProcessorOptions?.Invoke(serviceBusProcessorOptions); var serviceBusProcessor = _serviceBusClient.CreateProcessor(queueName, serviceBusProcessorOptions); - var processor = new AzureServiceBusCommandProcessor(serviceBusProcessor, _serviceCollection); + var processor = new AzureServiceBusCommandProcessor(serviceBusProcessor, ServiceCollection); return processor; }); @@ -77,7 +77,7 @@ public AzureServiceBusSetupEnvironment AddDelayedSessionProcessor( "You need to enable session support using ConfigureDelayedProcessing before using AddDelayedSessionProcessor"); } - _serviceCollection.AddHostedService>(_ => + ServiceCollection.AddHostedService>(_ => { var serviceBusSessionProcessorOptions = new ServiceBusSessionProcessorOptions() { @@ -94,7 +94,7 @@ public AzureServiceBusSetupEnvironment AddDelayedSessionProcessor( serviceBusSessionProcessorOptions); var processor = new AzureServiceBusCommandSessionProcessor( serviceBusSessionProcessor, - _serviceCollection); + ServiceCollection); return processor; }); diff --git a/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp/Commands/Greeter/GreeterCommand.cs b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp/Commands/Greeter/GreeterCommand.cs new file mode 100644 index 0000000..c195f53 --- /dev/null +++ b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp/Commands/Greeter/GreeterCommand.cs @@ -0,0 +1,13 @@ +using Wemogy.CQRS.Commands.Abstractions; + +namespace Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp.Commands.Greeter; + +public class GreeterCommand : ICommand +{ + public string Name { get; } + + public GreeterCommand(string name) + { + Name = name; + } +} diff --git a/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp/Commands/Greeter/GreeterCommandHandler.cs b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp/Commands/Greeter/GreeterCommandHandler.cs new file mode 100644 index 0000000..533cf6f --- /dev/null +++ b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp/Commands/Greeter/GreeterCommandHandler.cs @@ -0,0 +1,11 @@ +using Wemogy.CQRS.Commands.Abstractions; + +namespace Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp.Commands.Greeter; + +public class GreeterCommandHandler : ICommandHandler +{ + public Task HandleAsync(GreeterCommand command) + { + return Task.FromResult($"Hello, {command.Name}!"); + } +} diff --git a/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp/Commands/PrintHelloWorld/PrintHelloWorldCommand.cs b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp/Commands/PrintHelloWorld/PrintHelloWorldCommand.cs new file mode 100644 index 0000000..dfb67dd --- /dev/null +++ b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp/Commands/PrintHelloWorld/PrintHelloWorldCommand.cs @@ -0,0 +1,7 @@ +using Wemogy.CQRS.Commands.Abstractions; + +namespace Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp.Commands.PrintHelloWorld; + +public class PrintHelloWorldCommand : ICommand +{ +} diff --git a/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp/Commands/PrintHelloWorld/PrintHelloWorldCommandHandler.cs b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp/Commands/PrintHelloWorld/PrintHelloWorldCommandHandler.cs new file mode 100644 index 0000000..8e9a832 --- /dev/null +++ b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp/Commands/PrintHelloWorld/PrintHelloWorldCommandHandler.cs @@ -0,0 +1,12 @@ +using Wemogy.CQRS.Commands.Abstractions; + +namespace Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp.Commands.PrintHelloWorld; + +public class PrintHelloWorldCommandHandler : ICommandHandler +{ + public Task HandleAsync(PrintHelloWorldCommand command) + { + Console.WriteLine("Hello World 👋"); + return Task.CompletedTask; + } +} diff --git a/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp/Commands/ThrowError/ThrowErrorCommand.cs b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp/Commands/ThrowError/ThrowErrorCommand.cs new file mode 100644 index 0000000..e306012 --- /dev/null +++ b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp/Commands/ThrowError/ThrowErrorCommand.cs @@ -0,0 +1,14 @@ +using Wemogy.Core.Errors.Enums; +using Wemogy.CQRS.Commands.Abstractions; + +namespace Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp.Commands.ThrowError; + +public class ThrowErrorCommand : ICommand +{ + public ErrorType ErrorType { get; } + + public ThrowErrorCommand(ErrorType errorType) + { + ErrorType = errorType; + } +} diff --git a/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp/Commands/ThrowError/ThrowErrorCommandHandler.cs b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp/Commands/ThrowError/ThrowErrorCommandHandler.cs new file mode 100644 index 0000000..b0a4bd3 --- /dev/null +++ b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp/Commands/ThrowError/ThrowErrorCommandHandler.cs @@ -0,0 +1,25 @@ +using Wemogy.Core.Errors.Enums; +using Wemogy.Core.Errors.Exceptions; +using Wemogy.CQRS.Commands.Abstractions; + +namespace Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp.Commands.ThrowError; + +public class ThrowErrorCommandHandler : ICommandHandler +{ + public Task HandleAsync(ThrowErrorCommand command) + { + throw new CustomErrorException( + command.ErrorType, + "CustomError", + "This is a custom error", + null); + } + + class CustomErrorException : ErrorException + { + public CustomErrorException(ErrorType errorType, string code, string description, Exception? innerException) + : base(errorType, code, description, innerException) + { + } + } +} diff --git a/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp/Endpoints/GetAgeQueryEndpoint.cs b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp/Endpoints/GetAgeQueryEndpoint.cs new file mode 100644 index 0000000..4f30fb4 --- /dev/null +++ b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp/Endpoints/GetAgeQueryEndpoint.cs @@ -0,0 +1,12 @@ +using Wemogy.CQRS.Extensions.FastEndpoints.Endpoints; +using Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp.Queries.GetAge; + +namespace Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp.Endpoints; + +public class GetAgeQueryEndpoint : QueryEndpointBase +{ + public GetAgeQueryEndpoint() + { + EnableCircularDependencyToleration(); + } +} diff --git a/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp/Endpoints/GreeterCommandEndpoint.cs b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp/Endpoints/GreeterCommandEndpoint.cs new file mode 100644 index 0000000..82b0272 --- /dev/null +++ b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp/Endpoints/GreeterCommandEndpoint.cs @@ -0,0 +1,12 @@ +using Wemogy.CQRS.Extensions.FastEndpoints.Endpoints; +using Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp.Commands.Greeter; + +namespace Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp.Endpoints; + +public class GreeterCommandEndpoint : CommandEndpointBase +{ + public GreeterCommandEndpoint() + { + EnableCircularDependencyToleration(); + } +} diff --git a/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp/Endpoints/PrintHelloWorldCommandEndpoint.cs b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp/Endpoints/PrintHelloWorldCommandEndpoint.cs new file mode 100644 index 0000000..f2bacbd --- /dev/null +++ b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp/Endpoints/PrintHelloWorldCommandEndpoint.cs @@ -0,0 +1,12 @@ +using Wemogy.CQRS.Extensions.FastEndpoints.Endpoints; +using Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp.Commands.PrintHelloWorld; + +namespace Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp.Endpoints; + +public class PrintHelloWorldCommandEndpoint : CommandEndpointBase +{ + public PrintHelloWorldCommandEndpoint() + { + EnableCircularDependencyToleration(); + } +} diff --git a/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp/Endpoints/ThrowErrorCommandEndpoint.cs b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp/Endpoints/ThrowErrorCommandEndpoint.cs new file mode 100644 index 0000000..68f9da9 --- /dev/null +++ b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp/Endpoints/ThrowErrorCommandEndpoint.cs @@ -0,0 +1,12 @@ +using Wemogy.CQRS.Extensions.FastEndpoints.Endpoints; +using Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp.Commands.ThrowError; + +namespace Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp.Endpoints; + +public class ThrowErrorCommandEndpoint : CommandEndpointBase +{ + public ThrowErrorCommandEndpoint() + { + EnableCircularDependencyToleration(); + } +} diff --git a/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp/Program.cs b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp/Program.cs new file mode 100644 index 0000000..4528486 --- /dev/null +++ b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp/Program.cs @@ -0,0 +1,60 @@ +using FastEndpoints; +using Wemogy.CQRS; +using Wemogy.CQRS.Commands.Abstractions; +using Wemogy.CQRS.Extensions.FastEndpoints; +using Wemogy.CQRS.Extensions.FastEndpoints.Extensions; +using Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp.Commands.Greeter; +using Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp.Commands.PrintHelloWorld; +using Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp.Queries.GetAge; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +// FastEndpoints +builder.Services.AddFastEndpoints(); + +// Add CQRS +builder.Services.AddCQRS() + .AddRemoteHttpServer(new Uri("http://localhost:6005")) + .ConfigureRemoteCommandProcessing("api/commands/print-hello-world") + .ConfigureRemoteCommandProcessing("api/commands/greeter") + .ConfigureRemoteQueryProcessing("api/queries/get-age"); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +// FastEndpoints +app.UseFastEndpoints(c => +{ + c.Endpoints.Configurator = ep => + { + ep.AddErrorHandlerPostProcessor(); + }; +}); + +app.MapGet("/demo/print-hello-world", async (ICommands commands) => +{ + var cmd = new PrintHelloWorldCommand(); + await commands.RunAsync(cmd); + + return "Done"; +}); + +app.Run(); + +public partial class Program +{ + // Expose the implicitly defined Program class to the test project +} diff --git a/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp/Properties/launchSettings.json b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp/Properties/launchSettings.json new file mode 100644 index 0000000..fd37488 --- /dev/null +++ b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:59336", + "sslPort": 44300 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:6005", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp/Queries/GetAge/GetAgeQuery.cs b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp/Queries/GetAge/GetAgeQuery.cs new file mode 100644 index 0000000..9e1b889 --- /dev/null +++ b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp/Queries/GetAge/GetAgeQuery.cs @@ -0,0 +1,13 @@ +using Wemogy.CQRS.Queries.Abstractions; + +namespace Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp.Queries.GetAge; + +public class GetAgeQuery : IQuery +{ + public string Name { get; } + + public GetAgeQuery(string name) + { + Name = name; + } +} diff --git a/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp/Queries/GetAge/GetAgeQueryHandler.cs b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp/Queries/GetAge/GetAgeQueryHandler.cs new file mode 100644 index 0000000..d105134 --- /dev/null +++ b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp/Queries/GetAge/GetAgeQueryHandler.cs @@ -0,0 +1,11 @@ +using Wemogy.CQRS.Queries.Abstractions; + +namespace Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp.Queries.GetAge; + +public class GetAgeQueryHandler : IQueryHandler +{ + public Task HandleAsync(GetAgeQuery query, CancellationToken cancellationToken) + { + return Task.FromResult(18); + } +} diff --git a/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp/Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp.csproj b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp/Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp.csproj new file mode 100644 index 0000000..b27c7dc --- /dev/null +++ b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp/Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + enable + true + + + + + + + + + + + + + diff --git a/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp/appsettings.Development.json b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp/appsettings.json b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.UnitTests/Endpoints/CommandEndpointBaseTests.cs b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.UnitTests/Endpoints/CommandEndpointBaseTests.cs new file mode 100644 index 0000000..18a8a5d --- /dev/null +++ b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.UnitTests/Endpoints/CommandEndpointBaseTests.cs @@ -0,0 +1,94 @@ +using FastEndpoints; +using FluentAssertions; +using Microsoft.AspNetCore.Mvc.Testing; +using Wemogy.CQRS.Commands.Abstractions; +using Wemogy.CQRS.Common.ValueObjects; +using Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp.Commands.Greeter; +using Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp.Commands.PrintHelloWorld; +using Wemogy.CQRS.Extensions.FastEndpoints.UnitTests.TestApplication.Commands.LogTestContext; +using Wemogy.CQRS.Extensions.FastEndpoints.UnitTests.TestApplication.Endpoints; +using Wemogy.CQRS.Extensions.FastEndpoints.UnitTests.TestApplication.ValueObjects; + +namespace Wemogy.CQRS.Extensions.FastEndpoints.UnitTests.Endpoints; + +public class CommandEndpointBaseTests : IClassFixture> +{ + private readonly WebApplicationFactory _factory; + + public CommandEndpointBaseTests(WebApplicationFactory factory) + { + _factory = factory; + } + + [Fact] + public async Task CommandEndpointBase_ShouldAddDependenciesFromCommandRequest() + { + // Arrange + var serviceCollection = new ServiceCollection(); + var cqrsSetupEnvironment = serviceCollection.AddCQRS(); + var endpoint = Factory.Create( + c => + { + // Add the test services to the endpoint http context + c.AddTestServices( + s => + { + s.AddSingleton(cqrsSetupEnvironment); + }); + }); + var testContext = new TestContext() + { + UserId = Guid.NewGuid().ToString() + }; + var commandRequest = new CommandRequest( + new LogTestContextCommand(), + new List() + { + CommandQueryDependency.Create(typeof(TestContext), typeof(TestContext), testContext) + }); + + // Act + await endpoint.HandleAsync(commandRequest, CancellationToken.None); + + // Assert + LogTestContextCommandHandler.LogHistory.Should().ContainSingle().Which.UserId.Should().Be(testContext.UserId); + } + + [Fact] + public async Task PrintHelloWorldCommand_HappyPath() + { + // Arrange + var client = _factory.CreateClient(); + var serviceCollection = new ServiceCollection(); + serviceCollection.AddCQRS(typeof(PrintHelloWorldCommand).Assembly) + .AddRemoteHttpServer(client) + .ConfigureRemoteCommandProcessing("api/commands/print-hello-world"); + var commands = serviceCollection.BuildServiceProvider().GetRequiredService(); + var printHelloWorldCommand = new PrintHelloWorldCommand(); + + // Act + var exception = await Record.ExceptionAsync(() => commands.RunAsync(printHelloWorldCommand)); + + // Assert + exception.Should().BeNull(); + } + + [Fact] + public async Task GreeterCommand_HappyPath() + { + // Arrange + var client = _factory.CreateClient(); + var serviceCollection = new ServiceCollection(); + serviceCollection.AddCQRS(typeof(GreeterCommand).Assembly) + .AddRemoteHttpServer(client) + .ConfigureRemoteCommandProcessing("api/commands/greeter"); + var commands = serviceCollection.BuildServiceProvider().GetRequiredService(); + var greeterCommand = new GreeterCommand("Mickey Mouse"); + + // Act + var result = await commands.RunAsync(greeterCommand); + + // Assert + result.Should().Be("Hello, Mickey Mouse!"); + } +} diff --git a/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.UnitTests/Endpoints/QueryEndpointBaseTests.cs b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.UnitTests/Endpoints/QueryEndpointBaseTests.cs new file mode 100644 index 0000000..b0b0775 --- /dev/null +++ b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.UnitTests/Endpoints/QueryEndpointBaseTests.cs @@ -0,0 +1,75 @@ +using FastEndpoints; +using FluentAssertions; +using Microsoft.AspNetCore.Mvc.Testing; +using Wemogy.CQRS.Common.ValueObjects; +using Wemogy.CQRS.Extensions.FastEndpoints.TestWebApp.Queries.GetAge; +using Wemogy.CQRS.Extensions.FastEndpoints.UnitTests.TestApplication.Commands.LogTestContext; +using Wemogy.CQRS.Extensions.FastEndpoints.UnitTests.TestApplication.Endpoints; +using Wemogy.CQRS.Extensions.FastEndpoints.UnitTests.TestApplication.Queries.RequestTestContext; +using Wemogy.CQRS.Extensions.FastEndpoints.UnitTests.TestApplication.ValueObjects; +using Wemogy.CQRS.Queries.Abstractions; + +namespace Wemogy.CQRS.Extensions.FastEndpoints.UnitTests.Endpoints; + +public class QueryEndpointBaseTests : IClassFixture> +{ + private readonly WebApplicationFactory _factory; + + public QueryEndpointBaseTests(WebApplicationFactory factory) + { + _factory = factory; + } + + [Fact] + public async Task QueryEndpointBase_ShouldAddDependenciesFromQueryRequest() + { + // Arrange + var serviceCollection = new ServiceCollection(); + var cqrsSetupEnvironment = serviceCollection.AddCQRS(); + var endpoint = Factory.Create( + c => + { + // Add the test services to the endpoint http context + c.AddTestServices( + s => + { + s.AddSingleton(cqrsSetupEnvironment); + }); + }); + var testContext = new TestContext() + { + UserId = Guid.NewGuid().ToString() + }; + var queryRequest = new QueryRequest( + new RequestTestContextQuery(), + new List() + { + CommandQueryDependency.Create(typeof(TestContext), typeof(TestContext), testContext) + }); + + // Act + await endpoint.HandleAsync(queryRequest, CancellationToken.None); + + // Assert + RequestTestContextQueryHandler.ContextHistory.Should().ContainSingle().Which.UserId.Should().Be(testContext.UserId); + } + + [Fact] + public async Task GetAgeQuery_HappyPath() + { + // Arrange + var client = _factory.CreateClient(); + var serviceCollection = new ServiceCollection(); + serviceCollection.AddCQRS(typeof(GetAgeQuery).Assembly) + .AddRemoteHttpServer(client) + .ConfigureRemoteQueryProcessing("api/queries/get-age"); + var queries = serviceCollection.BuildServiceProvider().GetRequiredService(); + var getAgeQuery = new GetAgeQuery("Micky Mouse"); + + // Act + var result = await queries.QueryAsync(getAgeQuery); + + // Assert + result.Should().Be(18); + } +} diff --git a/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.UnitTests/GlobalUsings.cs b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.UnitTests/GlobalUsings.cs new file mode 100644 index 0000000..c802f44 --- /dev/null +++ b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.UnitTests/GlobalUsings.cs @@ -0,0 +1 @@ +global using Xunit; diff --git a/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.UnitTests/TestApplication/Commands/LogTestContext/LogTestContextCommand.cs b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.UnitTests/TestApplication/Commands/LogTestContext/LogTestContextCommand.cs new file mode 100644 index 0000000..c3628ab --- /dev/null +++ b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.UnitTests/TestApplication/Commands/LogTestContext/LogTestContextCommand.cs @@ -0,0 +1,7 @@ +using Wemogy.CQRS.Commands.Abstractions; + +namespace Wemogy.CQRS.Extensions.FastEndpoints.UnitTests.TestApplication.Commands.LogTestContext; + +public class LogTestContextCommand : ICommand +{ +} diff --git a/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.UnitTests/TestApplication/Commands/LogTestContext/LogTestContextCommandHandler.cs b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.UnitTests/TestApplication/Commands/LogTestContext/LogTestContextCommandHandler.cs new file mode 100644 index 0000000..15cd6bf --- /dev/null +++ b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.UnitTests/TestApplication/Commands/LogTestContext/LogTestContextCommandHandler.cs @@ -0,0 +1,22 @@ +using Wemogy.CQRS.Commands.Abstractions; +using Wemogy.CQRS.Extensions.FastEndpoints.UnitTests.TestApplication.ValueObjects; + +namespace Wemogy.CQRS.Extensions.FastEndpoints.UnitTests.TestApplication.Commands.LogTestContext; + +public class LogTestContextCommandHandler : ICommandHandler +{ + public static readonly Stack LogHistory = new Stack(); + + private readonly TestContext _testContext; + + public LogTestContextCommandHandler(TestContext testContext) + { + _testContext = testContext; + } + + public Task HandleAsync(LogTestContextCommand command) + { + LogHistory.Push(_testContext); + return Task.CompletedTask; + } +} diff --git a/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.UnitTests/TestApplication/Endpoints/LogTestContextCommandEndpoint.cs b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.UnitTests/TestApplication/Endpoints/LogTestContextCommandEndpoint.cs new file mode 100644 index 0000000..08d2dae --- /dev/null +++ b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.UnitTests/TestApplication/Endpoints/LogTestContextCommandEndpoint.cs @@ -0,0 +1,8 @@ +using Wemogy.CQRS.Extensions.FastEndpoints.Endpoints; +using Wemogy.CQRS.Extensions.FastEndpoints.UnitTests.TestApplication.Commands.LogTestContext; + +namespace Wemogy.CQRS.Extensions.FastEndpoints.UnitTests.TestApplication.Endpoints; + +public class LogTestContextCommandEndpoint : CommandEndpointBase +{ +} diff --git a/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.UnitTests/TestApplication/Endpoints/RequestTestContextQueryEndpoint.cs b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.UnitTests/TestApplication/Endpoints/RequestTestContextQueryEndpoint.cs new file mode 100644 index 0000000..29cc88e --- /dev/null +++ b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.UnitTests/TestApplication/Endpoints/RequestTestContextQueryEndpoint.cs @@ -0,0 +1,9 @@ +using Wemogy.CQRS.Extensions.FastEndpoints.Endpoints; +using Wemogy.CQRS.Extensions.FastEndpoints.UnitTests.TestApplication.Queries.RequestTestContext; +using Wemogy.CQRS.Extensions.FastEndpoints.UnitTests.TestApplication.ValueObjects; + +namespace Wemogy.CQRS.Extensions.FastEndpoints.UnitTests.TestApplication.Endpoints; + +public class RequestTestContextQueryEndpoint : QueryEndpointBase +{ +} diff --git a/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.UnitTests/TestApplication/Queries/RequestTestContext/RequestTestContextQuery.cs b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.UnitTests/TestApplication/Queries/RequestTestContext/RequestTestContextQuery.cs new file mode 100644 index 0000000..874da68 --- /dev/null +++ b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.UnitTests/TestApplication/Queries/RequestTestContext/RequestTestContextQuery.cs @@ -0,0 +1,8 @@ +using Wemogy.CQRS.Extensions.FastEndpoints.UnitTests.TestApplication.ValueObjects; +using Wemogy.CQRS.Queries.Abstractions; + +namespace Wemogy.CQRS.Extensions.FastEndpoints.UnitTests.TestApplication.Queries.RequestTestContext; + +public class RequestTestContextQuery : IQuery +{ +} diff --git a/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.UnitTests/TestApplication/Queries/RequestTestContext/RequestTestContextQueryHandler.cs b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.UnitTests/TestApplication/Queries/RequestTestContext/RequestTestContextQueryHandler.cs new file mode 100644 index 0000000..73aa029 --- /dev/null +++ b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.UnitTests/TestApplication/Queries/RequestTestContext/RequestTestContextQueryHandler.cs @@ -0,0 +1,22 @@ +using Wemogy.CQRS.Extensions.FastEndpoints.UnitTests.TestApplication.ValueObjects; +using Wemogy.CQRS.Queries.Abstractions; + +namespace Wemogy.CQRS.Extensions.FastEndpoints.UnitTests.TestApplication.Queries.RequestTestContext; + +public class RequestTestContextQueryHandler : IQueryHandler +{ + public static readonly Stack ContextHistory = new Stack(); + + private readonly TestContext _testContext; + + public RequestTestContextQueryHandler(TestContext testContext) + { + _testContext = testContext; + } + + public Task HandleAsync(RequestTestContextQuery query, CancellationToken cancellationToken) + { + ContextHistory.Push(_testContext); + return Task.FromResult(_testContext); + } +} diff --git a/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.UnitTests/TestApplication/ValueObjects/TestContext.cs b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.UnitTests/TestApplication/ValueObjects/TestContext.cs new file mode 100644 index 0000000..334efab --- /dev/null +++ b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.UnitTests/TestApplication/ValueObjects/TestContext.cs @@ -0,0 +1,11 @@ +namespace Wemogy.CQRS.Extensions.FastEndpoints.UnitTests.TestApplication.ValueObjects; + +public class TestContext +{ + public string UserId { get; set; } + + public TestContext() + { + UserId = string.Empty; + } +} diff --git a/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.UnitTests/Wemogy.CQRS.Extensions.FastEndpoints.UnitTests.csproj b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.UnitTests/Wemogy.CQRS.Extensions.FastEndpoints.UnitTests.csproj new file mode 100644 index 0000000..6ef1e76 --- /dev/null +++ b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.UnitTests/Wemogy.CQRS.Extensions.FastEndpoints.UnitTests.csproj @@ -0,0 +1,31 @@ + + + + net8.0 + enable + + false + true + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + diff --git a/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints/DependencyInjection.cs b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints/DependencyInjection.cs new file mode 100644 index 0000000..548cce0 --- /dev/null +++ b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints/DependencyInjection.cs @@ -0,0 +1,21 @@ +using Wemogy.CQRS.Extensions.FastEndpoints.Setup; +using Wemogy.CQRS.Setup; + +namespace Wemogy.CQRS.Extensions.FastEndpoints; + +public static class DependencyInjection +{ + public static RemoteHttpServerSetupEnvironment AddRemoteHttpServer( + this CQRSSetupEnvironment cqrsSetupEnvironment, + Uri baseAddress) + { + return new RemoteHttpServerSetupEnvironment(cqrsSetupEnvironment, baseAddress); + } + + public static RemoteHttpServerSetupEnvironment AddRemoteHttpServer( + this CQRSSetupEnvironment cqrsSetupEnvironment, + HttpClient httpClient) + { + return new RemoteHttpServerSetupEnvironment(cqrsSetupEnvironment, httpClient); + } +} diff --git a/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints/Endpoints/CommandEndpointBase`1.cs b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints/Endpoints/CommandEndpointBase`1.cs new file mode 100644 index 0000000..1c92f09 --- /dev/null +++ b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints/Endpoints/CommandEndpointBase`1.cs @@ -0,0 +1,73 @@ +using CaseExtensions; +using FastEndpoints; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Logging; +using Wemogy.Core.Extensions; +using Wemogy.CQRS.Abstractions; +using Wemogy.CQRS.Commands.Abstractions; +using Wemogy.CQRS.Common.ValueObjects; +using Wemogy.CQRS.Setup; +using ICommand = Wemogy.CQRS.Commands.Abstractions.ICommand; + +namespace Wemogy.CQRS.Extensions.FastEndpoints.Endpoints; + +public class CommandEndpointBase : Endpoint> + where TCommand : ICommand +{ + private bool _circularDependencyTolerationEnabled; + + protected void EnableCircularDependencyToleration() + { + _circularDependencyTolerationEnabled = true; + } + + public override void Configure() + { + Verbs(Http.POST); + var commandName = typeof(TCommand).Name.RemoveTrailingString("Command").ToKebabCase(); + Routes($"/api/commands/{commandName}"); + + // ToDo: remove this + AllowAnonymous(); + } + + public override async Task HandleAsync(CommandRequest req, CancellationToken ct) + { + var logger = HttpContext.RequestServices.GetRequiredService>>(); + var serviceCollection = HttpContext.RequestServices.GetRequiredService(); + var services = new ServiceCollection(); + + foreach (var serviceDescriptor in serviceCollection.ServiceCollection) + { + // check, if serviceDescriptor is IRemoteCommandRunner to avoid circular dependency + if (serviceDescriptor.ServiceType.IsGenericType && + serviceDescriptor.ServiceType.GetGenericTypeDefinition() == typeof(IRemoteCommandRunner<>) && + serviceDescriptor.ServiceType.GetGenericArguments()[0] == typeof(TCommand)) + { + if (_circularDependencyTolerationEnabled) + { + logger.LogWarning( + "Circular dependency detected. Skipping IRemoteCommandRunner<{CommandName}>", + typeof(TCommand).Name); + continue; + } + + logger.LogError( + "Circular dependency detected. IRemoteCommandRunner<{CommandName}> is not allowed", + typeof(TCommand).Name); + } + + services.Add(serviceDescriptor); + } + + services.AddCommandQueryDependencies(req.Dependencies); + + var serviceProvider = services.BuildServiceProvider(); + var commands = serviceProvider.GetRequiredService(); + + await commands.RunAsync(req.Command); + + await SendOkAsync(ct); + } +} diff --git a/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints/Endpoints/CommandEndpointBase`2.cs b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints/Endpoints/CommandEndpointBase`2.cs new file mode 100644 index 0000000..3c562fb --- /dev/null +++ b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints/Endpoints/CommandEndpointBase`2.cs @@ -0,0 +1,72 @@ +using CaseExtensions; +using FastEndpoints; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Logging; +using Wemogy.Core.Extensions; +using Wemogy.CQRS.Abstractions; +using Wemogy.CQRS.Commands.Abstractions; +using Wemogy.CQRS.Common.ValueObjects; +using Wemogy.CQRS.Setup; + +namespace Wemogy.CQRS.Extensions.FastEndpoints.Endpoints; + +public class CommandEndpointBase : Endpoint, TResult> + where TCommand : Commands.Abstractions.ICommand +{ + private bool _circularDependencyTolerationEnabled; + + protected void EnableCircularDependencyToleration() + { + _circularDependencyTolerationEnabled = true; + } + + public override void Configure() + { + Verbs(Http.POST); + var commandName = typeof(TCommand).Name.RemoveTrailingString("Command").ToKebabCase(); + Routes($"/api/commands/{commandName}"); + + // ToDo: remove this + AllowAnonymous(); + } + + public override async Task HandleAsync(CommandRequest req, CancellationToken ct) + { + var logger = HttpContext.RequestServices.GetRequiredService>>(); + var serviceCollection = HttpContext.RequestServices.GetRequiredService(); + var services = new ServiceCollection(); + + foreach (var serviceDescriptor in serviceCollection.ServiceCollection) + { + // check, if serviceDescriptor is IRemoteCommandRunner to avoid circular dependency + if (serviceDescriptor.ServiceType.IsGenericType && + serviceDescriptor.ServiceType.GetGenericTypeDefinition() == typeof(IRemoteCommandRunner<,>) && + serviceDescriptor.ServiceType.GetGenericArguments()[0] == typeof(TCommand)) + { + if (_circularDependencyTolerationEnabled) + { + logger.LogWarning( + "Circular dependency detected. Skipping IRemoteCommandRunner<{CommandName}>", + typeof(TCommand).Name); + continue; + } + + logger.LogError( + "Circular dependency detected. IRemoteCommandRunner<{CommandName}> is not allowed", + typeof(TCommand).Name); + } + + services.Add(serviceDescriptor); + } + + services.AddCommandQueryDependencies(req.Dependencies); + + var serviceProvider = services.BuildServiceProvider(); + var commands = serviceProvider.GetRequiredService(); + + var result = await commands.RunAsync(req.Command); + + await SendOkAsync(result, ct); + } +} diff --git a/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints/Endpoints/QueryEndpointBase.cs b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints/Endpoints/QueryEndpointBase.cs new file mode 100644 index 0000000..02f5165 --- /dev/null +++ b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints/Endpoints/QueryEndpointBase.cs @@ -0,0 +1,73 @@ +using CaseExtensions; +using FastEndpoints; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Logging; +using Wemogy.Core.Errors; +using Wemogy.Core.Extensions; +using Wemogy.CQRS.Abstractions; +using Wemogy.CQRS.Common.ValueObjects; +using Wemogy.CQRS.Queries.Abstractions; +using Wemogy.CQRS.Setup; + +namespace Wemogy.CQRS.Extensions.FastEndpoints.Endpoints; + +public class QueryEndpointBase : Endpoint, TResult> + where TQuery : IQuery +{ + private bool _circularDependencyTolerationEnabled; + + protected void EnableCircularDependencyToleration() + { + _circularDependencyTolerationEnabled = true; + } + + public override void Configure() + { + Verbs(Http.POST); + var queryName = typeof(TQuery).Name.RemoveTrailingString("Query").ToKebabCase(); + Routes($"/api/queries/{queryName}"); + + // ToDo: remove this + AllowAnonymous(); + } + + public override async Task HandleAsync(QueryRequest req, CancellationToken ct) + { + var logger = HttpContext.RequestServices.GetRequiredService>>(); + var serviceCollection = HttpContext.RequestServices.GetRequiredService(); + var services = new ServiceCollection(); + + foreach (var serviceDescriptor in serviceCollection.ServiceCollection) + { + // check, if serviceDescriptor is IRemoteQueryRunner to avoid circular dependency + if (serviceDescriptor.ServiceType.IsGenericType && + serviceDescriptor.ServiceType.GetGenericTypeDefinition() == typeof(IRemoteQueryRunner<,>) && + serviceDescriptor.ServiceType.GetGenericArguments()[0] == typeof(TQuery)) + { + if (_circularDependencyTolerationEnabled) + { + logger.LogWarning( + "Circular dependency detected. Skipping IRemoteQueryRunner<{QueryName}>", + typeof(TQuery).Name); + continue; + } + + throw Error.Unexpected( + "CircularDependency", + $"Circular dependency detected. IRemoteQueryRunner<{typeof(TQuery).Name}> is not allowed"); + } + + services.Add(serviceDescriptor); + } + + services.AddCommandQueryDependencies(req.Dependencies); + + var serviceProvider = services.BuildServiceProvider(); + var queries = serviceProvider.GetRequiredService(); + + var result = await queries.QueryAsync(req.Query, ct); + + await SendOkAsync(result, ct); + } +} diff --git a/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints/Extensions/EndpointDefinitionExtensions.cs b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints/Extensions/EndpointDefinitionExtensions.cs new file mode 100644 index 0000000..6e76a08 --- /dev/null +++ b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints/Extensions/EndpointDefinitionExtensions.cs @@ -0,0 +1,12 @@ +using FastEndpoints; +using Wemogy.CQRS.Extensions.FastEndpoints.PostProcessors; + +namespace Wemogy.CQRS.Extensions.FastEndpoints.Extensions; + +public static class EndpointDefinitionExtensions +{ + public static void AddErrorHandlerPostProcessor(this EndpointDefinition endpointDefinition) + { + endpointDefinition.PostProcessor(Order.Before); + } +} diff --git a/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints/Extensions/ExceptionInformationExtensions.cs b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints/Extensions/ExceptionInformationExtensions.cs new file mode 100644 index 0000000..74d87db --- /dev/null +++ b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints/Extensions/ExceptionInformationExtensions.cs @@ -0,0 +1,16 @@ +using Wemogy.Core.Json.ExceptionInformation; + +namespace Wemogy.CQRS.Extensions.FastEndpoints.Extensions; + +public static class ExceptionInformationExtensions +{ + public static Exception ToException(this ExceptionInformation exceptionInformation) + { + throw new NotImplementedException(); + } + + private static Type GetExceptionType(this ExceptionInformation exceptionInformation) + { + return Type.GetType(exceptionInformation.ExceptionType) ?? typeof(Exception); + } +} diff --git a/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints/Extensions/JsonTypeHeaderExtensions.cs b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints/Extensions/JsonTypeHeaderExtensions.cs new file mode 100644 index 0000000..8dbe211 --- /dev/null +++ b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints/Extensions/JsonTypeHeaderExtensions.cs @@ -0,0 +1,19 @@ +using Microsoft.AspNetCore.Http; +using RestSharp; + +namespace Wemogy.CQRS.Extensions.FastEndpoints.Extensions; + +public static class JsonTypeHeaderExtensions +{ + private const string JsonTypeHeaderName = "X-JSON-TYPE"; + + public static void AppendJsonTypeHeader(this IHeaderDictionary headers) + { + headers.Append(JsonTypeHeaderName, nameof(TJsonType).ToLower()); + } + + public static bool HasJsonTypeHeader(this IReadOnlyCollection headers) + { + return headers.Any(h => h.Name == JsonTypeHeaderName && h.Value?.ToString() == nameof(TJsonType).ToLower()); + } +} diff --git a/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints/PostProcessors/ErrorHandlerPostProcessor.cs b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints/PostProcessors/ErrorHandlerPostProcessor.cs new file mode 100644 index 0000000..848e573 --- /dev/null +++ b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints/PostProcessors/ErrorHandlerPostProcessor.cs @@ -0,0 +1,33 @@ +using FastEndpoints; +using Wemogy.Core.Errors.Exceptions; +using Wemogy.Core.Errors.Extensions; +using Wemogy.Core.Json.ExceptionInformation; +using Wemogy.CQRS.Extensions.FastEndpoints.Extensions; + +namespace Wemogy.CQRS.Extensions.FastEndpoints.PostProcessors; + +public class ErrorHandlerPostProcessor : IGlobalPostProcessor +{ + public async Task PostProcessAsync(IPostProcessorContext context, CancellationToken ct) + { + if (!context.HasExceptionOccurred) + { + return; + } + + var exception = context.ExceptionDispatchInfo.SourceException; + + if (exception is ErrorException errorException) + { + var statusCode = errorException.ErrorType.ToHttpStatusCode(); + + context.MarkExceptionAsHandled(); + + context.HttpContext.Response.Headers.AppendJsonTypeHeader(); + await context.HttpContext.Response.SendAsync(exception.ToJson(), (int)statusCode, cancellation: ct); + return; + } + + context.ExceptionDispatchInfo.Throw(); + } +} diff --git a/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints/RemoteCommandRunners/HttpRemoteCommandRunner`1.cs b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints/RemoteCommandRunners/HttpRemoteCommandRunner`1.cs new file mode 100644 index 0000000..a2e9089 --- /dev/null +++ b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints/RemoteCommandRunners/HttpRemoteCommandRunner`1.cs @@ -0,0 +1,54 @@ +using RestSharp; +using Wemogy.Core.Extensions; +using Wemogy.Core.Json.ExceptionInformation; +using Wemogy.CQRS.Abstractions; +using Wemogy.CQRS.Commands.Abstractions; +using Wemogy.CQRS.Common.ValueObjects; +using Wemogy.CQRS.Extensions.FastEndpoints.Extensions; + +namespace Wemogy.CQRS.Extensions.FastEndpoints.RemoteCommandRunners; + +public class HttpRemoteCommandRunner : IRemoteCommandRunner + where TCommand : ICommandBase +{ + private readonly RestClient _restClient; + + /// + /// This is the sub-path of the client base path + /// + private readonly string _urlPath; + + public HttpRemoteCommandRunner(RestClient restClient, string urlPath) + { + _restClient = restClient; + _urlPath = urlPath; + } + + public async Task RunAsync(CommandRequest command) + { + // Http call to the remote service + var request = new RestRequest(_urlPath) + .AddJsonBody(command); + + try + { + var response = await _restClient.ExecutePostAsync(request); + + if (!response.IsSuccessful) + { + if (response.Headers == null || !response.Headers.HasJsonTypeHeader()) + { + throw response.ErrorException ?? new Exception(response.Content); + } + + // ToDo: Handle the exception information + var exceptionInformation = response.Content?.FromJson(); + } + } + catch (HttpRequestException e) + { + Console.WriteLine(e); + throw; + } + } +} diff --git a/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints/RemoteCommandRunners/HttpRemoteCommandRunner`2.cs b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints/RemoteCommandRunners/HttpRemoteCommandRunner`2.cs new file mode 100644 index 0000000..a2b798a --- /dev/null +++ b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints/RemoteCommandRunners/HttpRemoteCommandRunner`2.cs @@ -0,0 +1,45 @@ +using System.Text.Json; +using RestSharp; +using Wemogy.CQRS.Abstractions; +using Wemogy.CQRS.Commands.Abstractions; +using Wemogy.CQRS.Common.ValueObjects; + +namespace Wemogy.CQRS.Extensions.FastEndpoints.RemoteCommandRunners; + +public class HttpRemoteCommandRunner : IRemoteCommandRunner + where TCommand : ICommand +{ + private readonly RestClient _restClient; + + /// + /// This is the sub-path of the client base path + /// + private readonly string _urlPath; + + public HttpRemoteCommandRunner(RestClient restClient, string urlPath) + { + _restClient = restClient; + _urlPath = urlPath; + } + + public async Task RunAsync(CommandRequest command) + { + // Http call to the remote service + var request = new RestRequest(_urlPath) + .AddJsonBody(command); + + var response = await _restClient.PostAsync(request); + + if (!response.IsSuccessful) + { + throw new Exception($"Failed to run command {command.Command.GetType().Name}"); + } + + if (response.Content == null) + { + return default!; + } + + return JsonSerializer.Deserialize(response.Content) !; + } +} diff --git a/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints/RemoteQueryRunners/HttpRemoteQueryRunner.cs b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints/RemoteQueryRunners/HttpRemoteQueryRunner.cs new file mode 100644 index 0000000..bafb894 --- /dev/null +++ b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints/RemoteQueryRunners/HttpRemoteQueryRunner.cs @@ -0,0 +1,47 @@ +using System.Text.Json; +using RestSharp; +using Wemogy.CQRS.Abstractions; +using Wemogy.CQRS.Common.ValueObjects; +using Wemogy.CQRS.Queries.Abstractions; + +namespace Wemogy.CQRS.Extensions.FastEndpoints.RemoteQueryRunners; + +public class HttpRemoteQueryRunner : IRemoteQueryRunner + where TQuery : IQuery +{ + private readonly RestClient _restClient; + + /// + /// This is the sub-path of the client base path + /// + private readonly string _urlPath; + + public HttpRemoteQueryRunner(RestClient restClient, string urlPath) + { + _restClient = restClient; + _urlPath = urlPath; + } + + public async Task QueryAsync(QueryRequest query, CancellationToken cancellationToken) + { + // ToDo: Get configuration for the TCommand + + // ToDo: Http call to the remote service + var request = new RestRequest(_urlPath) + .AddJsonBody(query); + + var response = await _restClient.PostAsync(request, cancellationToken: cancellationToken); + + if (!response.IsSuccessful) + { + throw new Exception($"Failed to run query {query.Query.GetType().Name}"); + } + + if (response.Content == null) + { + return default!; + } + + return JsonSerializer.Deserialize(response.Content) !; + } +} diff --git a/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints/Setup/RemoteHttpServerSetupEnvironment.cs b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints/Setup/RemoteHttpServerSetupEnvironment.cs new file mode 100644 index 0000000..91e260c --- /dev/null +++ b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints/Setup/RemoteHttpServerSetupEnvironment.cs @@ -0,0 +1,100 @@ +using System.Reflection; +using Microsoft.Extensions.DependencyInjection; +using RestSharp; +using Wemogy.Core.Errors; +using Wemogy.CQRS.Abstractions; +using Wemogy.CQRS.Commands.Abstractions; +using Wemogy.CQRS.Extensions.FastEndpoints.RemoteCommandRunners; +using Wemogy.CQRS.Extensions.FastEndpoints.RemoteQueryRunners; +using Wemogy.CQRS.Queries.Abstractions; +using Wemogy.CQRS.Setup; + +namespace Wemogy.CQRS.Extensions.FastEndpoints.Setup; + +public class RemoteHttpServerSetupEnvironment : CQRSSetupEnvironment +{ + private readonly RestClient _restClient; + + public RemoteHttpServerSetupEnvironment( + CQRSSetupEnvironment cqrsSetupEnvironment, + Uri baseUrl) + : base(cqrsSetupEnvironment) + { + _restClient = new RestClient(baseUrl); + } + + public RemoteHttpServerSetupEnvironment( + CQRSSetupEnvironment cqrsSetupEnvironment, + HttpClient httpClient) + : base(cqrsSetupEnvironment) + { + _restClient = new RestClient(httpClient); + } + + public RemoteHttpServerSetupEnvironment ConfigureRemoteCommandProcessing(string urlPath) + where TCommand : ICommandBase + { + // check if TCommand is ICommand or ICommand + if (typeof(ICommand).IsAssignableFrom(typeof(TCommand))) + { + ServiceCollection.AddSingleton>( + new HttpRemoteCommandRunner(_restClient, urlPath)); + } + else + { + // invoke the RegisterRemoteCommandRunner method + var method = GetType().GetMethod(nameof(RegisterRemoteCommandRunner), BindingFlags.Instance | BindingFlags.NonPublic); + var genericTypeArgument = typeof(TCommand).GetInterfaces().FirstOrDefault( + i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ICommand<>)) + ?.GetGenericArguments().FirstOrDefault(); + + if (genericTypeArgument == null) + { + throw Error.Unexpected( + "GenericTypeArgumentNotFound", + "Could not determine the generic type argument of the ICommand interface implemented by the command."); + } + + var genericMethod = method!.MakeGenericMethod(typeof(TCommand), genericTypeArgument); + genericMethod.Invoke(this, new object[] { urlPath }); + } + + return this; + } + + public RemoteHttpServerSetupEnvironment ConfigureRemoteQueryProcessing(string urlPath) + where TQuery : IQueryBase + { + // invoke the RegisterRemoteQueryRunner method + var method = GetType().GetMethod(nameof(RegisterRemoteQueryRunner), BindingFlags.Instance | BindingFlags.NonPublic); + var genericTypeArgument = typeof(TQuery).GetInterfaces().FirstOrDefault( + i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IQuery<>)) + ?.GetGenericArguments().FirstOrDefault(); + + if (genericTypeArgument == null) + { + throw Error.Unexpected( + "GenericTypeArgumentNotFound", + "Could not determine the generic type argument of the IQuery interface implemented by the query."); + } + + var genericMethod = method!.MakeGenericMethod(typeof(TQuery), genericTypeArgument); + genericMethod.Invoke(this, new object[] { urlPath }); + + return this; + } + + private void RegisterRemoteCommandRunner(string urlPath) + where TCommand : ICommand + { + ServiceCollection.AddSingleton>( + new HttpRemoteCommandRunner(_restClient, urlPath)); + } + + private void RegisterRemoteQueryRunner(string urlPath) + where TQuery : IQuery + { + ServiceCollection.AddSingleton>( + new HttpRemoteQueryRunner(_restClient, urlPath)); + } +} diff --git a/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.csproj b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.csproj new file mode 100644 index 0000000..b32866b --- /dev/null +++ b/src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints.csproj @@ -0,0 +1,31 @@ + + + + net8.0 + enable + true + + + + Wemogy.CQRS.Extensions.FastEndpoints + wemogy GmbH + wemogy GmbH + FastEndpoints extensions for wemogy.CQRS + wemogy + https://github.com/wemogy/libs-cqrs + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + +