Skip to content

Instantly share code, notes, and snippets.

@dasjestyr
Last active October 16, 2018 18:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dasjestyr/08576f463a238fd0c9ce566cadc2b7a0 to your computer and use it in GitHub Desktop.
Save dasjestyr/08576f463a238fd0c9ce566cadc2b7a0 to your computer and use it in GitHub Desktop.

Problem

Because the backend system is asynchronous over a message bus (NSB with RMQ), it is difficult to receive feedback on whether or not something has succeeded.

Sample scenario

  • Client UI issues a command to the API "Create new user".

In a traditional synchronous system, the client would make the request and then wait until it got a response before continuing. All steps to create a user would be completed before responding to the request with a 201 Created . In the asynchronous system, the commands are accepted from the client and then dropped on the bus and the server replies with a 202 Accepted. If there are any failures down the line, the UI does not know.

Typically, the standard solution for this is for the edge client to send the message and then immediately subscribe to response queue where it waits for the system to give it feedback. Alternatively, a popular solution is to have the server communicate this information over WebSocket which is what I am trying to do.

The diagram illustrates the following

  • A user issues a command to "Command Service"
  • "Command Service" distributes a process (optionally starts a saga) to 3 other services
  • 2 of those 3 services succeed but one throws an error. The saga can manage this error and roll this back or assume that all services will retry, but let's assume that the saga times out before the failing service can recover. The transaction is safe/consistent but this doesn't help the end user know that a problem occurred.
  • Since topics aren't an option, we leverage inheritance in NSB. All messages that implement IUpdateUI will also be written to an exchange called IUpdateUI to which our SignalR hub is bound. Our hub will receive that failure message and forward it on to the UI Client.

Additional:

  • The forwarding logic in the SignalR hub shouldn't really care about the message or it's origin or even it's content.
  • The hub may care about some headers (e.g. which user to send the message to and what task/correlation it has) that help identify the context back to the UI so that the UI knows what the message is in regards to.

Blockers

  • IHandleMessages forces T to match Handle(T message) - this type of type safety is problematic because:
    • T is the mechanism used by NSB to know what exchange it should subscribe to
    • Since T is an interface, it will only work if the interface shape matches the message shape

Potential Solutions

  • Create a standardized format that is modeled in IUpdateUI
    • The problem with this is that if we want the complete body of the message, the only way to make it flexible enough is to have a property that contains a serialized string representation of the event itself.
    • The implementation of this is really janky. The idea is to have an easy way to go back and simply mark an event with the interface to have it copied to additional exchanges without having to write serialization logic into messages which are just supposed to be simple DTOs.
  • Figure out a way to specify a more dynamic object into which a message will be deserialized while also being able to subscribe to the correct exchange
    • Subvert the "magic" handler registration by manually telling NSB what to do. For example, optional interface with no generic value:
public interface IHandleMessages
{
    Task Handle(object message, IMessageHandlerContext context);
}

public class UiMessageHandler : IHandleMessages
{
    public Task Handle(object message, IMessageHandlerContext context)
    {
        // do my thing
    }
}

// config
endpoint.Subscribe<IUpdateUI, UiMessageHandler>();

Then deserialization could be to dynamic. Optionally make the handle signature contain dynamic instead of object.

@andreasohlund
Copy link

andreasohlund commented Oct 16, 2018

You could do this in a different way.

  1. On all messages that needs "UI" feedback add 2 headers, UIRequestId, UIFeedbackEndpointAddress
  2. Create a behavior that you deploy to all endpoints that
    2.1 Checks for the existence of those headers
    2.2 If no exception happens sends a "UIFeedbackMessage" to the address in the header passing on the ID in the other header
    2.3 Optionally you could send failure messages using the same logic but hook that up to https://docs.particular.net/nservicebus/recoverability/subscribing-to-error-notifications

You could bundle all this in a assembly that you just drop into all endpoint at deploy time. Thoughts?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment