Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should IObservable be preferred over events when exposing notifications in a library targeting .NET 4+

I have a .NET library which, as part of an Object Model will emit notifications of certain occurrences.

It would seem to me that the main pros of events are approachability for beginners (and simplicity in certain consumption contexts) with the main negative being that they are not composable and hence are immediately forced into an Observable.FromEvent* if you want to do anything interesting without writing a code thicket.

The nature of the problem being solved is such that the event traffic won't be particularly frequent or voluminous (it's definitely not screaming RX), but there is definitely no requirement to support .NET versions prior to 4.0 [and hence I can use the built-in IObservable interface in System.Reactive without forcing any significant dependencies on consumers]. I'm interested in some general guidelines some specific concrete reasons to prefer IObservables over events from an API design perspective though - regardless of where my specific case might sit on the event - IObservable spectrum.

So, the question:

Is there anything concrete I'm making dramatically more difficult or problematic for API consumers if I go with the simplest thing and expose an event instead of an IObservable

Or, restated: Aside from the consumer having to do an Observable.FromEvent* to be able to compose events, is there really not a single reason to prefer an IObservable over an event when exposing a notification in an API?

Citations of projects that are using IObservable for not-screaming-RX stuff or coding guidelines would be ideal but are not critical.


NB as touched on in the comments with @Adam Houldsworth, I'm interested in concrete things wrt the API surface of a .NET 4+ library, not a survey of opinions as to which represents a better 'default architecture' for our times :)

NB this question has been touched on in IObserver and IObservable in C# for Observer vs Delegates, Events and IObservable vs Plain Events or Why Should I use IObservable?. The aspect of the question I'm asking has not been addressed in any of the responses due to SRP violations. Another slightly overlapping question is Advantages of .NET Rx over classic events?. Use of IObservable instead of events falls into that same category.

like image 630
Ruben Bartelink Avatar asked Jul 04 '14 10:07

Ruben Bartelink


2 Answers

In the comments of this answer, OP refined his question as:

[Is it] indeed definitely the case that each and every event can always be Adapted to be an IObservable?

To that question, the answer is basically yes - but with some caveats. Also note that the reverse is not true - see the section on reverse transformation and the conclusion for reasons why observables might be preferred to classic events because of the additional meaning they can convey.

For strict translation, all we need to do is map the event - which should include the sender as well as arguments - on to an OnNext invocation. The Observable.FromEventPattern helper method does a good job of this, with the overloads returning IObservable<EventPattern<T>> providing both the sender object and the EventArgs.

Caveats

Recall the Rx grammar. This can be stated in EBNF form as: Observable Stream = { OnNext }, [ OnError | OnCompleted ] - or 0 or more OnNext events optionally followed by either an OnCompleted or an OnError.

Implicit in this is the idea that from the view of an individual subscriber events do not overlap. To be clear, this means that a subscriber will not be called concurrently. Additionally, it is quite possible that other subscribers can be called not only concurrently but also at different times. Often it is subscribers themselves that control pace of event flow (create back-pressure) by handling events slower than the pace at which they arrive. In this situation typical Rx operators queue against individual subscribers rather than holding up the whole subscriber pool. In contrast, classic .NET event sources will more typically broadcast to subscribers in lock-step, waiting for an event to be fully processed by all subscribers before proceeding. This is the long-standing assumed behaviour for classic events, but it is not actually anywhere decreed.

The C# 5.0 Language Specification (this is a word document, see section 1.6.7.4) and the .NET Framework Design Guidelines : Event Design have surprisingly little to say on the event behaviour. The spec observes that:

The notion of raising an event is precisely equivalent to invoking the delegate represented by the event—thus, there are no special language constructs for raising events.

The C# Programming Guide : Events section says that:

When an event has multiple subscribers, the event handlers are invoked synchronously when an event is raised. To invoke events asynchronously, see Calling Synchronous Methods Asynchronously.

So classic events are traditionally issued serially by invoking a delegate chain on a single thread, but there is no such restriction in the guidelines - and occasionally we see parallel invocation of delegates - but even here two instances of an event will usually be raised serially even if each one is broadcast in parallel.

There is nothing anywhere I can find in the official specifications that explicitly states that event instances themselves must be raised or received serially. To put it another way, there is nothing that says multiple instances of an event can't be raised concurrently.

This is in contrast to observables where it is explicitly stated in the Rx Design Guidelines that we should:

4.2. Assume observer instances are called in a serialized fashion

Note how this statement only addresses the viewpoint of an individual subscriber instance and says nothing about events being sent concurrently across instances (which in fact is very common in Rx).

So two takeways:

  • Whilst OnNext captures the idea of an event it is possible that the classic .NET event may violate the Rx Grammar by invoking events concurrently.
  • It is common for pure Rx Observables to have different semantics around the delivery of events under load because back-pressure is typically handled per subscriber rather than per-event.

As long as you don't raise events concurrently in your API, and you don't care about the back-pressure semantics, then translation to an observable interface via a mechanism like Rx's Observable.FromEvent is going to be just fine.

Reverse Transformation

On the reverse transformation, note that OnError and OnCompleted have no analogue in classic .NET events, so it is not possible to make the reverse mapping without some additional machinery and agreed usage.

For example, one could translate OnError and OnCompleted to additional events - but this is definitely stepping outside of classic event territory. Also, some very awkward synchronization mechanism would be required across the different handlers; in Rx, it is quite possible for one subscriber to receive an OnCompleted whilst another is still receiving OnNext events - it's much harder to pull this off in a classic .NET events transformation.

Errors

Considering behaviour in error cases: It's important to distinguish an error in the event source from one in the handlers/subscribers. OnError is there to deal with the former, in the latter case both classic events and Rx simply (and correctly) blow up. This aspect of errors does translate well in either direction.

Conclusion

.NET classic events and Observables are not isomorphic. You can translate from events to observables reasonably easily as long as you stick to normal usage patterns. It might be the case that your API requires the additional semantics of observables not so easily modelled with .NET events and therefore it makes more sense to go Observable only - but this is a specific consideration rather than a general one, and more of a design issue than a technical one.

As general guidance, I suggest a preference for classic events if possible as these are broadly understood and well supported and can be transformed - but don't hesitate to use observables if you need the extra semantics of source error or completion represented in the elegant form of OnError and OnCompleted events.

like image 113
James World Avatar answered Sep 21 '22 23:09

James World


I've been reading a lot about Reactive extensions before finally dipping my toe, and after some rough starts I found them really interesting and useful.

Observables extension have this lovely optional parameter where you can pass your own time manager, effectively letting you manipulate time. In my case it helped a lot since I was doing some time related work (check this webservice every ten minutes, send one email per minute, etc) and it made testing a breeze. I would plug the TestScheduler in the component under test and simulate one day of events in a snap.

So if you have some workflows in your library where time plays a role in orchestration, I would really recommend using Observables as your output.

However if you are just raising events in direct responses to user inputs, I don't think that it's worth the added complexity for your users. As you noted they can wrap the event into their own Observable if needed.

You could have your cake and eat it too, although it would mean more work; offer a facade that turns your event library into a Observable-fueled one, by creating said Observable from your events. Or do it the other way: have an optional facade that suscribes to your observable and raise a classic event when triggered.

In my opinion there is a non-trivial technical step to take when dealing with reactive extensions and in this case it may come down to what your API consumers would be the most comfortable using

like image 26
samy Avatar answered Sep 22 '22 23:09

samy