Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

My colleague and I have dispute. We are writing a .NET application that processes massive amounts of data. It receives data elements, groups subsets of them into blocks according to some criterion and processes those blocks.

Let's say we have data items of type Foo arriving some source (from the network, for example) one by one. We wish to gather subsets of related objects of type Foo, construct an object of type Bar from each such subset and process objects of type Bar.

One of us suggested the following design. Its main theme is exposing IObservable objects directly from the interfaces of our components.

// ********* Interfaces **********
interface IFooSource
{
    // this is the event-stream of objects of type Foo
    IObservable<Foo> FooArrivals { get; }
}

interface IBarSource
{
    // this is the event-stream of objects of type Bar
    IObservable<Bar> BarArrivals { get; }
}

/ ********* Implementations *********
class FooSource : IFooSource
{
    // Here we put logic that receives Foo objects from the network and publishes them to the FooArrivals event stream.
}

class FooSubsetsToBarConverter : IBarSource
{
    IFooSource fooSource;

    IObservable<Bar> BarArrivals
    {
        get
        {
            // Do some fancy Rx operators on fooSource.FooArrivals, like Buffer, Window, Join and others and return IObservable<Bar>
        }
    }
}

// this class will subscribe to the bar source and do processing
class BarsProcessor
{
    BarsProcessor(IBarSource barSource);
    void Subscribe(); 
}

// ******************* Main ************************
class Program
{
    public static void Main(string[] args)
    {
        var fooSource = FooSourceFactory.Create();
        var barsProcessor = BarsProcessorFactory.Create(fooSource) // this will create FooSubsetToBarConverter and BarsProcessor

        barsProcessor.Subscribe();
        fooSource.Run(); // this enters a loop of listening for Foo objects from the network and notifying about their arrival.
    }
}

The other suggested another design that its main theme is using our own publisher/subscriber interfaces and using Rx inside the implementations only when needed.

//********** interfaces *********

interface IPublisher<T>
{
    void Subscribe(ISubscriber<T> subscriber);
}

interface ISubscriber<T>
{
    Action<T> Callback { get; }
}


//********** implementations *********

class FooSource : IPublisher<Foo>
{
    public void Subscribe(ISubscriber<Foo> subscriber) { /* ...  */ }

    // here we put logic that receives Foo objects from some source (the network?) publishes them to the registered subscribers
}

class FooSubsetsToBarConverter  : ISubscriber<Foo>, IPublisher<Bar>
{
    void Callback(Foo foo)
    {
        // here we put logic that aggregates Foo objects and publishes Bars when we have received a subset of Foos that match our criteria
        // maybe we use Rx here internally.
    }

    public void Subscribe(ISubscriber<Bar> subscriber) { /* ...  */ }
}

class BarsProcessor : ISubscriber<Bar>
{
    void Callback(Bar bar)
    {
        // here we put code that processes Bar objects
    }
}

//********** program *********
class Program
{
    public static void Main(string[] args)
    {
        var fooSource = fooSourceFactory.Create();
        var barsProcessor = barsProcessorFactory.Create(fooSource) // this will create BarsProcessor and perform all the necessary subscriptions

        fooSource.Run();  // this enters a loop of listening for Foo objects from the network and notifying about their arrival.
    }
}

Which one do you think is better? Exposing IObservable and making our components create new event streams from Rx operators, or defining our own publisher/subscriber interfaces and using Rx internally if needed?

Here are some things to consider about the designs:

  • In the first design the consumer of our interfaces has the whole power of Rx at his/her fingertips and can perform any Rx operators. One of us claims this is an advantage and the other claims that this is a drawback.

  • The second design allows us to use any publisher/subscriber architecture under the hood. The first design ties us to Rx.

  • If we wish to use the power of Rx, it requires more work in the second design because we need to translate the custom publisher/subscriber implementation to Rx and back. It requires writing glue code for every class that wishes to do some event processing.

share|improve this question
2  
This belongs to StackOverflow.com. –  haylem Jul 9 '12 at 10:58
2  
Questions about programming practices don't belong here? It seems like an ideological question - expose Rx or not? –  Alex Jul 9 '12 at 11:06
    
@haylem: While the question is about .Net reflection, it is more a general design/concept question than one asking for a solution to a specific problem. And it is applicable not just to .Net, but to any language supporting run-time investigation of instances etc. –  Marjan Venema Jul 9 '12 at 11:13
1  
I guess both approaches are too complex for creating an object of type bar from an object from type foo. Why not make it simple? –  Emmad Kareem Jul 9 '12 at 11:23
2  
It's been cross-posted on StackOverflow already; I'd suggest one or the other should be closed. –  Dan Puzey Jul 9 '12 at 13:17
show 3 more comments

2 Answers 2

up vote 10 down vote accepted

I think saying that IObservable<T> ties you to Rx is the same as saying that IEnumerable<T> ties you to LINQ. Yes, IObservable<T> is at the core of Rx and Rx lets you do a lot of things with IObservable<T>.

But that doesn't mean you have to use Rx to implement your classes, even if they inherit from IObservable<T>. And you also don't have to use Rx to work with the class, you could directly use Subscribe(). I believe this is also the reason why IObservable<T> is included in the core of the framework (it's in mscorlib since .Net 4), but Rx isn't.

So, unless IObservable<T> doesn't fit your needs, you should use it. On the plus side, you'll get the possibility to use Rx. On the negative side, you might need to wrap your implementation to make it look like IObservable<T>, if you decide to change the implementation later on. But you would have to do the same if you used a custom interface.

IObservable<T> is just an interface, it doesn't tie you up to anything. But it gives you the ability to use libraries that already work with it, unlike a custom interface.

share|improve this answer
add comment

First of all, it's worth noting that IObservable<T> is part of mscorlib.dll and the System namespace, and thus exposing it would be somewhat equivalent to exposing IComparable<T> or IDisposable. Which is equivalent to picking .NET as your platform, which you seem to have done already.

Now, instead of suggesting an answer, I want to suggest a different question, and then a different mindset, and I hope (and trust) that you'll manage from there.

You're basically asking: Do we want to promote scattered use of Rx operators all across our system?. Now obviously that's not very inviting, seeing as you probably conceptually treat Rx as a 3rd party library.

Either way, the answer doesn't lie in the basal designs you two proposed, but in the users of those designs. I recommend breaking your design down to abstraction levels, and making sure that the use of Rx operators is scoped in just one level. When I talk about abstraction levels, I mean something similar to the OSI Model, only in the same application's code.

The most important thing, in my book, is to not take the design standpoint of "Let's create something that's going to be used and scattered all across the system, and so we need to make sure we do it just once and just right, for all the years to come". I'm more of a "Let's make this abstraction layer produce the minimal API necessary for other layers to currently achieve their goals".

About the simplicity of both of your designs, it's actually hard to judge since Foo and Bar don't tell me much about use cases, and hence readability factors (which are, by the way, different from one use case to another).

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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