mariocasciaro / scatter

IoC container and out-of-the-box extensibility for Node.js applications
MIT License
154 stars 14 forks source link

Service from a second, later registered, particle is not called! #29

Open k7sleeper opened 9 years ago

k7sleeper commented 9 years ago

This works

scatter.registerParticle [ 'components.0', 'components.1' ], __dirname

(scatter.load 'svc|sequence!init').then (app_init) ->
  app_init()
.then () ->
  (scatter.load 'svc|sequence!start')
.then (app_start) ->
  app_start()

but registering the second particle later

scatter.registerParticle [ 'components.0' ], __dirname

(scatter.load 'svc|sequence!init').then (app_init) ->
  app_init()
.then () ->
  scatter.registerParticle [ 'components.1' ], __dirname
  (scatter.load 'svc|sequence!start')
.then (app_start) ->
  app_start()

does not work (service initis provioded by particle.0, service startis provioded by particle.1).

mariocasciaro commented 9 years ago

This is due to the caching mechanism implemented in Container.assemble(). If we want your use case to be supported we should invalidate and rebuild that cache when adding new particles. One question, is any particular reason you need to support "hot" registration of new particles? Consider that this may have repercussions on other features like module overriding, what happens if a module is overridden by the new particle but it was already loaded from another module? This goes beyond what we currently can do in Node.

k7sleeper commented 9 years ago

My reason is, that modules under components.1 may have a dep to a module which is created and registered at scatter after calling service init. In other words, components.0 provides service init which creates an object which is registered at scatter and is a dep of modules in components.1.

Why it works with the first code snippet is the fact, that scatter loads deps not before the module which needs the dep is loaded.

The first code snippet fails, when calling (for what reason ever) scatter.initializeAll()!

mariocasciaro commented 9 years ago

Let's analyse one problem at a time. The second snippet is not working because the invocation of a service will cause the initialisation and loading of all the modules in its namespace, which in your case is the root. The modules and namespaces are cached for performance reason, so when you add another particle the new modules are simply not picked up, because the cache is used instead.

Now, your use case creates even a bigger problem, because if all the modules are already wired together (after the first service is invoked), adding a new particle with new modules could potentially mean that all the modules have to be re-linked, and this is a task close to impossible without restarting the entire application.

My advice is to try to simplify your solution, for example, trying to dynamically register a module which in turn provides a service, looks to me like a task that Scatter itself should be doing...

k7sleeper commented 9 years ago

I did not get your advice.

Now, my idea is to work with 2 Scatter instances, one for initializing the app, the 2nd for starting and running the app. The first is thrown away after initialization is done, the 2nd is created after initialization because at this moment all deps for running the app are available.

Could that be an approach?

mariocasciaro commented 9 years ago

Having 2 different scatter instances would work only if you defined all your modules as factories and constructors, because in that case all modules would be re-created and re-initialized. object modules are cached by Node.js and will be shared between the 2 Scatter instances, with unpredictable results.

Could you please tell me more about what happens in the initialisation phase?

k7sleeper commented 9 years ago

In the initialisation phase more or less the app config is read from a JSON file, a JSON schema validation is done and a appConfig object is created and prepared for the other modules.

mariocasciaro commented 9 years ago

Does the config affect what modules are loaded? Or it's just a set of parameters?

k7sleeper commented 9 years ago

It's just a set of parameters.

mariocasciaro commented 9 years ago

But during initialization, you are actually inserting new modules in Scatter right? and you also want to insert particles (during the init), is that correct?

k7sleeper commented 9 years ago

Yes, during initialization this app config object is registered (when it is completely prepared) at Scatter.

After initialization, the core particles are inserted in Scatter.

mariocasciaro commented 9 years ago

Why don't you initialize the config before instantiating Scatter? This is actually a pattern I used for Hadron and Particles. The config object is really a prerequisite for Scatter, especially if you use it to load other particles. Is that a feasible alternative for your project?

k7sleeper commented 9 years ago

That's what I did in previous projects. I'm not entirely satisfied with that approach!

Reading the config and preparing it requires some modules (General info, JSON Schema validators, some assets, ...) which I also need later in the particles. These modules have some dependencies which, I thought, could be useful to wire together by Scatter.

So, I thought that it could be a good approach to use Scatter as soon as I have a logger object for Scatter. My app start sequence in my lastest project is now:

  1. parse command-line arguments and exit if an error occurs
  2. (error-free) prepare a simple logger object
  3. prepare an appInfo object with some very basic info (some paths, the app name, ,,,)
  4. create a Scatter instance and manually register appInfo, loggers, constants, assets, errrorCodes, some utils (exceptionUtils, objectUtils, errorUtils, jsvUtils, ...) at it.
  5. register the 'start-up' particle at Scatter
  6. call 'init' service which creates an appConfig object
  7. register the appConfig object with Scatter
  8. register the other particles at Scatter
  9. call service 'start' or 'shutdown'

Currently, my work around is, registering all particles together with the 'start-up' particle, hoping that Scatter only loads a module when it's needed. It works for me as long as I don't call scatter.initializeAll() which I luckily don't need.

mariocasciaro commented 9 years ago

Looking at your use case, a possible solution would be to scope your first service to specific namespace, for example: 'svc|sequence!initializers/init' 'svc|sequence!start' This would cause only the initializers namespace to be initialized and cached. Give it a try and let me know.

k7sleeper commented 9 years ago

It works!

Thanks for this hint!

mariocasciaro commented 9 years ago

Perfect! Glad it worked! :fireworks: