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

Can't find an example with CleanupFailedCartRequest #169

Closed
dmitribodiu opened this issue May 20, 2020 · 1 comment
Closed

Can't find an example with CleanupFailedCartRequest #169

dmitribodiu opened this issue May 20, 2020 · 1 comment

Comments

@dmitribodiu
Copy link

Can you pls show how to compensate in case on of the handlers fail to process the request? What would be the valid reason to fail? business exception? Request contains invalid data? Or should we strive to make sure our command is valid in 99% and dont perform any compensation requests?

@mauroservienti
Copy link
Owner

mauroservienti commented Jun 8, 2020

Hi again 😄 and thanks for having waited for me.

Can you pls show how to compensate in case on of the handlers fail to process the request? What would be the valid reason to fail? business exception? The request contains invalid data? Or should we strive to make sure our command is valid in 99% and dont perform any compensation requests?

In this specific sample, there isn't really any scenario that comes to my mind that can lead to a failed request. That's the main reason that leads me to entirely remove the whole CleanupFailedCartRequest thing in #101 and to update the talk to mention the new way. The latest version of the slides (and a recording) is available here: https://milestone.topics.it/events/all-our-aggregates-are-wrong-webinar.html

In summary, the previous approach on HTTP requests, and when in need of adding a new item to the cart the gateway had to orchestrate across 3 different outgoing POST requests, each one to a different service. If something goes badly in such a scenario there is no way for the gateway to make sure all requests are rolled back. HTTP does not provide (luckily) any transaction support.
A better way to approach the problem is to entirely remove the need for orchestration, this is achieved by having the gateway offloading the incoming HTTP request to add a new 'em to the cart to a queue (sending the message to itself):

public async Task Handle(string requestId, dynamic vm, RouteData routeData, HttpRequest request)
{
var requestData = new Dictionary<string, string>()
{
{ "sales-product-id", (string)routeData.Values["id"] },
{ "sales-quantity", request.Form["quantity"][0] },
};
await vm.RaiseEvent(new AddItemToCartRequested()
{
CartId = request.Cookies["cart-id"],
RequestId = requestId,
RequestData = requestData
});
await messageSession.SendLocal(new AddToCartRequest()
{
RequestId = requestId,
CartId = new Guid(request.Cookies["cart-id"]),
RequestData = requestData });
}

This means that one incoming HTTP request maps to one ongoing queue operation. And this is fine, if something goes badly at any level we can compensate. Once the message-to-self is processed it's handled by all the interested parties. For example, this is the Sales implementation:

class AddToCartRequestHandler : IHandleMessages<AddToCartRequest>
{
public Task Handle(AddToCartRequest message, IMessageHandlerContext context)
{
return context.Send("Sales.Service", new AddItemToCart()
{
RequestId = message.RequestId,
CartId = message.CartId,
ProductId = int.Parse(message.RequestData["sales-product-id"]),
Quantity = int.Parse(message.RequestData["sales-quantity"]),
});
}
}

(you can find in the repo similar implementations for all the interested services)

Now, the interesting change is that we're now dealing with an incoming message that needs to be translated into multiple outgoing messages. If something goes badly when sending one of the messages, the incoming message is retried until all the outgoing messages are successfully sent. The only downside to this is that in case of partial failure it leads to message duplication, but message deduplication can be solved at the receiver level by using the outbox pattern.

(The outbox pattern is not implemented in this demo as it's outside the scope of the demo/talk.)

Is there any downside in moving away from HTTP to messaging? Sure, it's always a tradeoff. In this case:

  • HTTP is synchronous and this has the benefit of simplifying the overall UX, when you get the HTTP response you're pretty sure the item is in the cart
  • Messaging is asynchronous and this has the benefit of entirely removing a set of problems at the UX cost. The UI now needs to deal with the option that items in the cart can be "late"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants