Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

StateMachine instead of compensation #15

Open
wants to merge 4 commits into
base: Compensation
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using MediatR;

namespace Shop.Communication.UseCases.Emails.Commands.ScheduleEmail
{
internal class ScheduleEmailCommand : IRequest
{
public string Address { get; set; }
public string Subject { get; set; }
public string Body { get; set; }
public int UserId { get; set; }
public int OrderId { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System.Threading;
using System.Threading.Tasks;
using AutoMapper;
using MediatR;
using Shop.Communication.DataAccess.Interfaces;
using Shop.Communication.Entities;

namespace Shop.Communication.UseCases.Emails.Commands.ScheduleEmail
{
internal class ScheduleEmailCommandHandler : AsyncRequestHandler<ScheduleEmailCommand>
{
private readonly ICommunicationDbContext _dbContext;
private readonly IMapper _mapper;

public ScheduleEmailCommandHandler(ICommunicationDbContext dbContext, IMapper mapper)
{
_dbContext = dbContext;
_mapper = mapper;
}

protected override async Task Handle(ScheduleEmailCommand request, CancellationToken cancellationToken)
{
var email = _mapper.Map<Email>(request);

_dbContext.Emails.Add(email);

await _dbContext.SaveChangesAsync(cancellationToken);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using AutoMapper;
using Shop.Communication.Entities;
using Shop.Communication.UseCases.Emails.Commands.ScheduleEmail;
using Shop.Communication.UseCases.Emails.Dto;

namespace Shop.Communication.UseCases.Emails.Mappings
Expand All @@ -9,6 +10,7 @@ internal class EmailsAutoMapperProfile : Profile
public EmailsAutoMapperProfile()
{
CreateMap<Email, EmailDto>();
CreateMap<ScheduleEmailCommand, Email>();
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@

[assembly: InternalsVisibleTo("Shop.Communication.Contract.Implementation")]
[assembly: InternalsVisibleTo("Shop.Communication.Controllers")]
[assembly: InternalsVisibleTo("Shop.Web")]

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
using System.Threading.Tasks;
using MediatR;
using Microsoft.AspNetCore.Mvc;
using Shop.Order.UseCases.Orders.Commands.CreateOrder;
using Shop.Order.UseCases.Orders.Dto;
using Shop.Order.UseCases.Orders.Queries.GetOrder;

Expand All @@ -25,12 +24,5 @@ public async Task<ActionResult<OrderDto>> Get(int id, CancellationToken token)
{
return await _mediator.Send(new GetOrderRequest { Id = id }, token);
}

// POST api/orders
[HttpPost]
public async Task<ActionResult<int>> Post([FromBody] CreateOrderDto createOrderDto, CancellationToken token)
{
return await _mediator.Send(new CreateOrderRequest { CreateOrderDto = createOrderDto }, token);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using AutoMapper;
using MediatR;
using Shop.Framework.UseCases.Interfaces.Services;
using Shop.Order.Contract.Notifications;
using Shop.Order.DataAccess.Interfaces;

namespace Shop.Order.UseCases.Orders.Commands.CreateOrder
Expand All @@ -14,18 +13,15 @@ internal class CreateOrderRequestHandler : IRequestHandler<CreateOrderRequest, i
private readonly IOrderDbContext _dbContext;
private readonly IMapper _mapper;
private readonly ICurrentUserService _currentUserService;
private readonly IPublisher _publisher;

public CreateOrderRequestHandler(
IOrderDbContext dbContext,
IMapper mapper,
ICurrentUserService currentUserService,
IPublisher publisher)
ICurrentUserService currentUserService)
{
_dbContext = dbContext;
_mapper = mapper;
_currentUserService = currentUserService;
_publisher = publisher;
}

public async Task<int> Handle(CreateOrderRequest request, CancellationToken cancellationToken)
Expand All @@ -38,20 +34,6 @@ public async Task<int> Handle(CreateOrderRequest request, CancellationToken canc

await _dbContext.SaveChangesAsync(cancellationToken);

try
{
await _publisher.Publish(new OrderCreatedNotification(order.Id, _currentUserService.Id, _currentUserService.Email), cancellationToken);
}
catch (Exception e) //Compensation
{
//log exception

_dbContext.Orders.Remove(order);
await _dbContext.SaveChangesAsync(cancellationToken);

throw;
}

return order.Id;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using MediatR;

namespace Shop.Order.UseCases.Orders.Commands.DeleteOrder
{
internal class DeleteOrderCommand : IRequest
{
public int Id { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System.Threading;
using System.Threading.Tasks;
using MediatR;
using Shop.Framework.UseCases.Interfaces.Exceptions;
using Shop.Order.DataAccess.Interfaces;

namespace Shop.Order.UseCases.Orders.Commands.DeleteOrder
{
internal class DeleteOrderCommandHandler : AsyncRequestHandler<DeleteOrderCommand>
{
private readonly IOrderDbContext _dbContext;

public DeleteOrderCommandHandler(IOrderDbContext dbContext)
{
_dbContext = dbContext;
}

protected override async Task Handle(DeleteOrderCommand request, CancellationToken cancellationToken)
{
var order = await _dbContext.Orders.FindAsync(request.Id);

if (order == null) throw new EntityNotFoundException();

_dbContext.Orders.Remove(order);

await _dbContext.SaveChangesAsync(cancellationToken);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("Shop.Order.Controllers")]
[assembly: InternalsVisibleTo("Shop.Web")]

[assembly: InternalsVisibleTo("Shop.Tests.Unit")]
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,17 @@
using Shop.Communication.UseCases;
using Shop.Emails.Implementation;
using Shop.Framework.UseCases.Implementation;
using Shop.Framework.UseCases.Interfaces.Services;
using Shop.Order.Contract.Implementation;
using Shop.Order.DataAccess.MsSql;
using Shop.Order.UseCases;
using Shop.Order.UseCases.Orders.Commands.CreateOrder;
using Shop.Order.UseCases.Orders.Dto;
using Shop.Utils.Modules;
using Shop.Web.Utils;
using Shop.Web.StateMachines;
using Xunit;

namespace Shop.Tests.Unit
{
public class WorkflowTests
public class StateMachineTests
{
[Fact]
public async Task Should_Create_Order_And_Email()
Expand All @@ -43,13 +41,15 @@ public async Task Should_Create_Order_And_Email()

var (orderDbContext, communicationDbContext) = await CreateDatabase(connectionString);

var sender = serviceProvider.GetRequiredService<ISender>();
var stateMachine = serviceProvider.GetRequiredService<CreateOrderStateMachine>();
var dto = new CreateOrderDto { Items = new[] { new OrderItemDto { Count = 1, ProductId = 1 } } };

//act
var orderId = await sender.Send(new CreateOrderRequest { CreateOrderDto = dto });
var orderId = await stateMachine.RunAsync(dto, CancellationToken.None);

//assert
Assert.NotNull(orderId);

var order = await orderDbContext.Orders.FirstOrDefaultAsync(x => x.Id == orderId);
var email = await communicationDbContext.Emails.FirstOrDefaultAsync(x => x.OrderId == orderId);

Expand Down Expand Up @@ -79,13 +79,15 @@ public async Task Should_Not_Create_Order_And_Email_On_Error()

var (orderDbContext, communicationDbContext) = await CreateDatabase(connectionString);

var sender = serviceProvider.GetRequiredService<ISender>();
var stateMachine = serviceProvider.GetRequiredService<CreateOrderStateMachine>();
var dto = new CreateOrderDto { Items = new[] { new OrderItemDto { Count = 1, ProductId = 1 } } };

//act
await Assert.ThrowsAsync<Exception>(() => sender.Send(new CreateOrderRequest { CreateOrderDto = dto }));
var orderId = await stateMachine.RunAsync(dto, CancellationToken.None);

//assert
Assert.Null(orderId);

var ordersCount = await orderDbContext.Orders.CountAsync();
var emailsCount = await communicationDbContext.Emails.CountAsync();

Expand Down Expand Up @@ -160,6 +162,8 @@ private ServiceCollection CreateServiceProvider(IConfiguration configuration)
services.RegisterModule<OrderContractModule>(configuration);
services.RegisterModule<OrderUseCasesModule>(configuration);

services.AddScoped<CreateOrderStateMachine>();

return services;
}

Expand Down
22 changes: 22 additions & 0 deletions ModularMonolith/Shop.Web/Controllers/OrdersController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Shop.Order.UseCases.Orders.Dto;
using Shop.Web.StateMachines;

namespace Shop.Web.Controllers
{
[Route("api/[controller]")]
[ApiController]
internal class OrdersController : ControllerBase
{
[HttpPost]
public async Task<ActionResult<int>> Post([FromBody] CreateOrderDto createOrderDto, CancellationToken cancellationToken, [FromServices]CreateOrderStateMachine stateMachine)
{
var orderId = await stateMachine.RunAsync(createOrderDto, cancellationToken);
if (orderId == null) throw new Exception("Unable to create order");
return orderId;
}
}
}
5 changes: 5 additions & 0 deletions ModularMonolith/Shop.Web/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("Shop.Tests.Unit")]


1 change: 1 addition & 0 deletions ModularMonolith/Shop.Web/Shop.Web.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Appccelerate.StateMachine" Version="5.1.0" />
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="11.0.0" />
<PackageReference Include="FluentScheduler" Version="5.5.1" />
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="10.0.1" />
Expand Down
3 changes: 3 additions & 0 deletions ModularMonolith/Shop.Web/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
using Shop.Emails.Implementation;
using Shop.Framework.UseCases.Implementation;
using Shop.Utils.Modules;
using Shop.Web.StateMachines;

namespace Shop.Web
{
Expand Down Expand Up @@ -51,6 +52,8 @@ public void ConfigureServices(IServiceCollection services)
services.RegisterModule<OrderContractModule>(Configuration);
services.RegisterModule<OrderUseCasesModule>(Configuration);

services.AddScoped<CreateOrderStateMachine>();

var sp = services.BuildServiceProvider();

var requests = sp.GetServices<IBaseRequest>();
Expand Down
Loading