Butler object = Service Locator ?

Events happening in the community are now at Drupal community events on www.drupal.org.
donquixote's picture

In his article about dependency injection, Martin Fowler makes a distinction between "dependency injection" and a "Service Locator".
http://martinfowler.com/articles/injection.html#ServiceLocatorVsDependen...

Dpi in a very general sense just means that consumer components (plugins, page callbacks, etc) get their dependencies (information about current user, current language, gateways to load nodes and other pieces of content) injected as parameters (constructor, setter, or just function parameters), so they do not have to pull this information from global state (global vars, singletons, functions with global state dependencies). Something that should be a policy in any sane project, but is desperately missing in Drupal.

In Fowler's article, dpi is something more specific:
- The consumer component does not get one know-it-all gateway object, but rather a bunch of tiny objects, representing different pieces of the information that is to be injected.
- There is a system that looks at the signature of the class to instantiate (constructor signature, existing setters, implemented interfaces), and that can automatically choose which objects need to be injected into the to-be-created object. To generalize this concept, we could replace the signature with a definition array, and put a factory in between the injector system and the class to instantiate.
- This also implies that objects are never constructed by the consumer component, but by the injector system.

The alternative he mentions is the "Service Locator":
- Instead of a bunch of tiny objects, every consumer component gets the same "service locator" object injected.
- The service locator knows how to respond to requests for specific pieces of information.
- Usually it will delegate these requests to registered or lazy-loaded sub-services ("plugins" ?).

Interesting observation: The service locator can be regarded as a special case of the dependency injection, where every constructor or factory wants nothing but a service locator injected. This also means that we could mix both concepts, if we really want to.

I had the impression that this service locator is very close to the butler concept.
Is this observation correct?
What do we gain, having found this existing term?
Why have I not seen the term in previous discussions?

Comments

Seems so

Crell's picture

Well, there's two parts here, which for lack of a better term I'll refer to as "dependent data" and "dependent services".

Dependent data is things like the node on which to operate, user on which to operate, the HTTP request on which to operate, etc. That's what the Butler/context object is primarily concerned with.

Dependent services would be things like the database connection object, cache system object, possibly even an output object to which we could attach JS and CSS, etc. So far we haven't discussed that in depth, and we don't know if that makes sense as part of the Butler object or not. Jury is still out there, I think.

Given the definitions above I believe the Butler object would definitely qualify as a "Data Locator", along the lines of the Service Locator. I haven't really been using the term to date because, well, getting 1000+ developers on board with the idea of dependency injection instead of just randomly calling functions willy-nilly is hard enough without introducing even more terms and pattern names. :-)

How we'll handle service objects, which in our case I suspect will mostly map to plugins, is, I think, still undefined. I would actually love it if we could build a proper Service Locator / factory for them, but I don't know if that is reasonable to do in a universal fashion. That's something we need to figure out once we get a better idea of what the trade-offs are.

"service objects" vs "plugins"

donquixote's picture

I haven't really been using the term to date because, well, getting 1000+ developers on board with the idea of dependency injection instead of just randomly calling functions willy-nilly is hard enough without introducing even more terms and pattern names. :-)

depends which folks you want to attract.
And I agree we should avoid throwing pattern names around like "let's get mvc for Drupal" or whatever. And even just using a term instead of an explanation can be a very bad idea in the Drupal world. But it can be very useful to at least mention terms, and say how what we do is similar or different, as long as we provide the full explanation alongside.

How we'll handle service objects, which in our case I suspect will mostly map to plugins, is, I think, still undefined.

This "plugin" part is still mysterious to me. Plugin is such a generic term.
As someone mentioned earlier, almost any object injected into another object (as a "policy" or "observer" or whatever) can be called a "plugin".

I assume we are talking about plugins for the butler?

This is what I imagine:
- In or around the butler we have some kind of lazy loading mechanism for data and/or services.
- The butler itself is something like a data locator or service locator, that can be given to page callbacks, theme functions etc as a "know-it-all" object.
- Modules can tell the butler about their data and services. This is what we call butler plugins. Again, this has to be with lazy loading, so the module only tells how to obtain the desired data.
- Instead of using the full butler object, page callbacks and other can declare their dependencies as something more specific.

We already do let page callbacks and theme functions declare a list of dependencies, even in D6. So, just a small step to extend that with an explicit dependency to the butler object or to individual services.

