Reactive Extensions (Rx)

 
10 januari 2014

Waarom reactive extensions

Een term die je steeds vaker tegenkomt is ‘Reactive Programming’. In essentie is dit een programmeerparadigma waarbij het actief propageren van wijzigingen centraal staat (http://en.wikipedia.org/wiki/Reactive_programming). De laatste tijd wordt de term in een wat bredere context gebruikt en staat het voor hoe je software kunt bouwen waarbij schaalbaar, responsive, resilient, event gedreven en asynchroon key words zijn. Zie ook het Reactive Manifesto (http://www.reactivemanifesto.org/).

Een library die bruikbaar is in deze context is Reactive Extensions (Rx). Het is onmogelijk om Rx en de context van ‘Reactive Programming’ goed uit te werken in enkele pagina’s in de nieuwsbrief, maar het is zo gaaf dat het onderwerp niet mag ontbreken in deze serie. Het wordt hier en daar dus wat kort door de bocht maar het is de moeite waard.

In essentie is Rx LINQ over events. Met Rx kun je eventstromen (muis events, inkomende twitterberichten, feed met aandelenkoersen, live GPS locatie, etc.) manipuleren en combineren met behulp van LINQ queries. Dit helpt om simpeler goede asynchrone en eventgedreven applicaties te maken.

De gedachte achter Rx: de dualiteit tussen IEnumerable en IObservable

RX is ontwikkeld door Erik Meijer binnen het Microsoft Cloud Team. De eerste versie is uitgebracht in 2009 en in 2012 is het open source gemaakt door Microsoft. De basisgedachte achter Rx is dat een eventstroom het pushequivalent is van een statische lijst, zie Figuur 1. Bij een Statische lijst heb je een vast aantal elementen. De lijst implementeert IEnumerable zodat je een IEnumerator kunt opvragen waarmee je één voor één de elementen uit de lijst langs kunt gaan (pull).

Bij Rx hebben we het niet over lijst van elementen maar een reeks van events die binnen komen. De interface IObservable is het push equivalent van IEnumerable. In plaats van het opvragen van een IEnumerator om door de bestaande elementen heen te lopen kun je subscriben met een IObserver waarna je één voor één de events binnen krijgt (push).

image
Figuur 1: IEnumerable<T> vs. IObservable<T>

De geraffineerdheid van de dualiteit zie je als je de IEnumerator en de IObserver naast elkaar zet. Er is een één op één mapping tussen de pull en de push functionaliteit.

Pull actie

IEnumerator

Push actie

IObserver<T>

Naar het volgende element

Call naar MoveNext. Daarna is de Current het volgende element

Nieuwe event komt binnen

Het volgende element (event) wordt meegegeven met de OnNext aanroep

Einde van de lijst

De call naar MoveNext geeft false als resultaat

Geen events meer

Er wordt OnCompleted aangeroepen als er geen events meer komen.

Er treedt een exception op

De call naar MoveNext gooit een exception

Er treedt een exception op

Er wordt een call gedaan naar OnError

Tabel 1 Dualiteit IEnumerator en IObserver

Welke mogelijkheden biedt deze dualiteit

Op het .Net platform hebben we LINQ. LINQ is één van de grote onderdelen van de, in 2007 geïntroduceerde, 3.5. versie van het .Net Framework. LINQ maakt het mogelijk om queries, filteringen en transformaties uit te voeren op IEnumerables. Mocht je niet bekent zijn met LINQ dan kun je het zien als dat je in code SQL-achtige queries uitvoert op IEnumerables. Methodes zoals Select, Where, Group By, Skip, Take, etc. Een zeer krachtige manier om lijsten met objecten te manipuleren. Het feit dat IEnumerable en IObservable één op één te mappen zijn betekend dat alles wat met een IEnumerable kan ook met een IObservable moet kunnen en dat je LINQ queries moet kunnen uitvoeren op eventstromen. En dat is exact wat Rx doet.

Werken Rx in de praktijk

Het werken met Rx in de praktijk kun je in twee onderdelen opsplitsen. Het creëren van observables die de eventstromen generen en het subscriben op en het manipuleren van de eventstromen. Een tweetal voorbeelden:

Een Where query op een eventstroom

In het eerste voorbeeld hoe je een Where clause kunt uitvoeren op een eventstroom.

codewhere

Figuur 2: Een Where query op een gegenereerde eventstroom

Op de eerste regel wordt een eventstroom gegenereerd (obs) die elke seconde een integer waarde genereert. 0, 1, 2, 3, 4, 5, etc. Op regel twee wordt er een Where filter aan de eventstroom gekoppeld die alleen de even getallen doorlaat. Hierdoor ontstaat er een tweede eventstroom (filtered). We hebben nu dus twee eventstromen waarop je kunt subscriben. Figuur 3 toont een marble diagram (https://github.com/richardszalay/raix/wiki/Marble-Diagrams) waarin de events getoond worden die door beide evenstromen gegenereerd worden. Daarnaast staat de console output van deze code. In beide zie je dat de gefilterde eventstroom alleen de even getallen geeft.

where
Figuur 3: Marble diagram originele en gefilterde eventstroom en de console output

Een rechthoek tekenen

Het tweede voorbeeld is wat realistischer. De case is dat we in een winforms applicatie een rechthoek moeten kunnen trekken en dat tijdens het trekken van de rechthoek de coördinaten hiervan getoond moeten worden.

coderect
Figuur 4: Code voor het volgen van een rechthoek die getrokken wordt

Figuur 4 toont de code om dit te bereiken. Op de eerste regels worden er drie eventstromen (observables) gecreeerd op basis van de standaard mouse events in winforms. De methode Observable.FromEventPattern is specifiek bedoelt om de omslag te maken van .Net events naar een observable.

Het volgende stuk code is ‘where the magic happens’. Hier worden de drie originele eventstromen (van het type IObservable<MouseEventArgs>) gecombineerd to één nieuwe eventstroom van het type IObservable<Rectangle> (dragging). Vrij vertaald staat hier: ‘Begin een nieuwe eventstroom als er een mousedown event is (1). Geef vervolgens alle mouse move events door (2) totdat er een mouse up event is (3). Vervolgens subscriben we op de nieuwe eventstroom om elke keer als de getrokken rectangle wijzigt dit te tonen. Je ziet hierin goed hoe je met behulp van LINQ queries 3 verschillende eventstromen kunt manipuleren en combineren. De eventstromen en de output zien er hierbij als volgt uit.

rect

Figuur 5: Marble diagram en output van het trekken van een rechthoek

En niet alleen voor .Net

Eén van de goede dingen aan Rx is dat er implementaties voor vele talen zijn. Microsoft zelf heeft naast de .Net versie ook o.a. een javascript, ruby en python implementatie waarbij met name de javascript versie behoorlijk gelijk loopt met de .Net versie. Daarnaast was Netflix zo enthousiast over het model dat die een Java port hebben gemaakt en die (gelukkig) open source hebben gemaakt.

En verder?

Rx biedt een krachtig model om eventgedreven applicaties vorm te geven. In dit stuk hebben we wat van het gedachtengoed laten zien en twee concrete voorbeelden van het filteren, aanpassen en combineren van eventstromen. Zoals vermeld is dit nog maar een fractie van het verhaal. Belangrijke aspecten die onvermeld zijn gebleven zijn bijvoorbeeld hoe je grip hebt op concurrency met behulp van subjects en schedulers en de elegantie van het opvangen van exceptions bij gecombineerde eventstromen.

Wil je meer weten over Rx en Reactive Programming dan kunnen de volgende links helpen:

Daarnaast geef ik regelmatig binnen Sogyo een vrijdagmiddagpresentatie over dit onderwerp waarbij ik dieper op het onderwerp in ga en veel meer voorbeelden heb. Mocht je niet bij Sogyo op kantoor zitten maar wel geïnteresseerd zijn kun je contact met me opnemen, dan kunnen we kijken wat de mogelijkheden zijn.

[[ In de interne nieuwsbrief van Sogyo schrijf ik, onder de titel ‘gave technologie’, over technologiën waar ik enthousiast over ben en waarvan ik vind dat iedere collega op zijn minst moet weten dat het bestaat.
Dit artikel komt uit de Sogyo Nieuwsbrief van januari 2014 ]]


Werken met ?
Kijk dan bij onze mogelijkheden voor starters en/of ervaren IT'ers.


Categorieën: Development, .Net, Overige talen en platformen, Open Source

Tags: , ,