Github here: Loader
Closure Library deals with dependencies at compile time creating a graph from those goog.require and goog.provide statments at the top of your files. Unfortunately this means that our dependencies are hardcoded to each file and we can get in trouble if there are circular dependencies.
The way to relieve this is to pass in the dependencies to our classes when they are instantiated. This way we can keep the majority of our dependencies in a single file and pass through the implementations that classes need. This means we have code that used to look like this:
can now become this:
We've moved the requires from being explicitly defined at the start to being something we have to define when we instantiate the class. If we do this throughout our program we can pass along dependencies all the way from the beginning. This not only means that we don't have to worry about our dependency graph but it has the added bonus that we can pass in mocks and stubs as the dependencies and make it easy to test each individual class without having to rely on our explicitly defined dependencies.
The big issue then is that this may get tricky the larger your program becomes. If you have to start passing along dependencies through parent files just to give them to the children then this can get messy quickly. To make this easier we can use a resource locater that has the ability to find the implementation we need. This is what Loader does for you.
Loader is based off the AngularJS dependency system but is made to work with Advanced Optimizations so differs in how it's used but also has a couple of other features such as instantiation of objects when their dependencies are met, in that way it also has some similarity to AMD
Loader is designed to be required in your main file where you will declare all the dependancies:
Once you've done that then Ldr is available for your entire program. The next thing you need to do is write your classes in the method described above, putting your dependencies at the start of your constructor function. If your constructor function also takes other parameters then you can place them afterwards, but your classes really shouldn't have any extra parameters (if they do then you can use them as dependencies only when they are singletons, though they can be instantiated without any problems).
Next you'll need to add a line to the prototype of your class to help Loader get the dependencies for you. It should be an array of strings that are the names for your dependencies:
Now your class is Loader friendly with the added bonus that you can still use this class as is even if you copy it to a project where you're not using Loader.
Next thing we need to do is go to the main file and require in all our dependencies. Once they are there we can tell Loader to store the dependencies for when an invocation of a class is needed.
There are three different types of dependencies in Loader:
2. Regular
These are for classes that you want to be instantiated before being passed to the class. Though it is made for Classes it can also take objects and will pass them directly through. These are registered like this:
3. Singletons
You can use this if you the object is meant to be a singleton. Although you can also just instantiate the object and pass it in to the regular method this has the added bonus that the singleton will only be instantiated when it's own dependancies are satisfied and will return a promise that you can use to do extra setup like so:
Now that we've registered the dependencies we can go ahead and instantiate our class that has it's own dependencies. It will pass the dependencies in recursively so we only have to call our top class. We can also pass in any arguments it might take like so:
There may be issues if a dependency is not registered and requested. We can test that all dependencies can be satisfied before instantiating by testing our class like so:
Or we can instead use the instSoon method which will return a promise that will run functions when the dependencies are met. This is a great way to get around the circular dependency issue that might come up with the usual method of static dependencies in the Closure Library:
There is just one trick left though. Dependencies like this work great for methods you have written, but what happens if you are using third party libraries that can take in constructors that they instantiate using the "new" keyword instead of Ldr.inst? Well we can make it so calling "new" on a function will instead run it through the instantiation steps of Ldr. All we need to do is this:
Now we can pass that in to other libraries with methods that take constructors such as PlastronJS and all the dependencies will be resolved when "new" is called on it.
Hopefully that will help you get started managing your dependencies, Loader is still quite new so there may be some changes but I'm sure they will all be for the better.
Closure Library deals with dependencies at compile time creating a graph from those goog.require and goog.provide statments at the top of your files. Unfortunately this means that our dependencies are hardcoded to each file and we can get in trouble if there are circular dependencies.
The way to relieve this is to pass in the dependencies to our classes when they are instantiated. This way we can keep the majority of our dependencies in a single file and pass through the implementations that classes need. This means we have code that used to look like this:
goog.require('myImplementation');
myClass = function() {
this.myImplementation = new myImplementation();
}
can now become this:
myClass = function(myImplementatation)() {
this.myImplementation = new myImplementation();
};
We've moved the requires from being explicitly defined at the start to being something we have to define when we instantiate the class. If we do this throughout our program we can pass along dependencies all the way from the beginning. This not only means that we don't have to worry about our dependency graph but it has the added bonus that we can pass in mocks and stubs as the dependencies and make it easy to test each individual class without having to rely on our explicitly defined dependencies.
The big issue then is that this may get tricky the larger your program becomes. If you have to start passing along dependencies through parent files just to give them to the children then this can get messy quickly. To make this easier we can use a resource locater that has the ability to find the implementation we need. This is what Loader does for you.
Loader is based off the AngularJS dependency system but is made to work with Advanced Optimizations so differs in how it's used but also has a couple of other features such as instantiation of objects when their dependencies are met, in that way it also has some similarity to AMD
Loader is designed to be required in your main file where you will declare all the dependancies:
require('Ldr');
Once you've done that then Ldr is available for your entire program. The next thing you need to do is write your classes in the method described above, putting your dependencies at the start of your constructor function. If your constructor function also takes other parameters then you can place them afterwards, but your classes really shouldn't have any extra parameters (if they do then you can use them as dependencies only when they are singletons, though they can be instantiated without any problems).
Next you'll need to add a line to the prototype of your class to help Loader get the dependencies for you. It should be an array of strings that are the names for your dependencies:
myClass.prototype.inject_ = ['myImplementation'];
Now your class is Loader friendly with the added bonus that you can still use this class as is even if you copy it to a project where you're not using Loader.
Next thing we need to do is go to the main file and require in all our dependencies. Once they are there we can tell Loader to store the dependencies for when an invocation of a class is needed.
There are three different types of dependencies in Loader:
1. Constants
These are for when you want the dependency passed in as is. For instance you may need to pass in a constructor function, Loader will try and instantiate any dependencies given so if you want the constructor instead of an instance then you'll want to pass it in like this:
Ldr.regConst('name', myConst);
2. Regular
These are for classes that you want to be instantiated before being passed to the class. Though it is made for Classes it can also take objects and will pass them directly through. These are registered like this:
Ldr.reg('name', depClass);
You can use this if you the object is meant to be a singleton. Although you can also just instantiate the object and pass it in to the regular method this has the added bonus that the singleton will only be instantiated when it's own dependancies are satisfied and will return a promise that you can use to do extra setup like so:
Ldr.regSingle('mediator', app.Mediator, arg1).next(function(mediator) {
mediator.on('thing', function() {do stuff();});
});
Now that we've registered the dependencies we can go ahead and instantiate our class that has it's own dependencies. It will pass the dependencies in recursively so we only have to call our top class. We can also pass in any arguments it might take like so:
var myInst = Ldr.inst(myClass, arg1);
There may be issues if a dependency is not registered and requested. We can test that all dependencies can be satisfied before instantiating by testing our class like so:
if (Ldr.test(myClass))
var myInst = Ldr.inst(myClass, arg1);
Or we can instead use the instSoon method which will return a promise that will run functions when the dependencies are met. This is a great way to get around the circular dependency issue that might come up with the usual method of static dependencies in the Closure Library:
Ldr.instSoon(myClass, arg1)
.next(function(myInst) {myInst.foo();})
.next(function(myInst) {myInst.bar();});
There is just one trick left though. Dependencies like this work great for methods you have written, but what happens if you are using third party libraries that can take in constructors that they instantiate using the "new" keyword instead of Ldr.inst? Well we can make it so calling "new" on a function will instead run it through the instantiation steps of Ldr. All we need to do is this:
var toPass = Ldr.makeInst(myClass);
Now we can pass that in to other libraries with methods that take constructors such as PlastronJS and all the dependencies will be resolved when "new" is called on it.
Hopefully that will help you get started managing your dependencies, Loader is still quite new so there may be some changes but I'm sure they will all be for the better.
I don't really understand the benefit from moving declarations from the top of the file in a prototype property, except in the rare cases of circular dependencies. In those (rare) cases the technique described initially is doing well enough.
ReplyDeleteI can easily imagine it was written specifically for Plastron, because from trying it I still have the bad taste in my mouth, having to juggle with constructor/classes back and forth.
I don't think it is generally applicable, but thanks for the effort.
the reason they're put on the prototype is so that it works with Advanced Optimizations. I wanted to have them as a Class variable but then the compiler changes the namespace so it's not the easiest to get.
ReplyDeletePutting in the names of the dependencies allows Loader to locate the dependencies for you, rather than having to pass them in manually. I only mentioned Plastron as a case for when you might need to pass it a constructor function that you want to have dependencies resolved for.
Basically the Loader will act as your factory for creating instances and resolve dependencies for you based on what you give it. For instance if I have a mediator and I want it available throughout the program then in my main file I just need to put:
Ldr.instSingle(mvc.Mediator);
then in any class I use throughout the program I can easily give it to the class like this:
myClass = function(mediator) {}
myClass.prototype.inject_ = ['mediator']
now whenever I instantiate the class it will automatically pass in the mediator for me:
var myInst = Ldr.inst(myClass);
you'll start to see how this is useful when you start having several dependencies that are needed by several classes, and dependencies that have dependencies and so forth because you just declare the dependencies on the class and when you instantiate it then it will go through and load the rest of the dependencies for you rather than you having to manually pass them all in.
I meant:
Deleteldr.instSingle('mediator' mvc.Mediator)
Quick notes:
ReplyDeleteCircular dependencies is always a problem, no matter if modules are loaded in compile time or run time.
I really don't like idea of passing classes instead of instances. If we need instance, put them a instance. Don't sacrifice clean design for sake of IoC container.
"require('Ldr');"
Please prefix your code with some namespace. It's not a kind to mess a global scope.
"classes really shouldn't have any extra parameters? "
Classes should have exactly these parameters which they needs to declare their dependencies.
myClass.prototype.inject_ is so Angulary. I don't like the way we have to decorate our classes to work with IoC container.
Anyway, I like the DI movement in Closure world.
circular dependencies cause much less headache using the instSoon method above. If the dependency is actually used in the classes methods instead of the constructor and you had to put dependencies at the top of the file it would be a problem, but allowing the method to instantiate when it's dependencies resolve themselves is pretty powerful - at least when I was testing it with my work's codebase (there is a bit of criss-crossing dependencies in there). Having all the dependencies brought in a single file takes away circular dependencies through goog.require completely, then the instSoon gives the dependencies a chance to resolve themselves in a way that goog.provide just doesn't allow.
Deletethe extra parameters I'm talking about are those outside of their dependencies, and only if that class is to de a dependency itself, rather than instantiated. Chances are if you want to pass something in when it's being automatically depended upon then that instance of the class should be made as a singleton and passed in rather than letting a new instance of it be passed in.
The short name is to make it easy to use - I know polluting the global namespace is an antipattern, but it's one that was made for sites that have third party javascript on and which is not really controlled by the webpage. Chances are if you're using Closure Tools you're writing a large application and are in control of the global namespace - and if you're not it's easy enough to take the file and just do a replaceAll to put the namespace you need on it. All the modules I write are meant to be incorporated and compiled in to an application rather than be used separately, though I'm sure it's not too hard to change things.
In this case I think the decoration is okay, as if you take away Loader you can still use the class normally and inject the dependencies manually. Perhaps there should be an option to inject the dependencies when you instantiate and object, or some sort of registry - but then you would have to keep a registry of all the objects that could be instantiated. Keeping an array of strings keeps things decoupled and gives a hint to whatever IoC you go with a hint about what should be loaded. The actual angular approach (and one taken by others) is to parse the function to get a hint from the variables you pass in - but that's not possible with compilation. An array of strings seems to be the lesser of evils while making it easier to maintain a project. At some point you have to buy in to a certain way of coding to work well with whatever library you're using.
Have you got any ideas about matching up the Class with it's dependencies? I was going to try an array that pointed to a cache so it would compile down but then the class would break without Loader so I went back to an array of strings. I thought about passing in strings, but then the class that is instantiating it needs to know what sort of dependencies to pass in and they would have to be declared every time you instantiated a class. It would make it easier to make more abstract classes because you could pass in different hints to Loader and so you'd come back with classes that have different implementations of dependencies - which would be cool but tricky to keep track of.
But this is all just from me playing around and putting in a solution at work that seems to work for me. I'm hoping that putting it out there will get more people thinking about it.
actually coming to think of it, it might be cool to have Loader as a constructor so you could have separate instances which can put in separate requirements for the same string and then your object's dependencies that are passed in would depend on which instance you called to create the object. I know angular allows you to override some of the dependencies when creating a class, but this would mean that the dependency is somewhere in your code rather than defined right on the class where it is easy to find. I think having Ldr1, Ldr2 etc might be a better solution as it would be easier to trace where the dependencies come from
DeleteThat's pretty interesting. I wrote a similar library a while ago, maybe we can share some concepts.
ReplyDelete