This way we have a smooth transition from the know-it-all data locator, to a know-it-all service locator, to an injection system for very specific dependencies.

No

Crell's picture

I am extremely wary of turning the context object into a a god object. That way lies madness. In fact, your description has convinced me that would be a bad idea. :-) We don't want the context object to do double duty as a service locator.

The context object is concerned with contextual data, and data that derives from contextual data: That is, the HTTP request and its various descendants. Plugins are an entirely separate system, which have a channel to provide context TO plugins.

These are three separate concepts that should be kept separate:

  • Logic (plugins)
  • Data (contextual or otherwise)
  • Configuration (of plugins or otherwise)

Muddling those together creates well, a muddle. We're trying to fix a muddle, not introduce a new one.

There may be value to explicit definition of necessary service objects for plugins; I don't know. But I am leaning further and further toward the context object not being where that happens.

god object There is a

donquixote's picture

god object

There is a difference between a god class and a god object, or a godly "gateway to things".

A god class has a lot of unrelated problem-specific code all in methods of the same class. This is something to be scared about.

A god object might be a container stuffed with unrelated information of all kinds. The class this object is an instance of might be very small and tidy and generic. The minimal example is an stdclass stuffed with information (if for some reason we choose not to use an array). Or it could be a readonly-wrapper for an array with data.

A godly gateway can provide us with all kinds of things, but the class itself can again be very minimal and generic. The gateway uses plugins to respond to problem-specific requests.
Or, think of a db connection object. This object has access to everything in the database, but the code itself is quite tidy and generic and problem-agnostic. Nothing to be scared about.

We don't want the context object to do double duty as a service locator.

An example.
1. Context ("butler") as a data provider, giving us the current language. We have to respect this setting whenever we call t().
2. Context ("butler") as a service provider, giving us an object that can do $i18n->t($str) for string translation.

It would be attractive having an object that can do $i18n->t(). If this is not provided via the butler system, then we want another system for the same purpose.

In the end I think it is very desirable to have either a service locator or something like signature-based dependency injection or both. The "consumer" would be things like page callbacks, block functions, wildcard loaders, theme functions etc. The "services" would be things that provide $i18n->t(), $theme->theme(), but also $entity_gateway->loadEntity() etc. I don't see another option, if we want to get out of the global state mess.

Both options involve some "spider in the web" system, something that can locate services based on configuration, current page etc. But this system itself has to be very generic and agnostic. Modules can tell the system about services they provide, they can provide configuration forms and then hook into the system to modify service weights, etc.

These are three separate concepts that should be kept separate:
- Logic (plugins)
- Data (contextual or otherwise)
- Configuration (of plugins or otherwise)

I am not convinced. Or I don't get it.

Scope creep

Crell's picture

It would be attractive having an object that can do $i18n->t(). If this is not provided via the butler system, then we want another system for the same purpose.

I agree completely. However, at the moment I don't believe that should be the context butler itself. At least for now I'd rather keep that focused on contextual data, not contextual services. A universal service locator is, I feel, out of scope for WSCCI, at least for the time being. We are not, yet, looking to replace all db_query() calls with $this->context->getDb()->query(). We may figure out a way to do so later, once Drupal is more plugin-ified. I don't know. At least for now, though, I feel our scope is big enough that we don't need to add more moving parts until we get at least some of the ones we already have scoped out knocked out of the park. Then we can circle back to the question of a universal service locator.

focused on contextual

donquixote's picture

focused on contextual data

Is this going to be a big array, or a smart container with lazy loading?

In the latter case, we are not so far away from a service locator. The plugins that do the lazy loading could choose to return service objects instead of raw data.

The "butler" itself would only be the infrastructure to make this stuff available to page callbacks and friends. For this infrastructure it doesn't make a big difference if it provides data or services.

Isomorphic does not mean identical

Crell's picture

The context object is a smart container with lazy loading. However, just because that makes it "not so far away" from a service locator (which is arguably true) doesn't mean we should use it as a service locator. An object should be responsible for one (1) thing. The context object is responsible for being a broker for contextual data. Period. It should not start adopting a dozen other functions as well.

If we also want an injectable service locator, that's another matter. We may, but not right now. That does not belong in the context object, because dependent services are not context. They are dependent services.

Web Services and Context Core Initiative

Group organizers

Group notifications

This group offers an RSS feed. Or subscribe to these personalized, sitewide feeds: