martyjs / marty

A Javascript library for state management in React applications
http://martyjs.org
MIT License
1.09k stars 76 forks source link

Dependency Injection #261

Closed jhollingworth closed 9 years ago

jhollingworth commented 9 years ago

@sporto made some valid points on the marty channel on reactiflux about dealing with concurrency in Marty. Namely

A couple of points I would add:

So what can we do about this?

I think all the fundementals were introduced in Marty v0.9, we just need to tweek how we use them.

1. No more default instance

Rather than module.exports = Marty.register(SomeStore) you do module.exports = SomeStore.

This has the added benefit of making everything much easier to test.

2. Centralised registration

When you initialize your application your application you create a new instance of Marty (or maybe easier to create an instance of Registry?) which you register all types to

const marty = new Marty();

marty.register('./stores/fooStore');
marty.register('./actions/fooActionCreators')
marty.register('./queries/fooQueries');

I was thinking we could using bulkify to automate this process

var bulk = require('bulk-require');

const marty = new Marty();

marty.register(bulk(__dirname, ['app/**/*.js']));

3. Resolve using context

When we want to render a component we create a container component which adds marty to the context (i.e. What we do now in renderToString) using bindTo. The first time this function is called it will instantiate instances of all types

const marty = new Marty();

marty.register('./stores/fooStore');
...

var SomeComponent = require('./components/someComponent')
var SomeComponentWithMarty = marty.bindTo(SomeComponent); // This creates the container component

React.render(<SomeComponentWithMarty />);

To resolve the instance of a type you use this.resolve(obj) which will be available in Containers, Stores, ActionCreators and Queries. resolve would accept either a type or the Id of that type and return the instance. I was thinking we could allow you to register anything (e.g. object, config, etc) which would resolve #199.

class UserStore extends Marty.Store {
  constructor(options) {
    super(options);

    this.userQueries = this.resolve(UserQueries);
  },
  getUser(id) {
    return this.fetch({
      id: id,
      locally() {
        return this.state[id];
      },
      remotely() {
        return this.userQueries.getUser(id);
      }
    })
  }
}

One annoyance with this is its difficult to determine the dependency graph which out initialising everything.

In the future it might be nice to use annotations like angular 2.0

@Inject(Dispatcher, UserQueries)
class UserStore extends Marty.Store {
  constructor(dispatcher, userQueries) {
    super(dispatcher);

    this.userQueries = userQueries;
  }
}

Or take advantage of types in Flow

class UserStore extends Marty.Store {
  constructor(dispatcher: Dispatcher, userQueries: UserQueries) {
    super(dispatcher);

    this.userQueries = userQueries;
  }
}

One big question I don't know the answer to is an easy migration from v0.9. I would personally like Marty.create* (e.g. Marty.createStore) to also return a type (Internally we actually create a class for it already) and stop us from having a singleton registry. That's a massive breaking change so don't want to do that straight away.

I'm thinking a migration path would be to have Marty.useDefaultInstances = false. When true Marty.create* and Marty.register will behave like v0.9. When false (default in v0.10) Marty.register will throw an error and Marty.create* will return a type instead of default instance.

tldr

jhollingworth commented 9 years ago

@dariocravero @davidpadbury @oliverwoodings @taion would love your thoughts on this

jhollingworth commented 9 years ago

Important thing to note, when I do const marty = new Marty(); that will create a new instance of the dispatcher: marty.dispatcher.dispatchAction(..) so you can have multiple dispatchers on the page

taion commented 9 years ago

Constructor-based dependency injection is one of my favorite things about working with Java, but I don't think it maps well to dynamically typed languages. A good chunk of the benefit of DI is specifying everything on the level of interfaces, then being able to change my config for tests to register test doubles instead of actual implementations (so I could have separate StubUserApi or FakeUserApi classes as needed), but having something interface-like (i.e. having them all extend some UserApiInterface or something) feels unidiomatic in JS.

I think resolving based on id is better than resolving based on type. This would let me do something like

marty.register(FakeUserApi, 'UserApi');

for writing a test. Mocks are great, but in a lot of cases having an explicit test double can make life a lot easier when writing something a bit larger than a single unit test, and this sort of pattern makes it a lot nicer to do so.

