Introduction to Reactive Extensions
Working in the field of technology means constantly learning new technologies and using them to build cool new software. I think my desire to learn is what drove me to this industry.
My most recent endeavor was learning the Reactive Extensions (Rx) for the .NET framework. I had no reason to believe that picking up Rx would be any different from any other technology. Boy was I wrong! In my 10 years of learning new technologies, the difficultly of learning Rx rated right up there with making the switch from procedural to object oriented programming. It is a paradigm shift that requires you to completely rethink the way you develop. Developing with Rx will probably come more naturally for those of you well versed in asynchronous programming, but I did not have much exposure to async programming prior to this undertaking (which is probably why I found it so difficult to pick up).
Another reason why I found it difficult to learn Rx was because of the pace at which it was evolving. In one release I would use one operator which would disappear in the next. Worse yet, when I tried to integrate concepts I proved out using a console app into my Windows Mobile app, I found that the libraries differed from platform to platform. All of this meant that it was very difficult to find relevant documentation for the concepts I was trying to build out.
When I started learning the Reactive concepts, I was fortunate to be surrounded by people much smarter than me who had already mastered the technology. Though they helped me along the way, they never resorted to hand-holding (despite my best efforts) and made learn the Rx in my own time. Looking back I'm grateful for this because it resulted in a much deeper understanding of how Rx works. There are however a few tips I wish they would have given me to make my learning experience a bit smoother:
1. In simple terms, Rx consists of two things; observables and observers
An observable is a stream of information. That information can be as simple as an integer or as complex as a user-defined type. Some examples of streams could be the result of a database query or the EventArgs of a UI event firing. An observer is simply someone who listens to that stream of information and acts accordingly.
2. Observers consist of 3 things; OnNext, OnError and OnCompleted
These three methods are the only three methods you will find on the IObserver interface. As I will cover in my next point, you should never actually implement these interfaces so in most cases you will be passing them in as actions to the Subscribe() method on your IObservable. What they actually do is rather self explanatory so I won't go into the details.
3. You should never actually implement the IObservable<T> and IObserver<T> interfaces
This is probably the single most important Rx best practice. IObservables and IObservers are all created and consumed by the Rx operators and methods in the library. There is no need to create your own implementation of them. IObservables are created using the Observable.Create<T> method or any one of the dozen or so extension methods in the Rx library. IObservers are created when the Subscribe() method is called.
4. Learn Rx by fully understanding the Observable.Create<T> method
At its core, an Rx stream is created using one method: Observable.Create<T>. As I mentioned above, there are a dozen or so extension methods that can be used to simplify the creation of IObservables in common situations like making asynchronous calls or subscribing to events. Eventually there will come a time when the extension methods fall short and when they do it will be important to know how to build an observable from scratch using the Observable.Create<T> method.
5. Understand the difference between a hot stream and a cold stream (and when it make sense to use one versus the other)
I worked with Rx for a good couple weeks before I heard the terms hot and cold stream. When I finally read an article explaining the difference, it changed the way I architected my code. A hot stream is a stream that stays open for an extended period of time and may in fact have no end. An example of this is an observer that subscribes to a stream of UI events. There is no beginning or end of the stream other than the lifetime of the application. A cold stream on the other hand will have a definite end. This may be an asynchronous observable waiting for a web request to return. The stream ends when the web request completes.
6. Despite what you may think, you probably want to return IObservable<T>, not IObservable<T[]>
When I wrote my first Rx enabled method, I was refactoring a service layer method that returned a collection of user defined types which came back from a web request. I was making the web request synchronously, waiting for the response, deserializing the data returned, then passing back that collection of messages. When I refactored that call to use Rx, I changed the method signature to return IObservable<T[]>. This meant that all my stream only contained one item and my messages came through in one big chunk. Unfortunately, this change did little for the performance and experience of my application. The messages all still loaded as they had before; all at once. Then I realized that I should actually be returning an IObservable<T> which will pass back each message as soon as it is ready (in my case after it has been deserialized). This changed had a dramatic effect. Now, each message was returned in stream individually and my UI updated each time a message was received.
(2012 2/52)
(2012 2/52)
Nice rundown. Thanks for that. The observer pattern has been around for a while, but you're right, this is quite a different way to use it. Happen to know why it's gaining popularity now?
ReplyDelete