Service split in multiple classes

fbregist's Avatar

fbregist

31 Jan, 2013 12:31 PM

I'm writing a Flex Desktop application that should autenticate the user and then show some content.
Now the question is: is correct to split the service in multiple classes like AuthService and ContentService or is better to have a single class as "gateway" and implement all in it??

ADD: while i was thinking i came to this approachh:
Multiple Service classes that have injectet one INetservice and delegate to it the low level communication.
what do you think?

  1. 1 Posted by Paul Robertson on 31 Jan, 2013 02:49 PM

    Paul Robertson's Avatar

    Heavy dose of opinion here, so take it for what it's worth:

    - More, smaller classes are usually easier to understand (for me
    personally). The downside is it's harder to browse the code tree --
    lots of smaller packages helps with that too, and grouping classes that
    are usually modified together into a single place helps with that too
    - Personally, I've never found myself wishing I had combined 2+ service
    classes, but I almost always wish I had divided a service into multiple
    services
    - Having said all that, I recommend that you avoid over-architecting up
    front. As long as you have a good tool (IntelliJ, Flash Builder,
    probably others) refactoring later (at least the "extract method" type
    of refactoring we're talking about) is fairly painless. Plus you have
    the strong upside that you already know what code you need, since
    you've already had to write it. If you plan the structure ahead of time
    you're just guessing what you'll need.

    Paul

  2. 2 Posted by fbregist on 31 Jan, 2013 04:41 PM

    fbregist's Avatar

    Yes but the problem is that i'm afraid to repeat the connection/low level communication code in every class so the solution i was thinking is to inject in each "highlevel" service a kind of "NetService" that will convert and do the request to the server.
    I'm wrong? there is a "better" way?

    Sorry if my questions are stupid but i've just started using RL and this is a big project made of multiple modules loaded at runtime and i'm trying to reorder my brain before going crazy.

  3. 3 Posted by Paul Robertson on 31 Jan, 2013 05:06 PM

    Paul Robertson's Avatar

    I think you're thinking exactly the right way.

    Make a class that manages the duplicated code. (You don't even have to
    call it a "service" -- it's sort of a helper for the services -- but
    you can decide for yourself what you consider a "service" or not.)

    Map that class as a singleton, or if you need some creation logic for
    it create the instance then map it. Then inject that instance into each
    of the service classes that need it.

    A simple example of this is in the AddressBook example that Joel Hooks
    and I built long ago (it's Robotlegs 1). In this case it's using a
    shared library for using a SQLite database:

    - Mapping the shared library
    https://github.com/probertson/robotlegs-examples-AddressBook/blob/master/src/org/robotlegs/examples/addressbook/controller/setup/ConfigureServiceCommand.as#L27

    - Injecting the shared library
    https://github.com/probertson/robotlegs-examples-AddressBook/blob/master/src/org/robotlegs/examples/addressbook/service/SQLContactService.as#L22

    Paul

  4. Support Staff 4 Posted by Ondina D.F. on 31 Jan, 2013 05:07 PM

    Ondina D.F.'s Avatar

    Adding to Paul’s answer:
    Another option

    If AuthService and ContentService share some of their methods, you could let them extend a base class, containing these common methods.
    Usually, as you might already know, services dispatch events to trigger either a command or to be listened to by a mediator, and these events carry the results from a server call.
    If you had a single service, and an AuthMediator and also a ContentMediator, both listening for the same event dispatched by the service, you’d have to add some logic to either your service or to your mediators, just to make sure each mediator gets only the data that it needs.
    If AuthService and ContentService extended a base class, AutService would dispatch an AuthEvent.AUTH_DATA_LOADED with the results as a payload, and AuthMediator would listen only to that event type, while ContentMediator would receive its data from the payload of an ContentEvent.CONTENT_DATA_LOADED dispatched by ContentService.

    Ondina

  5. Support Staff 5 Posted by Ondina D.F. on 31 Jan, 2013 05:08 PM

    Ondina D.F.'s Avatar

    oops, Paul was faster than me :)

  6. 6 Posted by fbregist on 05 Feb, 2013 10:44 AM

    fbregist's Avatar

    Thank you all for your help i've really appreciate it, but i'm stuck in this service implementation.
    i have to set different urlvariables based on the method called, for example:
    Login(user,password)
    { urlVar.action="login"; urlVar.user = user; urlVar.pass=pass; }

    and i would like to create some kind of factory pattern to handle this and don't have to insert in each method this variable code, but i can't find a solution.
    Following your suggestions i created a DataService class and all service inherit from it, using its method loadData() that create a urlloader instance and send the request, but this way i don't know how to differentiate the listeners and handle the response based on the caller.

  7. Support Staff 7 Posted by Ondina D.F. on 05 Feb, 2013 05:59 PM

    Ondina D.F.'s Avatar

    Hi,
    Here, a quick answer before going out for dinner.
    Some pseudo code to illustrate what I meant:

    BaseService

    import flash.events.IEventDispatcher;
    public class BaseService
    {
        [Inject]
        public var dispatcher:IEventDispatcher;
    protected function loadData(someVO:String):void { //code for loading data } protected function onServerResult(results: ResultEvent):void {
    } }

    LoginService and ILoginService

    public interface ILoginService
    {
        function loadUser(someParams:String):void;
    }
    
    public class LoginService extends BaseService implements ILoginService
    {               
        public function loadUser(someParams:UserVO):void
        {
            loadData(someParams);           
        }
        override protected function onServerResult(results: ResultEvent):void 
        {           
            dispatcher.dispatchEvent(new UserEvent(UserEvent.USER_LOADED, results));
        }
    }
    

    AnotherService

    public class AnotherService extends BaseService implements IAnotherService
    {               
        public function loadSomethingElse(someParams:Array):void
        {
            loadData(someParams);           
        }
        override protected function onServerResult(results: ResultEvent):void 
        {           
            dispatcher.dispatchEvent(new AnotherEvent(AnotherEvent.DATA_LOADED, results));
        }
    }
    

    If you’re using robotlegs 1, BaseService could extend Actor, instead of having the shared eventDispatcher injected into it.
    You map AnotherService and LoginService to their interfaces, which contain the methods specific for each service.

    In a command you inject the interface:
    [Inject] public var service:ILoginService;

    then in the execute() method:

    service.loadUser(event.payload);
    where event.payload could be a UserVO (VO=value object) with the data that the service would need.

    As you can see, LoginService dispatches a LoginEvent in the overriden method
    onServerResult(), and AnotherService dispatches AnotherEvent.

    Please let me know whether it helped or not.
    If it’s still not clear, I’ll write more explanations tomorrow, depending on your questions.

    Cheers,
    Ondina

  8. 8 Posted by fbregist on 05 Feb, 2013 06:13 PM

    fbregist's Avatar

    This is exactly what i've done until now!
    Now let me explain better:
    In LoginService i've created multiple methods: login(..) logout(..) recoverPsw(..).
    Each method create a URLVariables instance, sets on it a specific action:String property for PHP and passes it to loadData (there is some factory pattern to handle this based on some enum maybe?).
    The problem is onServerResult() because i don't know what was the method that generate the request and then i don't know how to parse the result data.
    Probably i'm approaching the problem from the wrong point of view...
    some code:

    public function login(userLoginVO:UserLoginVO):void
            {
    //Some factory pattern suggestion to handle this?
    var urlVar:URLVariables = new URLVariables();
    urlVar.action = PHPActionsEnum.LOGIN.toString(); //i'm using enum but i don't like it urlVar.uname = userLoginVO.username; urlVar.upassword = userLoginVO.password;
    loadData(urlVar); } public function logout():void { urlVar = new URLVariables(); urlVar.action = PHPActionsEnum.LOGOUT.toString(); loadData(urlVar); }
    override protected function processLoadedData(urlVars:URLVariables):void { //Who made the request?? //I need to know it to parse the response data! }
  9. 9 Posted by fbregist on 05 Feb, 2013 06:17 PM

    fbregist's Avatar

    maybe i should do the parsing in a command and not in the service?
    or i should create a serviceClass for each method?

  10. Support Staff 10 Posted by Ondina D.F. on 06 Feb, 2013 06:06 PM

    Ondina D.F.'s Avatar

    I know you don’t want to hear this, but the truth is, there are many ways to solve your problem. I’ll be able to mention just a few.

    **First part of your question:

    //Some factory pattern suggestion to handle this?

    Move the code from within your login() and logout() to, say, a LoginFactory, having a createLoginURLvars() and createLogoutURLVars()

    1. Then either you inject the loginFactory into your LoginService, and call it within login():
      urlVars URLVariables = loginFactory.createLoginURLvars ();
      loadData(urlVar);
      Within logout():
      urlVars URLVariables = loginFactory.createLogoutURLVars ();
      loadData(urlVar);

    2. or you inject the loginFactory into the command calling the service, and then call service.loadData(urlVar);

    For example a LoginCommand would do this:

    urlVars URLVariables = loginFactory.createLoginURLvars ();

    service.loadData(urlVar);

    and LogoutCommand would do this:

    urlVars URLVariables = loginFactory. createLogoutURLVars ();

    service.loadData(urlVar);

    This way, you wouldn’t need 2 methods inside your LoginService.

    **Second part:

    //Who made the request?? //I need to know it to parse the response data!

    1.The command calling the service could pass the required parser on to the service, if you need different parsers for login(ILoginParser, userVO) and logout(ILogoutParser, userVO)

    2.The command calling the service could pass a flag (needsParser or something based on the action, login/logout) along with the urlVars. Inject the parser into the service. If needsParser is true, onServerResult() would parse the data before dispatching the event.

    3.If you only need to parse data when the result from server is not null, then just check for it within onServerResult() and call the parser only when you have data.

    4.If you used an AsyncToken, you could set an asyncToken.needsParser or asyncToken.action and check against it inside onServerResult().

    Or with a RemoteObject, you could add different responders depending on your needs, onSimpleServerResult(), onServerResultWithParser()…

    remoteObjet.getOperation("loginUser").addEventListener(ResultEvent.RESULT, onServerResultWithParser);

    remoteObjet.getOperation("logoutUser").addEventListener(ResultEvent.RESULT, onSimpleServerResult);

    5.Like before, pass the token to the parser, and let it decide what to do with the data and what event to dispatch depending on the token

    maybe i should do the parsing in a command and not in the service?

    6.Parsing inside a command would be a nice alternative, since the command is short lived, and the service wouldn’t have to care about the data that it just sends via an event. But with your special use case, you’d run into the same difficulty of differentiating between parsers or between the event types that you’d need to dispatch in your service.

    7.Parsing inside a command and using commandMap.detain() and release() would be another option, but personally I don’t like it very much

    8.You go the path suggested by Paul.

    9.Or by Shaun: http://knowledge.robotlegs.org/discussions/robotlegs-2/38-service-w...

    10.> or i should create a serviceClass for each method?

    You can do that, if you want to. It would be very granular, but indeed, you could design each service as you wanted, let them implement different interfaces...

    11.In robotlegs 2 you could use guards and hooks..Just thinking out loud.. I haven’t tried guards and hooks with a service yet, but I will as soon as I get a chance. I’ll let you know about my findings, if you want to.

    Did I manage to totally confuse you? ;)

    Ondina

  11. 11 Posted by fbregist on 08 Feb, 2013 06:40 PM

    fbregist's Avatar

    your response is similar to what i was thinking...now i'll tell what i think:
    i will create a class PHPActionData that contain 2 properties

    action:String;
    parser:IDataParser;
    
    and a class ActionManager wich as an array actionsDataArray[]
    inside actionmanager:
    actionsDataArray[ActionsEnum.LOGIN] = new PHPActionData("login", loginParser);
    ...
    

    the service do:

    urlvar.action = actionsManager.getAction(ActionsEnum.LOGIN);
    
    then create an instance of a ServerRequest Class this way:
      new ServerRequest(urlrequest, actionsManager.getParser(ActionsEnum.LOGIN) ).
    

    ServerRequest is like a command: call urlloader and when finished pass data to parser and die.
    Parser will fire event or update model.

    what do you think???? I've a future as developer ? :D

  12. Support Staff 12 Posted by Ondina D.F. on 10 Feb, 2013 12:50 PM

    Ondina D.F.'s Avatar

    PHPActionData is kind of a VO and ActionManager a Model? Or is ActionManager the factory?
    ActionManager and the Service are injected into ServerRequestCommand, right?

    ServerRequest is like a command

    Why “like” a command? Why not a real command, triggered by an event, instead of new ServerRequest...?

    : call urlloader and when finished pass data to parser and die. Parser will fire event or update model. what do you think????

    Sounds good, provided that I understood your example correctly :)

    I've a future as developer ? :D

    Haha, of course, you do. Who’s saying otherwise?;)

  13. 13 Posted by fbregist on 10 Feb, 2013 01:07 PM

    fbregist's Avatar

    Mhh no i'll try to explain me better with some kind of drawing when I'll be home with a real keyboard.

  14. Support Staff 14 Posted by creynders on 10 Feb, 2013 07:13 PM

    creynders's Avatar

    Maybe it's time to take a step back.
    There are basically two roads:

    1/ with a service mapped as a singleton. Somehow the loader and parser need to be mapped to each other. This can be done in a myriad of ways, but the most basic way is using the loader as a key in a dictionary and the parser as a value. WHen the loader finishes it's used to look up the parser, which parses the results.
    In general though it gets a bit messier than that because many times you have to account for repeated requests, in which case I use some kind of ServiceRequestMapping class which holds all the data and which can be used to check whether the request is already running et cetera.

    2/with a service mapped to be instantiated for each request. This has a benefit in the sense that it encapsulates each request and the relevant objects can be stored in private members of the service class itself. The downside however is that it provides no way to check for already running requests that might get repeated.

    For both:
    The parser could be passed in by the command, but the clearest IMO is to let the service decide which parser it needs. Either it uses the injector to retrieve one, or some kind of factory.

    In general though, if my service needs more than one parser I split it up. I can't think of a situation where you might need more than one, yet not violate the SRP. Obviously in that case (i.e. with one parser per service) I simply have a Inject-tagged member that receives the mapped parser.

  15. 15 Posted by fbregist on 21 Feb, 2013 09:38 AM

    fbregist's Avatar

    Sorry for not responding until now but i've been writing the UI code in the past weeks.
    i've just 10 days to end this project and even if i read your responses and seemed clear, my brain can't find a way to apply your solutions, so that's what i've written (really bad code...)

    i've made a dynamic URLLoader so i can set on it an "action" property (to know what was the request) and "data" (for optional custom data), they are passed to processLoadedData when the request complete:

    override protected function processLoadedData(urlVars:URLVariables, action:PHPActionsEnum, data:Object):void
            {   
                
                switch(action)
                {
                    case PHPActionsEnum.LOGIN:
                        switch(urlVars.result)
                        {                   
                            case PHPRespEnum.OK:
                                trace("login: " + urlVars);
                                dispatch(new AuthServiceEvent(AuthServiceEvent.LOGGED_IN));                         
                                break;
                            case PHPRespEnum.BAD_USER:
                                dispatch(new AuthServiceErrorEvent(AuthServiceErrorEvent.BAD_USER));
                                break;
                            case PHPRespEnum.BAD_PASS:
                                dispatch(new AuthServiceErrorEvent(AuthServiceErrorEvent.BAD_PASS));
                                break;
                            default:
                                
                                break;
                        }   
                        break;
                    case PHPActionsEnum.LOGOUT:
                        trace("logout: " + urlVars);
                        dispatch(new AuthServiceEvent(AuthServiceEvent.LOGGED_OUT));
                        break;
                    
                    default:
                        trace("unknown auth response action");
                        break;
                }           
            
            }
    

    I don't like this solution... but i really can't find a way to do it better now, my mind is elsewhere

  16. Support Staff 16 Posted by Ondina D.F. on 22 Feb, 2013 03:31 PM

    Ondina D.F.'s Avatar

    Hehe,a switch inside a switch isn’t nice indeed.
    The outer switch isn’t even necessary, because each PHPActionsEnum is related to a distinct event type.

    I’d use a dictionary instead of the switch.
    Pseudo code:
    Build a dictionary using your PHPActionsEnum or whatever suits you:

    private const LOGOUT:String="logout";

    someEventMap=new Dictionary();
    someEventMap[LOGOUT] ||= new Dictionary();
    someEventMap[LOGOUT]["eventClass"]=SomeEvent;
    someEventMap[LOGOUT]["eventType"]=SomeEvent.SOMETHING_HAPPENED;

    (*or just someEventMap[LOGOUT]=new SomeEvent(SomeEvent.SOMETHING_HAPPENED);)

    Then you can use it like this in your service class:

    override protected function processLoadedData(urlVars:URLVariables, action:PHPActionsEnum, data:Object):void
    {

    eventClass=someEventMap[action]["eventClass"];
    eventType=someEventMap[action]["eventType"];
    var eventToDispatch:Event = new eventClass(eventType, somePayload);
    dispatchEvent(eventToDispatch);

    (*or dispatchEvent(someEventMap[action]);

    Does this answer your question?

  17. Support Staff 17 Posted by Ondina D.F. on 26 Feb, 2013 03:27 PM

    Ondina D.F.'s Avatar

    Closing the discussion for now. You can re-open it whenever you want:)

  18. Ondina D.F. closed this discussion on 26 Feb, 2013 03:27 PM.

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

Keyboard shortcuts

Generic

? Show this help
ESC Blurs the current field

Comment Form

r Focus the comment reply box
^ + ↩ Submit the comment

You can use Command ⌘ instead of Control ^ on Mac