Skip to content

Commit 9f84c2c

Browse files
committed
Created ResultOrientedCommandHandlerDecorator.cs for structured command execution and error handling.
1 parent ae10d88 commit 9f84c2c

File tree

13 files changed

+206
-6
lines changed

13 files changed

+206
-6
lines changed

TodoSample/Core/Application.Contracts/Honamic.Todo.Application.Contracts.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
<ItemGroup>
1212

13+
<ProjectReference Include="..\..\..\src\Core\Applications.Abstractions\Honamic.Framework.Applications.Abstractions.csproj" />
14+
1315
<ProjectReference Include="..\..\..\src\Core\Commands.Abstractions\Honamic.Framework.Commands.Abstractions.csproj" />
1416
</ItemGroup>
1517

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11

2+
using Honamic.Framework.Applications.Results;
23
using Honamic.Framework.Commands;
34

45
namespace Honamic.Todo.Application.TodoItems.Commands;
56
public record CreateTodoItemCommand(string title, string content, List<string> tags) : ICommand;
7+
8+
9+
10+
11+
public record CreateTodoItem2Command(string title, string content, List<string> tags)
12+
: ICommand<Result<CreateTodoItem2ResultCommand>>;
13+
14+
15+
16+
public class CreateTodoItem2ResultCommand
17+
{
18+
public long Id { get; set; }
19+
}

TodoSample/Core/Application/Extensions/ApplicationServiceCollectionExtensions.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using Honamic.Framework.Tools.IdGenerators;
88
using Honamic.Todo.Application.TodoItems.EventHandlers;
99
using Honamic.IdentityPlus.Domain.Users;
10+
using Honamic.Framework.Applications.Results;
1011

1112
namespace Honamic.Todo.Application.Extensions;
1213

