Skip to content
This repository was archived by the owner on Aug 21, 2024. It is now read-only.
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .all-contributorsrc
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"login": "KyleMcMaster",
"name": "Kyle McMaster",
"avatar_url": "https://avatars1.githubusercontent.com/u/11415127?v=4",
"profile": "https://github.com/KyleMcMaster",
"profile": "https://www.KyleMcMaster.com",
"contributions": [
"design",
"code",
Expand Down
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ csharp_style_unused_value_assignment_preference = discard_variable:suggestion
csharp_style_unused_value_expression_statement_preference = discard_variable:silent

# 'using' directive preferences
csharp_using_directive_placement = outside_namespace:silent
csharp_using_directive_placement = outside_namespace:suggestion

#### C# Formatting Rules ####

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ Sample HRIS application where a list of employees and their payroll information

### Api and Functions

![dotnet core - build & test](https://github.com/KyleMcMaster/payroll-processor/workflows/dotnet%20core%20-%20build%20&%20test/badge.svg)
![dotnet core - build & test](https://github.com/KyleMcMaster/payroll-processor/workflows/.github/workflows/policy-dotnetcore.yml/badge.svg)

### Client

![.github/workflows/npm.yml](https://github.com/KyleMcMaster/payroll-processor/workflows/.github/workflows/npm.yml/badge.svg)
![client - build & test](https://github.com/KyleMcMaster/payroll-processor/workflows/.github/workflows/policy-npm.yml/badge.svg)
[![Styled with Prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://prettier.io)

## Motivation
Expand Down
3 changes: 2 additions & 1 deletion api/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<PropertyGroup>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<WarningsAsErrors>CS8600;CS8602;CS8603</WarningsAsErrors>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍👍

</PropertyGroup>
</Project>
53 changes: 27 additions & 26 deletions api/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,44 +3,45 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Ardalis.ApiEndpoints" Version="4.0.1" />
<PackageVersion Include="Ardalis.GuardClauses" Version="4.0.1" />
<PackageVersion Include="AutoFixture" Version="4.17.0" />
<PackageVersion Include="AutoFixture.AutoNSubstitute" Version="4.17.0" />
<PackageVersion Include="AutoFixture.Idioms" Version="4.17.0" />
<PackageVersion Include="AutoFixture.Xunit2" Version="4.17.0" />
<PackageVersion Include="AutoMapper" Version="12.0.0" />
<PackageVersion Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.0" />
<PackageVersion Include="Azure.Storage.Queues" Version="12.11.1" />
<PackageVersion Include="Ardalis.ApiEndpoints" Version="4.1.0" />
<PackageVersion Include="Ardalis.GuardClauses" Version="4.1.1" />
<PackageVersion Include="AutoFixture" Version="4.18.0" />
<PackageVersion Include="AutoFixture.AutoNSubstitute" Version="4.18.0" />
<PackageVersion Include="AutoFixture.Idioms" Version="4.18.0" />
<PackageVersion Include="AutoFixture.Xunit2" Version="4.18.0" />
<PackageVersion Include="AutoMapper" Version="12.0.1" />
<PackageVersion Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.1" />
<PackageVersion Include="Azure.Storage.Queues" Version="12.15.0" />
<PackageVersion Include="Bogus" Version="34.0.2" />
<PackageVersion Include="coverlet.collector" Version="3.1.2">
<PackageVersion Include="coverlet.collector" Version="6.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageVersion>
<PackageVersion Include="CSharpFunctionalExtensions.FluentAssertions" Version="1.1.0" />
<PackageVersion Include="FluentAssertions" Version="6.8.0" />
<PackageVersion Include="LanguageExt.Core" Version="4.2.9" />
<PackageVersion Include="CSharpFunctionalExtensions" Version="2.40.1" />
<PackageVersion Include="CSharpFunctionalExtensions.FluentAssertions" Version="2.0.0" />
<PackageVersion Include="FluentAssertions" Version="6.12.0" />
<PackageVersion Include="LanguageExt.Core" Version="4.4.4" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.9" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Versioning" Version="5.0.0" />
<PackageVersion Include="Microsoft.Azure.Cosmos" Version="3.31.0" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Versioning" Version="5.1.0" />
<PackageVersion Include="Microsoft.Azure.Cosmos" Version="3.35.3" />
<PackageVersion Include="Microsoft.Azure.Functions.Extensions" Version="1.1.0" />
<PackageVersion Include="Microsoft.Azure.WebJobs.Extensions.Storage" Version="5.0.1" />
<PackageVersion Include="Microsoft.Azure.WebJobs.Extensions.Storage" Version="5.2.0" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="6.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.2" />
<PackageVersion Include="Microsoft.Extensions.Logging.Debug" Version="6.0.0" />
<PackageVersion Include="Microsoft.NET.Sdk.Functions" Version="4.1.3" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageVersion Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.17.0" />
<PackageVersion Include="Microsoft.NET.Sdk.Functions" Version="4.2.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
<PackageVersion Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.5" />
<PackageVersion Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="6.0.10" />
<PackageVersion Include="NewtonSoft.Json" Version="13.0.1" />
<PackageVersion Include="NSubstitute" Version="4.4.0" />
<PackageVersion Include="Scrutor" Version="4.2.0" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="6.4.0" />
<PackageVersion Include="Swashbuckle.AspNetCore.Annotations" Version="6.4.0" />
<PackageVersion Include="xunit" Version="2.4.2" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.4.5">
<PackageVersion Include="NSubstitute" Version="5.0.0" />
<PackageVersion Include="Scrutor" Version="4.2.2" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="6.5.0" />
<PackageVersion Include="Swashbuckle.AspNetCore.Annotations" Version="6.5.0" />
<PackageVersion Include="xunit" Version="2.5.0" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.5.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageVersion>
</ItemGroup>
</Project>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,15 @@
<PackageReference Include="CSharpFunctionalExtensions.FluentAssertions" />
<PackageReference Include="FluentAssertions" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="xunit"/>
<PackageReference Include="xunit.runner.visualstudio" />
<PackageReference Include="coverlet.collector" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.collector">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System.Threading;
using CSharpFunctionalExtensions;

namespace PayrollProcessor.Core.Domain.Intrastructure.Operations.Commands;
public interface IStranglerCommandDispatcher
{
Result Dispatch(ICommand command, CancellationToken token = default);

Result<TResponse> Dispatch<TResponse>(ICommand<TResponse> command, CancellationToken token = default);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System.Threading;
using CSharpFunctionalExtensions;

namespace PayrollProcessor.Core.Domain.Intrastructure.Operations.Commands;
public interface IStranglerCommandHandler<TCommand> where TCommand : ICommand
{
Result Execute(TCommand command, CancellationToken token);
}

public interface IStranglerCommandHandler<TCommand, TResponse> where TCommand : ICommand<TResponse>
{
Result<TResponse> Execute(TCommand command, CancellationToken token);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using System;
using System.Collections.Concurrent;
using System.Threading;
using Ardalis.GuardClauses;
using CSharpFunctionalExtensions;
using PayrollProcessor.Core.Domain.Intrastructure.Operations.Factories;

namespace PayrollProcessor.Core.Domain.Intrastructure.Operations.Commands;

/// <summary>
/// TODO: Temporarily named after the Strangler Fig Pattern as this serves as an implementation of <see cref="ICommandDispatcher"/> migrating to CSharpFunctionalExtensions from LanguageExt.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you done this kind of naming in a project before? I haven't but I like the idea of calling this type out explicitly. That way, you know it should eventually be renamed when it takes over.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't done this before but the approach was mentioned in Code That Fits In Your Head and I couldn't think of a better name so I figured calling it out explicitly would be best while the code is in migration state.

/// </summary>
public class StranglerCommandDispatcher : IStranglerCommandDispatcher
{
private readonly ServiceProviderDelegate serviceProvider;
private static readonly ConcurrentDictionary<Type, object> commandHandlers = new ConcurrentDictionary<Type, object>();

public StranglerCommandDispatcher(ServiceProviderDelegate serviceProvider)
{
Guard.Against.Null(serviceProvider, nameof(serviceProvider));

this.serviceProvider = serviceProvider;
}

public Result Dispatch(ICommand command, CancellationToken token = default)
{
Guard.Against.Null(command, nameof(command));

var commandType = command.GetType();

var handler = (StranglerCommandHandlerWrapper)commandHandlers
.GetOrAdd(
commandType,
#pragma warning disable CS8603 // Possible null reference return.
t => Activator
.CreateInstance(typeof(StranglerCommandHandlerWrapperImpl<>)
.MakeGenericType(commandType)));
#pragma warning restore CS8603 // Possible null reference return.

return handler.Dispatch(command, serviceProvider, token);
}

public Result<TResponse> Dispatch<TResponse>(ICommand<TResponse> command, CancellationToken token = default)
{
Guard.Against.Null(command, nameof(command));

var commandType = command.GetType();

var handler = (StranglerCommandHandlerWrapper<TResponse>)commandHandlers
.GetOrAdd(
commandType,
#pragma warning disable CS8603 // Possible null reference return.
t => Activator
.CreateInstance(typeof(StranglerCreateCommandHandlerWrapperImpl<,>)
.MakeGenericType(commandType, typeof(TResponse))));
#pragma warning restore CS8603 // Possible null reference return.

return handler.Dispatch(command, serviceProvider, token);
}
}

internal abstract class StranglerCommandHandlerWrapper : HandlerBase
{
public abstract Result Dispatch(ICommand command, ServiceProviderDelegate serviceProvider, CancellationToken token);
}

internal class StranglerCommandHandlerWrapperImpl<TCommand> : StranglerCommandHandlerWrapper
where TCommand : ICommand
{
public override Result Dispatch(ICommand command, ServiceProviderDelegate serviceProvider, CancellationToken token) =>
GetHandler<IStranglerCommandHandler<TCommand>>(serviceProvider).Execute((TCommand)command, token);
}

internal abstract class StranglerCommandHandlerWrapper<TResponse> : HandlerBase
{
public abstract Result<TResponse> Dispatch(ICommand<TResponse> command, ServiceProviderDelegate serviceProvider, CancellationToken token);
}

internal class StranglerCreateCommandHandlerWrapperImpl<TCommand, TResponse> : StranglerCommandHandlerWrapper<TResponse>
where TCommand : ICommand<TResponse>
{
public override Result<TResponse> Dispatch(ICommand<TResponse> command, ServiceProviderDelegate serviceProvider, CancellationToken token) =>
GetHandler<IStranglerCommandHandler<TCommand, TResponse>>(serviceProvider).Execute((TCommand)command, token);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

<ItemGroup>
<PackageReference Include="Ardalis.GuardClauses" />
<PackageReference Include="CSharpFunctionalExtensions" />
<PackageReference Include="LanguageExt.Core" />
<PackageReference Include="NewtonSoft.Json" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,14 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="NSubstitute" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio" />
<PackageReference Include="coverlet.collector" />
<PackageReference Include="xunit.runner.visualstudio">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.collector">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,14 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="NSubstitute" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio" />
<PackageReference Include="coverlet.collector" />
<PackageReference Include="xunit.runner.visualstudio">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.collector">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,14 @@
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio" />
<PackageReference Include="coverlet.collector" />
<PackageReference Include="xunit.runner.visualstudio">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.collector">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Threading.Tasks;
using Ardalis.ApiEndpoints;
using Ardalis.GuardClauses;
using CSharpFunctionalExtensions;
using Microsoft.AspNetCore.Mvc;
using PayrollProcessor.Core.Domain.Features.Employees;
using PayrollProcessor.Core.Domain.Intrastructure.Identifiers;
Expand All @@ -11,14 +12,14 @@

namespace PayrollProcessor.Web.Api.Features.Employees;

public class EmployeeCreate : EndpointBaseAsync
public class EmployeeCreate : EndpointBaseSync
.WithRequest<EmployeeCreateRequest>
.WithActionResult<Employee>
{
private readonly ICommandDispatcher dispatcher;
private readonly IStranglerCommandDispatcher dispatcher;
private readonly IEntityIdGenerator generator;

public EmployeeCreate(ICommandDispatcher dispatcher, IEntityIdGenerator generator)
public EmployeeCreate(IStranglerCommandDispatcher dispatcher, IEntityIdGenerator generator)
{
Guard.Against.Null(dispatcher, nameof(dispatcher));
Guard.Against.Null(generator, nameof(generator));
Expand All @@ -34,7 +35,7 @@ public EmployeeCreate(ICommandDispatcher dispatcher, IEntityIdGenerator generato
OperationId = "Employees.Create",
Tags = new[] { "Employees" })
]
public override Task<ActionResult<Employee>> HandleAsync([FromBody] EmployeeCreateRequest request, CancellationToken token)
public override ActionResult<Employee> Handle([FromBody] EmployeeCreateRequest request)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you drop the Async suffix most of the time now? I think Microsoft s current guidance is to only use it if there is also an existing sync API of the same name...

But I've seen a lot of modern code go both ways.

{
var command = new EmployeeCreateCommand(
generator.Generate(),
Expand All @@ -52,9 +53,9 @@ public override Task<ActionResult<Employee>> HandleAsync([FromBody] EmployeeCrea

return dispatcher
.Dispatch(command)
.Match<Employee, ActionResult<Employee>>(
employee => employee,
ex => new APIErrorResult(ex.Message));
.Match<ActionResult<Employee>, Employee>(
onSuccess: employee => Ok(employee),
onFailure: ex => new APIErrorResult(ex));
}
}

Expand Down
15 changes: 7 additions & 8 deletions payroll-processor.code-workspace
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
{
"folders": [
{
"path": ".",
"name": "root"
},
{
"path": "docs",
"name": "docs"
Expand All @@ -15,10 +19,6 @@
{
"path": "vue-client",
"name": "vue"
},
{
"path": ".",
"name": "root"
}
],
"settings": {
Expand All @@ -32,11 +32,11 @@
"api": true,
"client": true,
"docs": true,
"vue-client": true,
"**/bin": true,
"**/obj": true,
"**/dist": true
},

"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": {
Expand All @@ -52,12 +52,11 @@
*/
"editor.codeActionsOnSave": {}
},

"omnisharp.defaultLaunchSolution": "PayrollProcessor.sln",
"omnisharp.autoStart": true,
"omnisharp.enableEditorConfigSupport": true,
"omnisharp.enableRoslynAnalyzers": true,
"omnisharp.useEditorFormattingSettings": true
"omnisharp.useEditorFormattingSettings": true,
"dotnet.defaultSolution": "PayrollProcessor.sln"
},
"extensions": {
"recommendations": [
Expand Down