Hey @luisherranz,
I am already using the “everything is a package” pattern and it works great. Also the Space.Injector
solves all of your mentioned problems without being Meteor-aware (which is also something a lot of people forget, its just Javascript, not Meteor)! And I am not really sure why runtime dependencies should be reactive, this makes the code even more complicated and coupled.
Here is your example how I would write and test it with space:base
:
// Simple sugar for javascript inheritance (not required)
CatalogStore = Space.Object.extend({
// just a prototype property, no magic
Dependencies: {
countersStore: 'CountersStore',
catalogRouter: 'CatalogRouter'
}
});
beforeEach(function(){
// Store we want to test
var catalogStore = new CatalogStore({
countersStore: new CountersStore(),
catalogRouter: new CatalogRouter()
});
});
in your real app you want the injector to wire things up:
var injector = new Space.Injector();
var countersStore = new CountersStore();
var catalogRouter = new CatalogRouter();
injector.map('CountersStore').to(countersStore);
injector.map('CatalogRouter').to(catalogRouter);
var catalogStore = new CatalogStore();
injector.injectInto(catalogStore); // provide runtime dependencies
this was the bare-metals approach, only using the injector and doing everything else by hand.
Of course space comes with convenience like namespace lookups:
var injector = new Space.Injector();
injector.map('CountersStore').asSingleton();
injector.map('CatalogRouter').asSingleton();
injector.map('CatalogStore').asSingleton(); // deps are auto-injected
One thing you don’t consider is this: there is no way to decouple everything. If any parts of your app talk with each other (in any way) then they are coupled (in some way). Maybe not to implementation details (the Meteor way) but even with your dispatcher and/or dependency injection the various parts are coupled at runtime -> its the messaging contract that couples them.
Think about it: you can build the most generic, decoupled package possible, but still something, somewhere has to send to or retrieve messages from it. These messages are coupling, even when they are abstracted behind string constants and extremely loosely contracts like with flux dispatcher. In my opinion that’s even worse, because now you have coupled things together but have no explicit contract defined how messages have to look like.
Coupling means this: “Do I have to change anything else if I change this part of the code?”. So if you change how your package receives parameters (or which) then you have to update all callers too, even if they never have a direct reference to the receiver.
The other aspects like namespacing, globals etc. are not really relevant and a matter of taste. There are people that like the “java namespacing pattern” and others who hate it. You can build your structure any way you want and even the Meteor way (simple globals) has its place.
With dependency injection you can remove the static coupling (which is great for testing) but you can never remove runtime coupling, because that’s what your app is made of. (Actually you can, there is a pattern called “anti-corruption layer” which means that you use mediators between parts of your app that translate the api messages of both parties, but that’s another story).
In my experience it works like this: You start mostly with very app-specific code, small encapsulated classes that do one thing well and some “controller”-like parts that wire them up. Eventually there appear some bigger-scope concerns where you think “hmm … this could become its own package” – but still it doesn’t have to be “generic” or “agnostic”. The code inside the package can be extremely self-coupled and straight-forward. Then eventually you come across functionality that is really re-usable, like e.g. a package that wraps the Stripe api which you could use in more than one application.
The “generic” package, shared between multiple apps sounds great BUT it comes with a big price tag: maintenance of the API contract. In my opinion you have to carefully consider where it pays off and which parts of the code should not become too generic.
To sum it up: the only benefit you get from using a messaging dispatcher / event bus / any kind of middleware layer, is that you can hook into the message flow. That’s it, there is nothing more. Forget about these patterns in other languages (where they are used to decouple), we are in Javascript land where its even possible to switch your pants while you are shitting them. Think: I could replace Meteor
with something else, before calling a method that uses it – this would be a (hacky) way of dependency injection.
But what I really like about messaging patterns is the possibility to make the contract between packages explicit. Because that’s one downside of dynamic languages: you can’t rely on the compiler to tell you if you called something “the right way” which is a pain while refactoring.
Here is a simple self-checking message that you can send to my stripe package:
class Stripe.ChargeCustomer extends Space.Struct
@fields:
customerId: String
total: Number
description: String
ipAddress: String
this uses Meteor check
internally when you instantiate the struct with a param object. Of course I use these messages also in my unit-tests etc. so that if you change the api, everything breaks and tells you that where you have to update your code to complete the refactoring.
Ok this is getting really long and confusing I hope its still understandable.