@@ -25,6 +26,11 @@ private static void AddCommandHandlers(this IServiceCollection services)
2526
{
2627
services.AddCommandHandler<DeleteTodoItemCommand, DeleteTodoItemCommandHandler>();
2728
services.AddCommandHandler<CreateTodoItemCommand, CreateTodoItemCommandHandler>();
29+
services.AddCommandHandler<
30+
CreateTodoItem2Command,
31+
CreateTodoItem2CommandHandler,
32+
Result<CreateTodoItem2ResultCommand>>();
33+
2834
services.AddCommandHandler<MakeCompletedTodoItemCommand, MakeCompletedTodoItemCommandHandler>();
2935
}
3036

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using Honamic.Framework.Applications.Results;
2+
using Honamic.Framework.Commands;
3+
using Honamic.Framework.Domain;
4+
using Honamic.Todo.Application.TodoItems.Commands;
5+
using Honamic.Todo.Domain.TodoItems;
6+
7+
namespace Honamic.Todo.Application.TodoItems.CommandHandlers;
8+
internal class CreateTodoItem2CommandHandler :
9+
ICommandHandler<CreateTodoItem2Command, Result<CreateTodoItem2ResultCommand>>
10+
{
11+
private readonly ITodoItemRepository _todoItemRepository;
12+
private readonly IIdGenerator _idGenerator;
13+
14+
public CreateTodoItem2CommandHandler(ITodoItemRepository todoItemRepository, IIdGenerator idGenerator)
15+
{
16+
_todoItemRepository = todoItemRepository;
17+
_idGenerator = idGenerator;
18+
}
19+
20+
public async Task<Result<CreateTodoItem2ResultCommand>> HandleAsync(CreateTodoItem2Command command, CancellationToken cancellationToken)
21+
{
22+
var todoItem = new TodoItem(_idGenerator.GetNewId(), command.title, command.content, command.tags);
23+
await _todoItemRepository.InsertAsync(todoItem);
24+
25+
var result = new CreateTodoItem2ResultCommand
26+
{
27+
Id = todoItem.Id
28+
};
29+
30+
return result;
31+
}
32+
33+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using Honamic.Framework.Applications.Results;
2+
using Honamic.Framework.Commands;
3+
using Honamic.Todo.Application.TodoItems.Commands;
4+
using Microsoft.AspNetCore.Mvc;
5+
6+
namespace Honamic.Todo.Endpoints.WebApi.Controllers;
7+
8+
[Route("api/[controller]")]
9+
[ApiController]
10+
public class TodoItems2Controller : ControllerBase
11+
{
12+
private readonly ILogger<TodoItemsController> _logger;
13+
private readonly ICommandBus _commandBus;
14+
15+
public TodoItems2Controller(ILogger<TodoItemsController> logger, ICommandBus commandBus)
16+
{
17+
_logger = logger;
18+
_commandBus = commandBus;
19+
}
20+
21+
[HttpPost]
22+
public async Task<Result<CreateTodoItem2ResultCommand>> Post([FromBody] CreateTodoItem2Command model, CancellationToken cancellationToken)
23+
{
24+
return await _commandBus
25+
.DispatchAsync<CreateTodoItem2Command, Result<CreateTodoItem2ResultCommand>>
26+
(model, cancellationToken);
27+
}
28+
29+
30+
}

TodoSample/Endpoints/WebApi/Honamic.Todo.Endpoints.WebApi/TodoItems/TodoItemsController.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ public Task<Result<TodoItemQuery>> Get(long id, CancellationToken cancellationTo
3737

3838
}
3939

40+
[HttpPost("Create")]
41+
public Task<Result<long>> Create([FromBody] CreateTodoItemCommand model, CancellationToken cancellationToken)
42+
{
43+
return _todoItemFacade.Create(model, cancellationToken);
44+
}
45+
4046
[HttpPost]
4147
public Task<Result<long>> Post([FromBody] CreateTodoItemCommand model, CancellationToken cancellationToken)
4248
{

src/Facade/Abstractions/Exceptions/UnauthenticatedException.cs renamed to src/Core/Applications.Abstractions/Exceptions/UnauthenticatedException.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
namespace Honamic.Framework.Facade.Exceptions;
1+
namespace Honamic.Framework.Applications.Exceptions;
22

33
public class UnauthenticatedException : Exception
44
{

src/Facade/Abstractions/Exceptions/UnauthorizedException.cs renamed to src/Core/Applications.Abstractions/Exceptions/UnauthorizedException.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
namespace Honamic.Framework.Facade.Exceptions;
1+
namespace Honamic.Framework.Applications.Exceptions;
22

33
public class UnauthorizedException : Exception
44
{
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
using Honamic.Framework.Applications.Exceptions;
2+
using Honamic.Framework.Applications.Results;
3+
using Honamic.Framework.Commands;
4+
using Honamic.Framework.Domain;
5+
6+
namespace Honamic.Framework.Applications.CommandHandlerDecorators;
7+
8+
public class ResultOrientedCommandHandlerDecorator<TCommand, TResponse> : ICommandHandler<TCommand, TResponse>
9+
where TCommand : ICommand<TResponse>
10+
{
11+
private readonly ICommandHandler<TCommand, TResponse> _commandHandler;
12+
13+
public ResultOrientedCommandHandlerDecorator(ICommandHandler<TCommand, TResponse> commandHandler)
14+
{
15+
_commandHandler = commandHandler;
16+
}
17+
18+
public async Task<TResponse> HandleAsync(TCommand command, CancellationToken cancellationToken)
19+
{
20+
TResponse result;
21+
try
22+
{
23+
result = await _commandHandler.HandleAsync(command, cancellationToken);
24+
}
25+
catch (Exception ex)
26+
{
27+
if (IsResultOriented(typeof(TResponse)))
28+
{
29+
result = CreateResultWithError(typeof(TResponse), ex);
30+
return result;
31+
}
32+
33+
throw;
34+
}
35+
36+
return result;
37+
}
38+
39+
private TResponse CreateResultWithError(Type type, Exception ex)
40+
{
41+
var resultObject = CreateResult(type);
42+
43+
if (resultObject is Result result)
44+
{
45+
switch (ex)
46+
{
47+
case UnauthenticatedException:
48+
result.SetStatusAsUnauthenticated();
49+
result.AppendError(ex.Message);
50+
break;
51+
case UnauthorizedException:
52+
result.SetStatusAsUnauthorized();
53+
result.AppendError(ex.Message);
54+
break;
55+
case BusinessException businessException:
56+
result.Status = ResultStatus.ValidationError;
57+
var code = businessException.GetCode();
58+
var message = businessException.GetMessage();
59+
result.AppendError(message, null, code);
60+
break;
61+
default:
62+
result.SetStatusAsUnhandledExceptionWithSorryError();
63+
result.AppendError(ex.ToString(), "Exception");
64+
break;
65+
}
66+
return resultObject;
67+
}
68+
69+
// If we can't cast to Result, we have a serious error
70+
throw new ArgumentException($"Expected a Result type but got {type.FullName}");
71+
}
72+
73+
private TResponse CreateResult(Type type)
74+
{
75+
// For non-generic Result
76+
if (type == typeof(Result))
77+
{
78+
return (TResponse)(object)new Result();
79+
}
80+
81+
// For Result<T>
82+
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Result<>))
83+
{
84+
var genericArgType = type.GenericTypeArguments[0];
85+
var resultType = typeof(Result<>).MakeGenericType(genericArgType);
86+
return (TResponse)Activator.CreateInstance(resultType);
87+
}
88+
89+
return default;
90+
}
91+
92+
private bool IsResultOriented(Type type)
93+
{
94+
if (type == typeof(Result))
95+
{
96+
return true;
97+
}
98+
99+
if (type.IsGenericType
100+
&& type.GetGenericTypeDefinition() == typeof(Result<>))
101+
{
102+
return true;
103+
}
104+
105+
return false;
106+
}
107+
}

src/Core/Applications/Extensions/ServiceCollectionExtensions.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public static void AddCommandHandler<TCommand, TCommandHandler>(this IServiceCol
2727
{
2828
services.AddTransient<ICommandHandler<TCommand>, TCommandHandler>();
2929
services.Decorate<ICommandHandler<TCommand>, TransactionalCommandHandlerDecorator<TCommand>>();
30+
// Note: No ResultOriented decorator for non-response commands
3031
}
3132

3233
public static void AddCommandHandler<TCommand, TCommandHandler, TResponse>(this IServiceCollection services)
@@ -35,6 +36,7 @@ public static void AddCommandHandler<TCommand, TCommandHandler, TResponse>(this
3536
{
3637
services.AddTransient<ICommandHandler<TCommand, TResponse>, TCommandHandler>();
3738
services.Decorate<ICommandHandler<TCommand, TResponse>, TransactionalCommandHandlerDecorator<TCommand, TResponse>>();
39+
services.Decorate<ICommandHandler<TCommand, TResponse>, ResultOrientedCommandHandlerDecorator<TCommand, TResponse>>();
3840
}
3941

4042
public static void AddEventHandler<TEvent, TEventHandler>(this IServiceCollection services)

0 commit comments

Comments
 (0)