1

I have a large complex AngularJS Comet application ("real-time"). Clients send data to the server, and also receive notifications from the server about events other users have initiated.

In its simplest form, the app has these client-side components ("Service" below could be AngularJS .service or .factory, whatever)

  • [Object] Controller: could be any object, like Student, Teacher, Lesson, Document
  • [Object] Service: holds model of the object and has methods to operate on that data
  • Socket Service: wrapper for socket library

Calls can go both directions

  • User Updates Something > [Object] Controller > [Object] Service > Socket Service > Server
  • Server > Socket Service > [Object] Service > [Object] Controller > User Notified

What is the best architecture for two-way inter-module communication in this case?

Here's what I think are the options. Please suggest other good ones if I've left them out.

  1. Use the circular dependency fudge. Everywhere I read it seems we shouldn't be doing that, including Lu4 in his "fudge" answer. On the other hand, doing this means you can have method calls in both directions, keeping inter-module communication consistent throughout the app.
  2. Use dependency injection and method calls in one direction, and events in the other direction. There's some opposition to over-using events. Is this a case of over-using?
  3. Use events in both directions. Moves closer to "over-use", but keeps inter-module communication consistent throughout the app.
  4. Use a Mediator pattern, ie have a central orchestrating service that coordinates in both directions. I'm not sure how this is different or better than using events, wherein $rootScope becomes the "mediator" against which all events and their callbacks are registered.
  5. Some kind of long-polling between modules, so that, eg the [Object] Controller makes a call to [Object] Service, which returns a promise and in turn calls Socket Service that also returns a promise, and when Socket Service receives some data, the promise resolves back up the chain, at which point [Object] Controller initiates another request.
Community
  • 1
  • 1
poshest
  • 4,157
  • 2
  • 26
  • 37

1 Answers1

1

A clean solution would be to use classical DI for the user initiated direction. Since your [Object] Service contains the model you only need to update that (if you implementation is otherwise clean, i.e. you use the injected model object everywhere).

So the problem is reduced to how to get the new updates from your Socket Service to the [Object] Service. Since your Socket Service actually should not know about your Models Details injection of the models is not a reasonable solution (leave alone the so created circular dependency). Recieving an update from a server for anything in your client app is in in the very definition an event (can occure at any time), so I think it is very reasonable to get the $RootScope in your SocketService and broadcast it. On this event your [Object] Services take than care for updating the models based on the new information.

If you have many services and usually the server initiated events are only relevant to one or very few of them, a possible optimization would be to sent the a field from the server which models are affected by this update. This is reasonable, since the server knows about the models already and it enables the Socket Service to broadcast updateFor:xxx events without knowing about what xxx means. The respective [Object] service then listens only to the updateFor:[Object] event. But as always with optimizations, do not do them if the easy way already works for you.

jan.vogt
  • 1,801
  • 10
  • 27
  • "Recieving an update from a server for anything in your client app is in in the very definition an event". But so are user events, hence `$("#clickme").on("click", ...` in jQuery, and so on. Why is using events throughout not "clean"? It's simpler. – poshest Oct 07 '14 at 08:43
  • I would not agree that events are in any way simpler. Actually, IMHO, one of the main reasons to use angular are two-way bindings. But they have to be set up using a controller. Angular provides only the concept of controllers for views using the `$scope` as glue. It does AFAIK not provide any sort of model controller which could be used to bind models to a datasources that emits events. Which is fine, as most web apps are turn based anyway. (The `$HTTP`-service does a good job hiding the 'ready' or 'failure' events, which are fundamentally different from events that can arrive at any time.) – jan.vogt Oct 07 '14 at 14:08
  • AngularJS does not provide a "model controller which could be used to bind models to a datasources" whatsoever! And I'm not proposing to abandon two-way bindings. But imagine calling `$scope.addNewTeacher`, which instead of doing a sockets version of `$http` which returns promises and all, just emit an event, the SAME event that could be emitted by Socket Service if the new Teacher was coming from the server. No need to write two different addNewTeacher functions that rely on completely different technologies. – poshest Oct 07 '14 at 18:24
  • On which `$scope`? The model service and the creation of new models should not be dependent on any `$scope`. I wonder if we are talking past each other: Where do you store the current set of e.g. Teacher objects already created? I assumed in the Teacher Service... – jan.vogt Oct 08 '14 at 09:04
  • Yes, I'm saying that `Teacher Service` would store the current set of teachers, and have ONLY listeners for CRUD events, eg `add.teacher` event, the handler for which would add the new Teacher to the model. I was proposing that both the Socket Service AND the Teacher Controller could `$emit` `add.teacher` events. Then there's no need for any method calls from `Teacher Controller` to `Teacher Service`. One "protocol" throughout. – poshest Oct 08 '14 at 09:55