I don't think it's indefensible to have Marty remain a singleton. I think there's something nice about smaller applications being able to use less boilerplate code, as long as the interfaces are consistent enough such that you can neatly move to the more strategic solution as your app expands, rather than having a learning cliff instead of a learning curve (like, say, Angular 1.x). I don't think there's anything wrong with having marty.register return an instance. If you need to register multiple classes with mutual dependencies, you ought to always be able to do (per your bulk example)

marty.register({
    UserStore: UserStore,
    UserApi: HttpUserApi
});

One last thing is that instead of doing

this.userQueries = this.resolve(UserQueries);

in the constructor, you could consider using something like the existing propTypes or contextTypes pattern, something like:

UserStore.martyTypes = {
    userQueries: UserQueries
}

This would also let you figure out the dependency graph without initializing everything.

taion commented 9 years ago

I'm not going to edit my above comment because I want my brain fart preserved for posterity, but I just realized it directly contradicts itself. Especially once you have property initializers, doing something like

class UserStore extends Marty.Store {
    static martyTypes = {userQueries: UserQueries};
}

is obviously semantically the same as constructor injection. Oops.

sporto commented 9 years ago

I think this changes are a great way forward.

I am not keen on the registration part thought:

marty.register('./stores/fooStore'); marty.register('./actions/fooActionCreators') marty.register('./queries/fooQueries'); This goes against the idea of modular code, if I want to add a store in one part of my app then in I need to come here to register it.

I suspect that all these can be done within marty.resolve, so registered on the fly.

On 4 Apr 2015, at 10:00 pm, James Hollingworth notifications@github.com wrote:

@sporto made some valid points on the marty channel on reactiflux about dealing with concurrency in Marty. Namely

It's overly complex (e.g. default instances, registries, etc) It's easy to forget to add the .for(this) (We do warn you if this is happening but you could argue this is a smell) A couple of points I would add:

