From 8f6ec174ece34f9294b597f48be3472962a0ab6f Mon Sep 17 00:00:00 2001 From: Amichai Mantinband Date: Thu, 11 Jan 2024 20:14:44 +0200 Subject: [PATCH] Go functional experiment --- CleanArchitecture.sln | 9 ++ Directory.Packages.props | 22 ++--- ErrorOr | 1 + .../Controllers/RemindersController.cs | 82 +++++++------------ .../Controllers/SubscriptionsController.cs | 72 ++++++---------- .../Controllers/TokensController.cs | 48 +++++------ .../DeleteReminderCommandHandler.cs | 30 +++---- .../DismissReminderCommandHandler.cs | 33 ++------ .../SetReminder/SetReminderCommandHandler.cs | 38 ++++----- .../Events/ReminderDeletedEventHandler.cs | 11 ++- .../Events/ReminderDismissedEventHandler.cs | 12 +-- .../GetReminder/GetReminderQueryHandler.cs | 11 +-- .../CancelSubscriptionCommandHandler.cs | 26 ++---- .../CreateSubscriptionCommandHandler.cs | 38 ++++----- .../GetSubscriptionQueryHandler.cs | 6 +- .../CleanArchitecture.Domain.csproj | 5 +- 16 files changed, 178 insertions(+), 266 deletions(-) create mode 160000 ErrorOr diff --git a/CleanArchitecture.sln b/CleanArchitecture.sln index 05432e5..a3460bd 100644 --- a/CleanArchitecture.sln +++ b/CleanArchitecture.sln @@ -27,6 +27,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CleanArchitecture.Applicati EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CleanArchitecture.Api.IntegrationTests", "tests\CleanArchitecture.Api.IntegrationTests\CleanArchitecture.Api.IntegrationTests.csproj", "{2A315D67-B631-478A-8F67-9EE9DB03D0C5}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ErrorOr", "ErrorOr", "{C47B3C2F-F488-4960-AAF6-A68BB9CB07E3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ErrorOr", "ErrorOr\src\ErrorOr.csproj", "{CBA12FED-3AB0-4276-A930-4AA691D595DE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -76,6 +80,10 @@ Global {2A315D67-B631-478A-8F67-9EE9DB03D0C5}.Debug|Any CPU.Build.0 = Debug|Any CPU {2A315D67-B631-478A-8F67-9EE9DB03D0C5}.Release|Any CPU.ActiveCfg = Release|Any CPU {2A315D67-B631-478A-8F67-9EE9DB03D0C5}.Release|Any CPU.Build.0 = Release|Any CPU + {CBA12FED-3AB0-4276-A930-4AA691D595DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CBA12FED-3AB0-4276-A930-4AA691D595DE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CBA12FED-3AB0-4276-A930-4AA691D595DE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CBA12FED-3AB0-4276-A930-4AA691D595DE}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {0B9F7D22-2A37-4EB6-A410-32BA01213D2A} = {E6C0A80B-5ADC-4138-A79D-C9C128EA163B} @@ -88,5 +96,6 @@ Global {9ED129C8-9C2F-4E19-8CCC-51FBC5CDE0A7} = {1DD25606-DF8E-4B91-9335-F916B14BF596} {EC6267FA-4762-400A-8D7C-0FC0FB167AE8} = {1DD25606-DF8E-4B91-9335-F916B14BF596} {2A315D67-B631-478A-8F67-9EE9DB03D0C5} = {1DD25606-DF8E-4B91-9335-F916B14BF596} + {CBA12FED-3AB0-4276-A930-4AA691D595DE} = {C47B3C2F-F488-4960-AAF6-A68BB9CB07E3} EndGlobalSection EndGlobal diff --git a/Directory.Packages.props b/Directory.Packages.props index 2febe1d..f7218bb 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -4,21 +4,21 @@ - - + + - + - - - + + + - + @@ -27,7 +27,7 @@ - + @@ -36,9 +36,9 @@ - - + + - + \ No newline at end of file diff --git a/ErrorOr b/ErrorOr new file mode 160000 index 0000000..40de55e --- /dev/null +++ b/ErrorOr @@ -0,0 +1 @@ +Subproject commit 40de55eb8738605310d096e3adf252e7b88e4cf9 diff --git a/src/CleanArchitecture.Api/Controllers/RemindersController.cs b/src/CleanArchitecture.Api/Controllers/RemindersController.cs index 44151d2..79ac103 100644 --- a/src/CleanArchitecture.Api/Controllers/RemindersController.cs +++ b/src/CleanArchitecture.Api/Controllers/RemindersController.cs @@ -6,6 +6,8 @@ using CleanArchitecture.Contracts.Reminders; using CleanArchitecture.Domain.Reminders; +using ErrorOr; + using MediatR; using Microsoft.AspNetCore.Mvc; @@ -16,67 +18,41 @@ namespace CleanArchitecture.Api.Controllers; public class RemindersController(ISender _mediator) : ApiController { [HttpPost] - public async Task CreateReminder(Guid userId, Guid subscriptionId, CreateReminderRequest request) - { - var command = new SetReminderCommand(userId, subscriptionId, request.Text, request.DateTime.UtcDateTime); - - var result = await _mediator.Send(command); - - return result.Match( - reminder => CreatedAtAction( - actionName: nameof(GetReminder), - routeValues: new { UserId = userId, SubscriptionId = subscriptionId, ReminderId = reminder.Id }, - value: ToDto(reminder)), - Problem); - } + public async Task CreateReminder(Guid userId, Guid subscriptionId, CreateReminderRequest request) => + await new SetReminderCommand(userId, subscriptionId, request.Text, request.DateTime.UtcDateTime).ToErrorOr() + .ThenAsync(command => _mediator.Send(command)) + .Then(ToDto) + .Match( + reminderResponse => CreatedAtAction( + actionName: nameof(GetReminder), + routeValues: new { UserId = userId, SubscriptionId = subscriptionId, ReminderId = reminderResponse.Id }, + value: reminderResponse), + Problem); [HttpPost("{reminderId:guid}/dismiss")] - public async Task DismissReminder(Guid userId, Guid subscriptionId, Guid reminderId) - { - var command = new DismissReminderCommand(userId, subscriptionId, reminderId); - - var result = await _mediator.Send(command); - - return result.Match( - _ => NoContent(), - Problem); - } + public async Task DismissReminder(Guid userId, Guid subscriptionId, Guid reminderId) => + await new DismissReminderCommand(userId, subscriptionId, reminderId).ToErrorOr() + .ThenAsync(command => _mediator.Send(command)) + .Match(_ => NoContent(), Problem); [HttpDelete("{reminderId:guid}")] - public async Task DeleteReminder(Guid userId, Guid subscriptionId, Guid reminderId) - { - var command = new DeleteReminderCommand(userId, subscriptionId, reminderId); - - var result = await _mediator.Send(command); - - return result.Match( - _ => NoContent(), - Problem); - } + public async Task DeleteReminder(Guid userId, Guid subscriptionId, Guid reminderId) => + await new DeleteReminderCommand(userId, subscriptionId, reminderId).ToErrorOr() + .ThenAsync(command => _mediator.Send(command)) + .Match(_ => NoContent(), Problem); [HttpGet("{reminderId:guid}")] - public async Task GetReminder(Guid userId, Guid subscriptionId, Guid reminderId) - { - var query = new GetReminderQuery(userId, subscriptionId, reminderId); - - var result = await _mediator.Send(query); - - return result.Match( - reminder => Ok(ToDto(reminder)), - Problem); - } + public async Task GetReminder(Guid userId, Guid subscriptionId, Guid reminderId) => + await new GetReminderQuery(userId, subscriptionId, reminderId).ToErrorOr() + .ThenAsync(query => _mediator.Send(query)) + .Match(Ok, Problem); [HttpGet] - public async Task ListReminders(Guid userId, Guid subscriptionId) - { - var query = new ListRemindersQuery(userId, subscriptionId); - - var result = await _mediator.Send(query); - - return result.Match( - reminders => Ok(reminders.ConvertAll(ToDto)), - Problem); - } + public async Task ListReminders(Guid userId, Guid subscriptionId) => + await new ListRemindersQuery(userId, subscriptionId).ToErrorOr() + .ThenAsync(query => _mediator.Send(query)) + .Then(reminders => reminders.ConvertAll(ToDto)) + .Match(Ok, Problem); private ReminderResponse ToDto(Reminder reminder) => new(reminder.Id, reminder.Text, reminder.DateTime, reminder.IsDismissed); diff --git a/src/CleanArchitecture.Api/Controllers/SubscriptionsController.cs b/src/CleanArchitecture.Api/Controllers/SubscriptionsController.cs index a3a2041..8e3de39 100644 --- a/src/CleanArchitecture.Api/Controllers/SubscriptionsController.cs +++ b/src/CleanArchitecture.Api/Controllers/SubscriptionsController.cs @@ -4,6 +4,8 @@ using CleanArchitecture.Application.Subscriptions.Queries.GetSubscription; using CleanArchitecture.Contracts.Subscriptions; +using ErrorOr; + using MediatR; using Microsoft.AspNetCore.Mvc; @@ -17,55 +19,35 @@ namespace CleanArchitecture.Api.Controllers; public class SubscriptionsController(IMediator _mediator) : ApiController { [HttpPost] - public async Task CreateSubscription(Guid userId, CreateSubscriptionRequest request) - { - if (!DomainSubscriptionType.TryFromName(request.SubscriptionType.ToString(), out var subscriptionType)) - { - return Problem( - statusCode: StatusCodes.Status400BadRequest, - detail: "Invalid plan type"); - } - - var command = new CreateSubscriptionCommand( - userId, - request.FirstName, - request.LastName, - request.Email, - subscriptionType); - - var result = await _mediator.Send(command); - - return result.Match( - subscription => CreatedAtAction( - actionName: nameof(GetSubscription), - routeValues: new { UserId = userId }, - value: ToDto(subscription)), - Problem); - } + public async Task CreateSubscription(Guid userId, CreateSubscriptionRequest request) => + await DomainSubscriptionType.TryFromName(request.SubscriptionType.ToString(), out var subscriptionType).ToErrorOr() + .FailIf(val => val is false, Error.Validation("Invalid plan type")) + .Then(_ => new CreateSubscriptionCommand( + userId, + request.FirstName, + request.LastName, + request.Email, + subscriptionType)) + .ThenAsync(command => _mediator.Send(command)) + .Match( + subscription => CreatedAtAction( + actionName: nameof(GetSubscription), + routeValues: new { UserId = userId }, + value: ToDto(subscription)), + Problem); [HttpDelete("{subscriptionId:guid}")] - public async Task DeleteSubscription(Guid userId, Guid subscriptionId) - { - var command = new CancelSubscriptionCommand(userId, subscriptionId); - - var result = await _mediator.Send(command); - - return result.Match( - _ => NoContent(), - Problem); - } + public async Task DeleteSubscription(Guid userId, Guid subscriptionId) => + await new CancelSubscriptionCommand(userId, subscriptionId).ToErrorOr() + .ThenAsync(command => _mediator.Send(command)) + .Match(_ => NoContent(), Problem); [HttpGet] - public async Task GetSubscription(Guid userId) - { - var query = new GetSubscriptionQuery(userId); - - var result = await _mediator.Send(query); - - return result.Match( - user => Ok(ToDto(user)), - Problem); - } + public async Task GetSubscription(Guid userId) => + await new GetSubscriptionQuery(userId).ToErrorOr() + .ThenAsync(query => _mediator.Send(query)) + .Then(ToDto) + .Match(Ok, Problem); private static SubscriptionType ToDto(DomainSubscriptionType subscriptionType) => subscriptionType.Name switch diff --git a/src/CleanArchitecture.Api/Controllers/TokensController.cs b/src/CleanArchitecture.Api/Controllers/TokensController.cs index f172eeb..3fb7b66 100644 --- a/src/CleanArchitecture.Api/Controllers/TokensController.cs +++ b/src/CleanArchitecture.Api/Controllers/TokensController.cs @@ -3,6 +3,8 @@ using CleanArchitecture.Contracts.Common; using CleanArchitecture.Contracts.Tokens; +using ErrorOr; + using MediatR; using Microsoft.AspNetCore.Authorization; @@ -17,41 +19,29 @@ namespace CleanArchitecture.Api.Controllers; public class TokensController(ISender _mediator) : ApiController { [HttpPost("generate")] - public async Task GenerateToken(GenerateTokenRequest request) - { - if (!DomainSubscriptionType.TryFromName(request.SubscriptionType.ToString(), out var plan)) - { - return Problem( - statusCode: StatusCodes.Status400BadRequest, - detail: "Invalid subscription type"); - } - - var query = new GenerateTokenQuery( - request.Id, - request.FirstName, - request.LastName, - request.Email, - plan, - request.Permissions, - request.Roles); - - var result = await _mediator.Send(query); - - return result.Match( - generateTokenResult => Ok(ToDto(generateTokenResult)), - Problem); - } - - private static TokenResponse ToDto(GenerateTokenResult authResult) - { - return new TokenResponse( + public async Task GenerateToken(GenerateTokenRequest request) => + await DomainSubscriptionType.TryFromName(request.SubscriptionType.ToString(), out var plan).ToErrorOr() + .FailIf(val => val is false, Error.Validation("Invalid subscription type")) + .Then(_ => new GenerateTokenQuery( + request.Id, + request.FirstName, + request.LastName, + request.Email, + plan, + request.Permissions, + request.Roles)) + .ThenAsync(query => _mediator.Send(query)) + .Then(ToDto) + .Match(Ok, Problem); + + private static ErrorOr ToDto(GenerateTokenResult authResult) => + new TokenResponse( authResult.Id, authResult.FirstName, authResult.LastName, authResult.Email, ToDto(authResult.SubscriptionType), authResult.Token); - } private static SubscriptionType ToDto(DomainSubscriptionType subscriptionType) => subscriptionType.Name switch diff --git a/src/CleanArchitecture.Application/Reminders/Commands/DeleteReminder/DeleteReminderCommandHandler.cs b/src/CleanArchitecture.Application/Reminders/Commands/DeleteReminder/DeleteReminderCommandHandler.cs index 72fb4b7..52ce332 100644 --- a/src/CleanArchitecture.Application/Reminders/Commands/DeleteReminder/DeleteReminderCommandHandler.cs +++ b/src/CleanArchitecture.Application/Reminders/Commands/DeleteReminder/DeleteReminderCommandHandler.cs @@ -1,4 +1,6 @@ using CleanArchitecture.Application.Common.Interfaces; +using CleanArchitecture.Domain.Reminders; +using CleanArchitecture.Domain.Users; using ErrorOr; @@ -12,24 +14,14 @@ public class DeleteReminderCommandHandler( { public async Task> Handle(DeleteReminderCommand request, CancellationToken cancellationToken) { - var reminder = await _remindersRepository.GetByIdAsync(request.ReminderId, cancellationToken); - - var user = await _usersRepository.GetByIdAsync(request.UserId, cancellationToken); - - if (reminder is null || user is null) - { - return Error.NotFound(description: "Reminder not found"); - } - - var deleteReminderResult = user.DeleteReminder(reminder); - - if (deleteReminderResult.IsError) - { - return deleteReminderResult.Errors; - } - - await _usersRepository.UpdateAsync(user, cancellationToken); - - return Result.Success; + Reminder? reminder = await _remindersRepository.GetByIdAsync(request.ReminderId, cancellationToken); + User? user = await _usersRepository.GetByIdAsync(request.UserId, cancellationToken); + + return await (Reminder: reminder, User: user).ToErrorOr() + .FailIf(pair => pair.Reminder is null || pair.User is null, Error.NotFound("Reminder not found")) + .Then(pair => (Reminder: pair.Reminder!, User: pair.User!)) + .Then(pair => pair.User.DeleteReminder(pair.Reminder).Then(success => pair)) + .ThenDoAsync(pair => _usersRepository.UpdateAsync(pair.User, cancellationToken)) + .Then(_ => Result.Success); } } \ No newline at end of file diff --git a/src/CleanArchitecture.Application/Reminders/Commands/DismissReminder/DismissReminderCommandHandler.cs b/src/CleanArchitecture.Application/Reminders/Commands/DismissReminder/DismissReminderCommandHandler.cs index 908e193..f93c814 100644 --- a/src/CleanArchitecture.Application/Reminders/Commands/DismissReminder/DismissReminderCommandHandler.cs +++ b/src/CleanArchitecture.Application/Reminders/Commands/DismissReminder/DismissReminderCommandHandler.cs @@ -6,28 +6,13 @@ namespace CleanArchitecture.Application.Reminders.Commands.DismissReminder; -public class DismissReminderCommandHandler( - IUsersRepository _usersRepository) - : IRequestHandler> +public class DismissReminderCommandHandler(IUsersRepository _usersRepository) + : IRequestHandler> { - public async Task> Handle(DismissReminderCommand request, CancellationToken cancellationToken) - { - var user = await _usersRepository.GetByIdAsync(request.UserId, cancellationToken); - - if (user is null) - { - return Error.NotFound(description: "Reminder not found"); - } - - var dismissReminderResult = user.DismissReminder(request.ReminderId); - - if (dismissReminderResult.IsError) - { - return dismissReminderResult.Errors; - } - - await _usersRepository.UpdateAsync(user, cancellationToken); - - return Result.Success; - } -} + public async Task> Handle(DismissReminderCommand request, CancellationToken cancellationToken) => + await (await _usersRepository.GetByIdAsync(request.UserId, cancellationToken)).ToErrorOr() + .FailIf(user => user is null, Error.NotFound("User not found")) + .Then(user => user!.DismissReminder(request.ReminderId).Then(success => user!)) + .ThenDoAsync(user => _usersRepository.UpdateAsync(user, cancellationToken)) + .Then(_ => Result.Success); +} \ No newline at end of file diff --git a/src/CleanArchitecture.Application/Reminders/Commands/SetReminder/SetReminderCommandHandler.cs b/src/CleanArchitecture.Application/Reminders/Commands/SetReminder/SetReminderCommandHandler.cs index 0ce7764..4328563 100644 --- a/src/CleanArchitecture.Application/Reminders/Commands/SetReminder/SetReminderCommandHandler.cs +++ b/src/CleanArchitecture.Application/Reminders/Commands/SetReminder/SetReminderCommandHandler.cs @@ -12,28 +12,20 @@ public class SetReminderCommandHandler(IUsersRepository _usersRepository) { public async Task> Handle(SetReminderCommand command, CancellationToken cancellationToken) { - var reminder = new Reminder( - command.UserId, - command.SubscriptionId, - command.Text, - command.DateTime); - - var user = await _usersRepository.GetBySubscriptionIdAsync(command.SubscriptionId, cancellationToken); - - if (user is null) - { - return Error.NotFound(description: "Subscription not found"); - } - - var setReminderResult = user.SetReminder(reminder); - - if (setReminderResult.IsError) - { - return setReminderResult.Errors; - } - - await _usersRepository.UpdateAsync(user, cancellationToken); - - return reminder; + return await (await _usersRepository.GetBySubscriptionIdAsync(command.SubscriptionId, cancellationToken)).ToErrorOr() + .FailIf(user => user is null, Error.NotFound(description: "Subscription not found")) + .Then(user => + { + var reminder = new Reminder( + command.UserId, + command.SubscriptionId, + command.Text, + command.DateTime); + + return user!.SetReminder(reminder) + .Then(success => (User: user!, Reminder: reminder)); + }) + .ThenDoAsync(pair => _usersRepository.UpdateAsync(pair.User, cancellationToken)) + .Then(pair => pair.Reminder); } } \ No newline at end of file diff --git a/src/CleanArchitecture.Application/Reminders/Events/ReminderDeletedEventHandler.cs b/src/CleanArchitecture.Application/Reminders/Events/ReminderDeletedEventHandler.cs index 1725a42..01118cf 100644 --- a/src/CleanArchitecture.Application/Reminders/Events/ReminderDeletedEventHandler.cs +++ b/src/CleanArchitecture.Application/Reminders/Events/ReminderDeletedEventHandler.cs @@ -1,6 +1,8 @@ using CleanArchitecture.Application.Common.Interfaces; using CleanArchitecture.Domain.Users.Events; +using ErrorOr; + using MediatR; namespace CleanArchitecture.Application.Reminders.Events; @@ -9,9 +11,10 @@ public class ReminderDeletedEventHandler(IRemindersRepository _remindersReposito { public async Task Handle(ReminderDeletedEvent notification, CancellationToken cancellationToken) { - var reminder = await _remindersRepository.GetByIdAsync(notification.ReminderId, cancellationToken) - ?? throw new InvalidOperationException(); - - await _remindersRepository.RemoveAsync(reminder, cancellationToken); + await (await _remindersRepository.GetByIdAsync(notification.ReminderId, cancellationToken)).ToErrorOr() + .FailIf(reminder => reminder is null, Error.Unexpected()) + .SwitchAsync( + reminder => _remindersRepository.RemoveAsync(reminder!, cancellationToken), + _ => throw new InvalidOperationException()); } } diff --git a/src/CleanArchitecture.Application/Reminders/Events/ReminderDismissedEventHandler.cs b/src/CleanArchitecture.Application/Reminders/Events/ReminderDismissedEventHandler.cs index 88cc760..ca1835f 100644 --- a/src/CleanArchitecture.Application/Reminders/Events/ReminderDismissedEventHandler.cs +++ b/src/CleanArchitecture.Application/Reminders/Events/ReminderDismissedEventHandler.cs @@ -1,6 +1,8 @@ using CleanArchitecture.Application.Common.Interfaces; using CleanArchitecture.Domain.Users.Events; +using ErrorOr; + using MediatR; namespace CleanArchitecture.Application.Reminders.Events; @@ -9,11 +11,9 @@ public class ReminderDismissedEventHandler(IRemindersRepository _remindersReposi { public async Task Handle(ReminderDismissedEvent notification, CancellationToken cancellationToken) { - var reminder = await _remindersRepository.GetByIdAsync(notification.ReminderId, cancellationToken) - ?? throw new InvalidOperationException(); - - reminder.Dismiss(); - - await _remindersRepository.UpdateAsync(reminder, cancellationToken); + await (await _remindersRepository.GetByIdAsync(notification.ReminderId, cancellationToken)).ToErrorOr() + .FailIf(reminder => reminder is null, Error.Unexpected()) + .Then(reminder => reminder!.Dismiss().Then(success => reminder!)) + .ThenDoAsync(reminder => _remindersRepository.UpdateAsync(reminder, cancellationToken)); } } diff --git a/src/CleanArchitecture.Application/Reminders/Queries/GetReminder/GetReminderQueryHandler.cs b/src/CleanArchitecture.Application/Reminders/Queries/GetReminder/GetReminderQueryHandler.cs index e14e55b..15ac50d 100644 --- a/src/CleanArchitecture.Application/Reminders/Queries/GetReminder/GetReminderQueryHandler.cs +++ b/src/CleanArchitecture.Application/Reminders/Queries/GetReminder/GetReminderQueryHandler.cs @@ -12,13 +12,8 @@ public class GetReminderQueryHandler(IRemindersRepository _remindersRepository) { public async Task> Handle(GetReminderQuery query, CancellationToken cancellationToken) { - var reminder = await _remindersRepository.GetByIdAsync(query.ReminderId, cancellationToken); - - if (reminder?.UserId != query.UserId) - { - return Error.NotFound(description: "Reminder not found"); - } - - return reminder; + return (await _remindersRepository.GetByIdAsync(query.ReminderId, cancellationToken)).ToErrorOr() + .FailIf(reminder => reminder?.UserId != query.UserId, Error.NotFound(description: "Reminder not found")) + .Then(reminder => reminder!); } } \ No newline at end of file diff --git a/src/CleanArchitecture.Application/Subscriptions/Commands/CancelSubscription/CancelSubscriptionCommandHandler.cs b/src/CleanArchitecture.Application/Subscriptions/Commands/CancelSubscription/CancelSubscriptionCommandHandler.cs index 59d2c76..704a0d6 100644 --- a/src/CleanArchitecture.Application/Subscriptions/Commands/CancelSubscription/CancelSubscriptionCommandHandler.cs +++ b/src/CleanArchitecture.Application/Subscriptions/Commands/CancelSubscription/CancelSubscriptionCommandHandler.cs @@ -9,24 +9,10 @@ namespace CleanArchitecture.Application.Subscriptions.Commands.CancelSubscriptio public class CancelSubscriptionCommandHandler(IUsersRepository _usersRepository) : IRequestHandler> { - public async Task> Handle(CancelSubscriptionCommand request, CancellationToken cancellationToken) - { - var user = await _usersRepository.GetByIdAsync(request.UserId, cancellationToken); - - if (user is null) - { - return Error.NotFound(description: "User not found"); - } - - var deleteSubscriptionResult = user.CancelSubscription(request.SubscriptionId); - - if (deleteSubscriptionResult.IsError) - { - return deleteSubscriptionResult.Errors; - } - - await _usersRepository.UpdateAsync(user, cancellationToken); - - return Result.Success; - } + public async Task> Handle(CancelSubscriptionCommand request, CancellationToken cancellationToken) => + await (await _usersRepository.GetByIdAsync(request.UserId, cancellationToken)).ToErrorOr() + .FailIf(user => user is null, Error.NotFound(description: "User not found")) + .Then(user => user!.CancelSubscription(request.SubscriptionId).Then(success => user!)) + .ThenDoAsync(user => _usersRepository.UpdateAsync(user, cancellationToken)) + .Then(_ => Result.Success); } diff --git a/src/CleanArchitecture.Application/Subscriptions/Commands/CreateSubscription/CreateSubscriptionCommandHandler.cs b/src/CleanArchitecture.Application/Subscriptions/Commands/CreateSubscription/CreateSubscriptionCommandHandler.cs index cbaa03e..ad3931d 100644 --- a/src/CleanArchitecture.Application/Subscriptions/Commands/CreateSubscription/CreateSubscriptionCommandHandler.cs +++ b/src/CleanArchitecture.Application/Subscriptions/Commands/CreateSubscription/CreateSubscriptionCommandHandler.cs @@ -12,24 +12,22 @@ namespace CleanArchitecture.Application.Subscriptions.Commands.CreateSubscriptio public class CreateSubscriptionCommandHandler( IUsersRepository _usersRepository) : IRequestHandler> { - public async Task> Handle(CreateSubscriptionCommand request, CancellationToken cancellationToken) - { - if (await _usersRepository.GetByIdAsync(request.UserId, cancellationToken) is not null) - { - return Error.Conflict(description: "User already has an active subscription"); - } - - var subscription = new Subscription(request.SubscriptionType); - - var user = new User( - request.UserId, - request.FirstName, - request.LastName, - request.Email, - subscription); - - await _usersRepository.AddAsync(user, cancellationToken); - - return SubscriptionResult.FromUser(user); - } + public async Task> Handle(CreateSubscriptionCommand request, CancellationToken cancellationToken) => + await (await _usersRepository.GetByIdAsync(request.UserId, cancellationToken)).ToErrorOr() + .FailIf(user => user is not null, Error.Conflict(description: "User already has an active subscription")) + .Then(_ => + { + var subscription = new Subscription(request.SubscriptionType); + + var user = new User( + request.UserId, + request.FirstName, + request.LastName, + request.Email, + subscription); + + return (Subscription: subscription, User: user); + }) + .ThenDoAsync(pair => _usersRepository.AddAsync(pair.User, cancellationToken)) + .Then(pair => SubscriptionResult.FromUser(pair.User)); } diff --git a/src/CleanArchitecture.Application/Subscriptions/Queries/GetSubscription/GetSubscriptionQueryHandler.cs b/src/CleanArchitecture.Application/Subscriptions/Queries/GetSubscription/GetSubscriptionQueryHandler.cs index 8b084f1..fed39d2 100644 --- a/src/CleanArchitecture.Application/Subscriptions/Queries/GetSubscription/GetSubscriptionQueryHandler.cs +++ b/src/CleanArchitecture.Application/Subscriptions/Queries/GetSubscription/GetSubscriptionQueryHandler.cs @@ -13,8 +13,8 @@ public class GetSubscriptionQueryHandler(IUsersRepository _usersRepository) { public async Task> Handle(GetSubscriptionQuery request, CancellationToken cancellationToken) { - return await _usersRepository.GetByIdAsync(request.UserId, cancellationToken) is User user - ? SubscriptionResult.FromUser(user) - : Error.NotFound(description: "Subscription not found."); + return (await _usersRepository.GetByIdAsync(request.UserId, cancellationToken)).ToErrorOr() + .FailIf(user => user is null, Error.NotFound(description: "Subscription not found.")) + .Then(user => SubscriptionResult.FromUser(user!)); } } diff --git a/src/CleanArchitecture.Domain/CleanArchitecture.Domain.csproj b/src/CleanArchitecture.Domain/CleanArchitecture.Domain.csproj index 9813ac5..5205b72 100644 --- a/src/CleanArchitecture.Domain/CleanArchitecture.Domain.csproj +++ b/src/CleanArchitecture.Domain/CleanArchitecture.Domain.csproj @@ -8,9 +8,12 @@ - + + + +