5 ms·
Yes, the controller consumes an instance of ChannelWriter<T> where T is a job type, and the service consumes an instance of ChannelReader<T>. With basic depende
by FroshKiller 3y ago
Yes, the controller consumes an instance of ChannelWriter<T> where T is a job type, and the service consumes an instance of ChannelReader<T>. With basic dependency injection, this means you have to set up & register different channels with different types if you need to coordinate with multiple services, but I think that's a small price to pay.
Having a job class with a type parameter for its encapsulated workload saves you from having to actually write different job classes, at least. It's trivial to write a generic extension method that takes the encapsulated type as a parameter, sets up the channel with your preferred options, and registers the reader & writer for DI. Then you have a one-liner for adding this kind of channel support to your projects.
- peheje 3y agoWhat are some of the benefits of this approach compared to calling an interface of your services returning Task<T> if needed from you controller? Some of the benefits of this in my view is: - Clear view of how many dependencies a controller has, though the dependencies are abstractions - Easy to Control+Click into the service in your IDE. - Easy to "Find all usages" of the service. - Easy to mock in unit tests, just mock the interface - Can be decorated if needed with Scrutor - Fast, just a method call - Leverages Task if async is required, easy to e.g. call a service get id returned to use to call another service. How would you do these things with ChannelWriter and ChannelReader, do you have an example of this approach somewhere?
- FroshKiller 3y agoI'm glad you asked, because I should clarify that my use case is "fire and forget" tasks. The controller is not interested in the result of the service's operation, so there is no need for any return value. Regarding your other points: Dependencies can be traced by the job type associated with the channel. It is somewhat indirect but no more indirect than an interface, really. Since I am not concerned with results, mocking is even easier. I just have a service implementation that consumes from a ChannelReader and discards the result. This is really a single class since it can take a type parameter if that makes sense--no need for separate mocks. I haven't used Scrutor, so I can't speak to that. Reading and writing from a channel are also just method calls. There are synchronous and asynchronous options for both. I don't have any open code to share, but I found the design very intuitive. If you read the documentation and follow your intuition, you will probably implement things exactly as I did. Also I am not trying to convince anyone that channels are the best answer! I just found them helpful in my projects and think they have not been promoted very well in the .NET sphere.