0

I'm looking for some advice/recomendations on structuring an application which involved multiple classes that need access to each other for the sole purpose of tasking (i.e. class A needs to pass something to class B for processing, but class A doesn't care about the result. Class B may later produce a result which needs to be passed to class A for transmission over a network connection).

The application I'm designing (in C#) consists of two primary I/O classes: a web socket client and a named pipe server, both of which receive and send various messages from/to their connected clients.

Received messages need to be passed to one of many (3-4 at this stage but this may grow) types of processors depending on the message type. These processors may generate responses at a later stage at which point these processers will need to send data via either the websocket or the named pipe connection.

The only real limitation I have is the messages must be processed in the order they are received.

Given this setup, what would be a good way to pass tasking around my application?

I've been tossing up several options so far:

Option 1 - References everywhere

Not ideal. In this situation, when my application starts it would create a websocket client, named pipe server, and all processor classes then pass all required references via setters (taskProcessor1.webSocketHandler = webSocketHandler; etc). This feels like a horrible solution and is not scalable.

Option 2 - Static classes

This could work if I place my websocket, named pipe and each processor type behind a static manager class of some kind which would accept tasking and pass it to various instances under it but this feels like one of those abuse of staic classes situations.

Option 3 - Event Handlers

Using .NET's event handlers works (this is my currently working solution) however its not that nice. You essentially end up with either one over arching class which instantiates your IO and processor class instances, then wires up all the event handlers or event handlers calling event handlers to bubble and event up and down your class hirachy.

Option 4 - MediatR

MediatR looks like a potential solution to this issue and should remove the need for the various IO classes to ever need to know about the processors.

Thank you for any recommendations you can provide, I've been thinking about this for days now and am going around in circles on the merits and downsides of each option.

1
  • 2
    Get 4 pieces of paper. At the top of each paper, write one of the options. Draw a line vertically down the center of the paper or fold it in half so that you have a straight line that divides the paper into two columns. At the top of the left column, write "pros;" at the top of the right column write "cons." Write all of the pros and cons in the correct column of the correct piece of paper. Avoid terms like "good," "horrible," "scalable," "abuse," "nice," "ideal" and "potential" unless you can define those terms more specifically. Commented Mar 29, 2023 at 12:26

2 Answers 2

1

I don't know how familiar you are with ASP.NET, because while it's not the same thing it faces many of the same challenges: requests come in and have to be routed to services.

You are going to end up with an object in the middle which does all the dispatching. Whether this is MediatR or a mediator class of your own. However, if you use the service concept and dependency injection, this becomes more manageable. If you have it instantiated as a singleton at startup, then each of the request sources (named pipe and websocket) can request it from the DI framework and associate themselves with it. Likewise all the handlers can register as consumers.

Having it instantiated as an object deals with your "abuse of static classes" distinction, and "using DI" deals with the problems traditionally associated with global singletons.

0

An obvious candidate is using some type of message passing. A potential downside with this, and most concurrent solutions, is that it can be more difficult to debug if you do not have a nice, convenient callstack to trace problems back to the source.

The Dataflow library might be suitable, it is designed to setup pipelines, where messages flow between "blocks" according to rules you can configure. A core feature of this is the ability to process message concurrently by different blocks, and also allow optional synchronization if you want to update the UI or similar, synchronized blocks should process messages in order.

There is Reactive Extensions (Rx) that models events in a somewhat similar way as IEnumerable<T>, with support for LINQ operators and such. But I have not used it and cannot provide commentary.

I have also heard about akka.Net, that uses an actor model. But I have not used it.

You might also want to check out dependency injection container if you are not aware of that idea. That can let you declare types like

public class MyProcessor : IMessageProcessor<SomeMessage>
    IMessageSender<MyResultType> sender;
    public MyProcessor(IMessageSender<MyResultType> sender) => this.sender = sender;
    public void Process(SomeMessage message){
        // do processing
        sender.Send(result);
    }
}

The core idea here is that you register all your types in a container object. You can then ask the container for objects, like all message processors for SomeMessage, and the container will create all the objects needed for that. This can make it simpler to create a large and complex hierarchy of objects.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.