The whole module.exports = Marty.register(SomeStore) has never sat well with me This came from a desire to maintain backwards compatibility with ES5 which in retrospect might not have been the best idea All types live within Marty making it difficult to build more modular applications (e.g. #260) So what can we do about this?

I think all the fundementals were introduced in Marty v0.9, we just need to tweek how we use them.

  1. No more default instance

Rather than module.exports = Marty.register(SomeStore) you do module.exports = SomeStore.

This has the added benefit of making everything much easier to test.

  1. Centralised registration

When you initialize your application your application you create a new instance of Marty (or maybe easier to create an instance of Registry?) which you register all types to

const marty = new Marty();

marty.register('./stores/fooStore'); marty.register('./actions/fooActionCreators') marty.register('./queries/fooQueries'); I was thinking we could using bulkify to automate this process

var bulk = require('bulk-require');

const marty = new Marty();

marty.register(bulk(_dirname, ['app/*/_.js']));

  1. Resolve using context

When we want to render a component we create a container component which adds marty to the context (i.e. What we do now in renderToString) using bindTo. The first time this function is called it will instantiate instances of all types

const marty = new Marty();

marty.register('./stores/fooStore'); ...

var SomeComponent = require('./components/someComponent') var SomeComponentWithMarty = marty.bindTo(SomeComponent); // This creates the container component

React.render(); To resolve the instance of a type you use this.resolve(obj) which will be available in Containers, Stores, ActionCreators and Queries. resolve would accept either a type or the Id of that type and return the instance. I was thinking we could allow you to register anything (e.g. object, config, etc) which would resolve #199.

class UserStore extends Marty.Store { constructor(options) { super(options);

this.userQueries = this.resolve(UserQueries);

}, getUser(id) { return this.fetch({ id: id, locally() { return this.state[id]; }, remotely() { return this.userQueries.getUser(id); } }) } } One annoyance with this is its difficult to determine the dependency graph which out initialising everything.

In the future it might be nice to use annotations like angular 2.0

@Inject(Dispatcher, UserQueries) class UserStore extends Marty.Store { constructor(dispatcher, userQueries) { super(dispatcher);

this.userQueries = userQueries;

} } Or take advantage of types in Flow

class UserStore extends Marty.Store { constructor(dispatcher: Dispatcher, userQueries: UserQueries) { super(dispatcher);

this.userQueries = userQueries;

} } One big question I don't know the answer to is an easy migration from v0.9. I would personally like Marty.create* (e.g. Marty.createStore) to also return a type (Internally we actually create a class for it already) and stop us from having a singleton registry. That's a massive breaking change so don't want to do that straight away.

I'm thinking a migration path would be to have Marty.useDefaultInstances = false. When true Marty.create* and Marty.register will behave like v0.9. When false (default in v0.10) Marty.register will throw an error and Marty.create* will return a type instead of default instance.

tldr

Room for improvement for dealing with concurrency Export types, not instances Registration happens in one central place Add this.resolve(Type) to get the instance of a type — Reply to this email directly or view it on GitHub.

taion commented 9 years ago

I really like the idea of centralized registration at least as an optional thing. I don't think it should be mandatory (i.e. the current Marty.register part should continue to work so smaller projects can be built with minimal boilerplate), but the registration actually promotes modularity in that it gives you a mechanism for swapping out implementations of concrete objects.

It's much, much nicer to be able to say, in test setup, that I should receive a fake or stub UserApi instead of having to mock everything. That sort of thing where you have a config that lets you swap out concrete implementations is one of the best things about DI frameworks, and it's a really powerful pattern for larger projects. It's just great to say something like:

UserStore.martyTypes = {
  userApi: 'UserApi'
};

and have the option to, in a config-driven way, resolve that separately to e.g. HttpUserApi in one case, WebsocketUserApi in a second, and FakeUserApi for a test. It's explicitly a benefit to be able to configure all of this in a centralized config location.

sporto commented 9 years ago

I have came to the opposite conclusion over the years, that in dynamic languages like JS a dependency injection framework is unnecessary.

Let's say you do:

marty.resolve(FooStore)

Then is easy to stub FooStore in a test by using something like rewire https://github.com/jhnns/rewire

My main point is that a central registry is painful to maintain. Say I add something to the registry, then is used in a component, later I remove the use but forget to remove that from the registry. Now I have dead code in my application bundle.

I like the way things get included in an organic way in a bundle by means of requiring them in the place where they are used. I see a central registry going against this.

On 5 Apr 2015, at 9:06 am, Jimmy Jia notifications@github.com wrote:

I really like the idea of centralized registration at least as an optional thing. I don't think it should be mandatory (i.e. the current Marty.register part should continue to work so smaller projects can be built with minimal boilerplate), but the registration actually promotes modularity in that it gives you a mechanism for swapping out implementations of concrete objects.

It's much, much nicer to be able to say, in test setup, that I should receive a fake or stub UserApi instead of having to mock everything. That sort of thing where you have a config that lets you swap out concrete implementations is one of the best things about DI frameworks, and it's a really powerful pattern for larger projects. It's just great to say something like:

UserStore.martyTypes = { userApi: 'UserApi' }; and have the option to, in a config-driven way, resolve that separately to e.g. HttpUserApi in once case, WebsocketUserApi in second, and FakeUserApi for a test. It's explicitly a benefit to be able to configure all of this in a centralized config location.

— Reply to this email directly or view it on GitHub.

taion commented 9 years ago

It's not just for testing, though. Having a registry lets me have a centralized place for switching concrete implementations even in production. If I have HttpV1UserApi and HttpV2UserApi that hit different endpoints on the server (e.g. /api/v1/... and /api/v2/...), having a centralized registry gives me a coherent way to swap out my implementation, as opposed to forcing me to either replace the old HttpUserApi class altogether, or the modify everything that needs any sort of UserApi.

Another benefit of having explicit central config is that in doing more integration-focused tests, it can be significantly less error-prone. If UserStore calls into UserQueries, and I want to ensure that functionality on UserStore calling into UserQueries works correctly, but I want to override UserApi for my test, then having a central config allows me to just swap out the registered implementation of UserApi. This is better than mocking out the concrete implementation of UserApi, because it means that you don't have an explicit dependency on the choice of UserApi itself.

I do agree that this adds a decent amount of overhead, and I don't think that it should be the default, but I do think that it's very little additional complexity on the library side, and it adds a lot of flexibility to take advantage of the benefits of DI.

taion commented 9 years ago

What I'm really saying, though, is that I don't see why you can't have both. If you don't want a centralized config, then in UserApi.js, you can do:

class UserApi extends Marty.HttpStateSource {
  ..
}
Marty.register(UserApi);

export default UserApi;

If you do want a centralized config, just move all the Marty.register calls into a separate config.js file.

I think it's almost the case that any reasonable implementation is necessarily going to work with both, no?

sporto commented 9 years ago

@taion so something like:

class UserApi extends Marty.HttpStateSource {
  ..
}
Marty.register(UserApi);

export default UserApi;

in queries:

class UserQueries extends Marty.Queries {
   fetchUsers() {
    let api = marty.resolve('UserApi')
    return api.fetch(...)
  }
}

Marty.register(UserQueries);

export default UserQueries;

So every component will need to be registered and then resolved? I would really like to avoid the part of registering things. Just have resolve do all the work.

oliverwoodings commented 9 years ago

@sporto could you give an example of resolve doing everything? I can't quite visual how this would work.

taion commented 9 years ago

@sporto That's very similar to the existing ES6 syntax, though.

taion commented 9 years ago

A couple of other points:

I don't think we're talking about DI per se. Or rather I don't think we're talking about anything beyond the difference between DI v using a service locator. Whether you do:

class UserStore extends Marty.Store {
  constructor(options) {
  super(options);

    this.userQueries = this.resolve(UserQueries);
  }

 ...
}

or

class UserStore extends Marty.Store {
  static martyTypes = {
    userQueries: UserQueries
  }

 ...
}

You're still relying on the container to resolve the class to an instance. The only difference between the two is whether you do IoC or not, but they're otherwise identical. Also, I think the IoC-based version is more consistent with how React contexts work currently with contextTypes, in that it's the same syntax.

Secondly, while you could potentially automatically register any concrete classes that you need to resolve (i.e. in the above example, if there is no registered UserQueries, just register the class that you attempted to resolve), I think this becomes a lot messier if you need to pass in options to the UserQueries constructor. Without the ability to do explicit registration, I don't think you'd have any choice but to pass in options every time you needed a UserQueries object. This seems more verbose and potentially error-prone than necessary.

jhollingworth commented 9 years ago

I do have a slight preference towards constructor injection. Mainly because I feel that stores, queries etc shouldn't be responsible for dependency resolution (i.e. breaks the single responsibility principle).

I like the idea of declaring your dependencies on the class. Not sure about martyTypes as we're potentially injecting in more than just types, what about inject? We could pass the dependencies through the options and automatically add them to the instance for you

class UserStore extends Marty.Store {
  // Using ES7 class properties
  static inject = {
    userQueries: UserQueries
  }
  constructor(options) {
    super(options);

    //this.userQueries == options.userQueries
  }
  getUser() {
    return this.userQueries.getUser(123);
  }
}

UserStore.inject = {
  userQueries: UserQueries
};

Testing becomes really simple at this point

var store = new UserStore({
  userQueries: new MockUserQueries()
})

This also plays nicely with containers

module.exports = Marty.createContainer(User, {
  inject: {
    userStore: 'UserStore'
  },
  fetch: {
    user() {
      return this.userStore.getUser(123)
    }
  }
})

Another added bonus for constructor based dependency injection you could register any type you like

class Something {
  static inject = {
    userQueries: 'UserQueries'
  }
  constructor(options) {
    this.userQueries = options.userQueries;
  }
}

I don't think it's indefensible to have Marty remain a singleton. I think there's something nice about smaller applications being able to use less boilerplate code

I'm not certain if there is much more boiler plate code with marty being an instance. In my mind its just one extra line:

var marty = new Marty();

marty.register('./stores/fooStore');

var Home = marty.bindTo(require('./views/home'));

React.render(<Home />, document.getElementById('app'));

//vs.

Marty.register('./stores/fooStore');

var Home = Marty.bindTo(require('./views/home'));

React.render(<Home />, document.getElementById('app'));

I'd like to get away from a singleton if possible as it makes testing tricker. We will need to keep legacy support for it for a while though. Although I'm wondering if its possible for Marty to be an instance of itself so we have a single code base for everything.

I would really like to avoid the part of registering things. Just have resolve do all the work.

I like the idea of resolve(Type) instantiating an instance of the type if it hasn't been seen before however I have a concern about stores. Say you have a store that is used by a component which is not immediately rendered, it wouldn't be able to handle any actions that were dispatched before that component is rendered. The only way around this is to instantiate all stores when the application starts which means (in my eyes) we need some sort of registration.

This goes against the idea of modular code, if I want to add a store in one part of my app then in I need to come here to register it.

It should be fairly easy to split registration out into a few separate files, e.g.

Marty.register(require('./stores'));
Marty.register(require('some-node-package'));
Marty.register({
    foo: {
        bar: 'baz'
    }
});

Marty.resolve('foo') === { bar: 'baz' };

//stores/index.js
module.exports = [
    require('./fooStore'),
    require('./barStore')
];

My concern with advocating registration happening alongside the definition of the type is it only works when Marty is a singleton. If you were to have multiple instances of marty (e.g. As a solution to #260) you'd have to pass that instance to every file for it to register itself which seems like a lot of work.

taion commented 9 years ago

Net of the issues with when stores get instantiated, I think the auto-resolve functionality would be better than having the register method sit on a singleton for purposes of minimal-boilerplate small apps.

I think there might be value in separating out the object on which you make registrations from the object that does the resolution. I believe there's some amount of work involved in traversing the dependency tree, and that it would be preferable to do this once. This would look something like:

// config.js
const config = new MartyConfig();  // I miss C const semantics.
config.register('UserStore', HttpUserStore);

export default config;

// app.js
import config from 'config';

var marty = new Marty(config);
var Home = marty.bindTo(Home);

This would actually enable the user to make the config object be a user-level singleton, and e.g. do registration per-file or something, if desired. Also, if register remains the only method on this object, I don't think it'd be too bad to have:

// marty.js
const defaultConfig = new MartyConfig();
export const register = defaultConfig.register.bind(defaultConfig);
taion commented 9 years ago

The other thing this would potentially let you do is define

export function bind(Component) {
  const marty = new Marty(defaultConfig);
  return marty.bindTo(Component);
}

I think this is relatively lightweight code-wise, enough such that the benefit of of (my) being able to work with less explicit scaffolding outweighs the cost of (your) maintaining this code. :stuck_out_tongue_closed_eyes:

sporto commented 9 years ago

could you give an example of resolve doing everything? I can't quite visual how this would work.

Pretty much the idea that .resolve would look for an instance of UserStore in the registry, if it doesn't find it then it creates it and returns the newly created instance. But it seems this might not fully work because the stores need to be instantiated early.

I do have a slight preference towards constructor injection

This looks nice. Seems flexible enough to accommodate many use cases.

I like the idea of resolve(Type) instantiating an instance of the type if it hasn't been seen before however I have a concern about stores.

True, the stores have no way to respond to anything until they get instantiated in a component. :(

I think there might be value in separating out the object on which you make registrations from the object that does the resolution.

In understand that you are creating a new instance of MartyConfig once and then using that singleton for every server request. That seems potentially trouble if that instance gets modified in a request. I would prefer to declare the config on the marty instance itself or force you to create a new config each time.

taion commented 9 years ago

I think in general, you're going to want your registrations to remain static. Certainly if it is the case that you're going to be modifying your registrations on-the-fly, then you would need to have a separate configuration per Marty instance, but I don't think it will usually be the case that you will do so.

In general, I feel like mucking with those registrations would morally be very similar to mucking with e.g. UserStore.prototype in a request. You can come up with reasons why you would do so, but it seems somewhat unlikely.

There's nothing preventing you from setting up your registrations per-request, but I think in the usual case it's going to be easier to treat that configuration as a singleton.

sporto commented 9 years ago

My main gripe has been with the idea of having to maintain a central registry. I really see this as a maintenance headache. If it can be avoided that would be wonderful, if not then ok.

jhollingworth commented 9 years ago

@sporto What about using something like bulkify (I'm going to assume theres a webpack equivalent) as a way of automatically registering everything?

var bulk = require('bulk-require');

const marty = new Marty();

marty.register(bulk(__dirname, ['app/**/*.js']));
taion commented 9 years ago

I think decentralized config with eager instantiation of stores would be one of the patterns that could be enabled with a config singleton. You would do:

// config.js
export default const config = new MartyConfig();

// UserStore.js
export default class UserStore extends Marty.Store {
  ...
}
config.register(UserStore);

// Component.js
export default class Component extends React.Component {
  static inject = {
    userStore: UserStore
  }

  ...
}

// app.js
const marty = new Marty(config);
const ComponentWithMarty = marty.bindTo(Component);

And with decorators, you could even write the store as

@config.register
class UserStore extends Marty.Store {
  ...
}

if you're not overly concerned about having multiple configurations.

oliverwoodings commented 9 years ago

I think I agree with @sporto about a central registry being a maintenance headache.

@taion I'm not sure I like the idea of naming it 'config' - surely it is just a registry, unless you have some other ideas of 'configuration' that it could be used for?

jhollingworth commented 9 years ago

Not a fan of having to require in an a custom object for registration. Say someone had put some stores/actions into a node module, it would be difficult to register them since they would need to know about the config/registry object within the node module. Decoupling registration from definition gives you much more flexibility. Also, looking at this from the perspective of someone new to Marty, having registration happen with the definition seems like a lot of boiler plate code.

var Marty = require('marty');

class SomeStore extends Marty.Store {
  ...
}

module.exports = SomeStore;

//vs.

var Marty = require('marty');
var config = require('../../config');

class SomeStore extends Marty.Store {
  ...
}

module.exports = config.register(SomeStore);

//vs.

var Marty = require('marty');

class SomeStore extends Marty.Store {
  ...
}

module.exports = Marty.register(SomeStore);

That said, I can understand why some people would want to register within the class. Unless I'm missing something the proposed approach doesn't prevent you from doing it if you want. This seems to be more a debate about style no?

jhollingworth commented 9 years ago

@taion

I think there might be value in separating out the object on which you make registrations from the object that does the resolution.

Agree with you there however I'm concerned its yet another concept for developers to learn. Do you think theres a way we could have this as an internal concern, exposing a simpler API to developers. Also I think the Registry is pretty much the same as MartyConfig

taion commented 9 years ago

Ah, yes. Okay, to put it all together, I think we should do something like:

// marty.js
export class Registry {
  register(clazz) { ... }

  ...
}

const defaultRegistry = new Registry();

export const register = defaultRegistry.register.bind(defaultRegistry);

// maybe ...
export function bind(Component, registry = defaultRegistry) {
  const marty = new Marty(registry);
  return marty.bindTo(Component);
}

What this ends up giving you is that in most cases, where you only have a single application, you just use Marty.register. If you have multiple sub-applications, then you can register multiple Registry objects and explicitly pass them in when constructing Marty implementations.

I think this leaves you with something that's pretty similar to the current code for the normal use case (except you have to specify inject on your classes and have to explicitly bind before rendering), but gives advanced users the flexibility to manage multiple custom Registry objects if they need to do so.

taion commented 9 years ago

To add to that, I think this explicitly enables two use cases.

1.

// UserStore.js
export default class UserStore extends Marty.Store { ... }
Marty.register(UserStore);  // Implicitly using Marty.defaultRegistry

// app.js
const HomeWithMarty = Marty.bind(Home);  // Implicitly using Marty.defaultRegistry

2.

// UserStore.js
export default class UserStore extends Marty.Store { ... }

// registry.js
const registry = new Marty.Registry();
registry.register(UserStore);
export default registry;

// app.js
const HomeWithMarty = Marty.bind(Home, registry);

The two are very similar conceptually. I agree with @sporto that in most cases you don't want the centralized registrations, and I think this accomplishes that in a way that is fairly straightforward and doesn't involve anything too opaque.

I'd expect almost everyone to use case (1), but case (2) looks pretty similar and enables the kinds of things we've been talking about, and is a pretty small conceptual jump between the two that's mostly stylistic.

This leaves the question - should DI always be required? I think it adds a small barrier to entry to someone who would want to work just in the browser and is totally fine with using singletons everywhere. On the flip side, keeping the singletons means you end up having to change everything pretty extensively if you want to move your code to run on the server.

sporto commented 9 years ago

re. bulkify

I'm not a fan of this shotgun approach. I want to require the code I use and nothing else.

re. Config class

This makes Marty looks complex. What about just passing a configuration hash to Marty and register directly on the marty instance?

const options = {...}
const marty = new Marty(options)

marty.register(FoosStore)

re. Multiple ways of doing thing

I would prefer to avoid this, just have the no-singleton approach. Having two possible approaches will be confusing. I can imagine the docs trying to deal with this and just confusing people.

taion commented 9 years ago

My intention with the config class was just to have some separate object for storing all the registrations. It seems odd to me semantically to define registrations on the Marty object. In most cases your class registrations ought to be tied to your application class, not to a specific instance of your application.

The benefit of having the "default registry" singleton is that it makes it easier to do the thing most people want to do, which is to have a single application with a single set of stores, &c. running. I think it on balance, it's nice to have the standard use case be as simple as possible, and introduce complexity as it's needed.

jhollingworth commented 9 years ago

I've just started a PR looking at resolving this issue. Let me know if it makes sense

dariocravero commented 9 years ago

I'm drafting some code trying to solve the same issue but with a slightly different approach. I believe it simplifies the code base a great deal. Need to tie a few things in before I see whether it makes sense or not...