Should a service be a 'bubble' or individual pieces?

Stray's Avatar

Stray

18 Mar, 2010 09:53 PM via web

A simple scenario: my application requires some XML to be loaded, and for models to be updated as a result.

This is handled by two classes - LessonXMLLoader and LessonXMLProcessor.

Is it better to:
1) Couple these two together - so the LessonXMLLoader creates and feeds the LessonXMLProcessor, and then fire a single event at the end (or fail point) of the process when the work is all done...

or

2) Keep them ignorant of each other by using the RL event dispatcher / commands?

  1. Support Staff 2 Posted by Joel Hooks on 19 Mar, 2010 04:18 AM

    Joel Hooks's Avatar

    Inject ILessonDataProcessor concrete LessonXMLProcessor into the service. Service will always need to process data. I think that these classes are intimate and that convolution of the necessary coupling is bad. Loose coupling is great, but I think that you can overdo it. The drawback to loose coupling is loss of clarity and intent, making it harder for others to understand the code. I think it is admirable across tiers, for the most part, but a processor that is only going to be used by the service doesn't need to be on an island. :>

  2. 3 Posted by Stray on 19 Mar, 2010 10:25 AM

    Stray's Avatar

    Ah ... clarity.

    Thanks Joel - I agree that you can overdo the loose coupling.

    I woke in the night with a suddenly recognition that if no other classes in the whole app give a shit that an event is being fired, the LessonXMLLoader has got no business firing it on the shared event bus when it could just handle it itself.

    I can feel a coupling decision flowchart brewing...

  3. 4 Posted by Jonny Reeves on 19 Mar, 2010 11:37 AM

    Jonny Reeves's Avatar

    Just to chime in,

    My approach is essentially the same as Joel's. There is an endpoint (Service URL) which contains a serialized representation of a FooValueObject (ie: JSON, XML, AMF, etc).

    The simplest member of the process is the FooParser. The Parsers job is to simply convert the serialised data into a FooValueObject, the interface looks like this:

    public interface FooParser {
        function parse(data : *) : FooValueObject;
    }
    

    And a typical implementation looks like this:

    public class FooJSONParser implements FooParser {
        public function parse(data : *) : FooValueObject
        {
            const values : Object = new JSONDecoder(data).getValue();
            const foo : FooValueObject = new FooValueObject();
    
            // The parser can now copy the data from the Dynamic object to the strongly typed ValueObject.
            foo.property = values.property;
            return foo;
        }
    }
    

    The Service class will have a FooParser injected into it via the FooParser interface, for example:

    [Inject]
    public var parser : FooParser;
    

    The Service makes use of a URLLoader and, in the onCompleteEvent block (ie: when the remote data has finished loading), it will call the parse() method of the FooParser. If the parsing completes without an exception, we can dispatch a FooServiceEvent back to the framework (and the value object can be set on a Model). However, should an error be thrown, a FooServiceErrorEvent will notify the system that we have a problem!

    private function onCompleteEvent(event : Event) : void {
        removeLoaderEventListeners();
        try {
            const fooValueObject : FooValueObject = parser.parse(_loader.data);
            dispatch(new FooServiceEvent(FooServiceEvent.COMPLETE, fooValueObject));
        }
        catch (e : Error) {
            dispatch(new FooServiceErrorEvent(FooServiceErrorEvent.ERROR, e.message));
        }
    }
    

    This approach proves very flexible as it allows you to swap the FooParser implementation both at compile time and at run time. When you first build your application, the Back End team may decide to make the endpoint spit out XML data, so your injector would be setup like this:

    injector.mapClass(FooParser, FooXMLParser);
    

    However, just before release they decide that XML is old hat and that JSON is the new cool - no problem, just write a JSON Parser and update your injection mapping:

    injector.mapClass(FooParser, FooJSONParser);
    

    This can even be taken one step further to allow you to pick which Parser gets used a run-time with the use of a Factory Method. Say that you Back End team are very indecisive and can't make up their mind if they are going to use XML or JSON; fine, we can create a new implementation of the FooService interface which automatically decides which parser to use based on the incoming data, for example:

    public class FooParserFactory implements FooParser {
        public function parse(data : *) : FooValueObject {
            if (data is XML) {
                return new FooXMLParser().parse(data);
            }
            else {
                return new FooJSONParser().parse(data);
            }
        }
    }
    

    Jonny.

  4. 5 Posted by Stray on 19 Mar, 2010 11:43 AM

    Stray's Avatar

    Nice one - thanks for sharing so much detail, very much appreciated.

    Also made me realise that I should use a separate Error event and not just rely on a different event type for success / failure.

    This stuff is where RL really comes into its own I think.

  5. Support Staff 6 Posted by Joel Hooks on 19 Mar, 2010 02:49 PM

    Joel Hooks's Avatar

    "you should not target low coupling, instead target appropriate coupling" - Mike Labriola (@mlabriola)

    That was the quote I was searching for :>

  6. 7 Posted by Stray on 19 Mar, 2010 10:14 PM

    Stray's Avatar

    That is a very, very good quote.

  7. Stray closed this discussion on 11 Feb, 2011 11:33 PM.

Comments are currently closed for this discussion. You can start a new one.