Module-level dependencies gathering up - best practices?

Topics: Writing modules
Developer
Nov 14, 2011 at 11:10 PM

Recently I've come to the problem that although dependencies are handled on per-feature basis, several module-level dependencies can quickly arise: most of the time service or model classes are used from the module our module claims being dependent on, therefore one should add a reference in the own module's project to the other module's project. Now since a missing project reference causes compile-time errors, AFAIK there is no way (other than juggling with dynamically loading assemblies with Reflection) to have a feature in a module that misses a "C# dependency" (not some static one like jQuery) even it's not enabled.

To make things concrete, I'm having some issues with my Facebook Suite module: one of the feature would depend on another module's feature, which in term would also depend on another one. This would cause that even if the user would never use that feature, she/he would have to install two additional modules (which I sincerely hope will soon be handled automatically).

Am I missing something? What approach do use? Are there any solutions other than breaking up the module into multiple ones?

Coordinator
Nov 14, 2011 at 11:18 PM

Not sure what the question is here.

Developer
Nov 14, 2011 at 11:31 PM

An example:

My module's feature (let's name it Feature A) depends on Comments (this is only an example), because its functionality incorporates creating comments automatically. The module has two other features, Feature B and Feature C that have nothing to do with Comments. Now the dependency on Comments is declared in Module.txt at Feature A.

However, to use ICommentService in the classes of Feature A the module's project file should have a reference to the project Orchard.Comments. Now if a user installs my module but only wants to use Feature B and Feature C but never Feature A then she/he is still required to install Orchard.Comments (if not necessarily enabling it), since my module's project has a reference to it. Otherwise the solution won't compile.

Nov 15, 2011 at 12:03 AM

When I hit this, I create a separate module. An example is the CacheEviction module I made in Science Project - it interfaces between Mechanics.Plumbing and Contrib.Cache to support evicting page cache records for Plumbing's custom routing (if that makes sense!) This is only neat way to do it really.

Developer
Nov 15, 2011 at 7:28 PM

Thanks, that's what I though. Anyway, this wouldn't really be an issue if the dream of proper dependency handling would become true.

I've thought about completely loosely-coupling modules by introducing webservice-like communication interfaces (like one module would expose service class functionality with a controller action that returns JSON), but that just looks like overcomplicating.

Coordinator
Nov 15, 2011 at 7:44 PM

Mmh, you do know about the message bus don't you?

Nov 15, 2011 at 7:45 PM
Edited Nov 15, 2011 at 7:46 PM

Actually, there's one other possibility, I've been meaning to ask the team about this, maybe someone can elaborate on this.

I've noticed in 1.3, some of the modules appear to use a form of duck typing in order to interface with rules.

Example: Orchard.Core has no dependencies on anything except Orchard.Framework. But yet, Orchard.Core/Contents has a Rules feature which adds auditing events to the Rules UI. If you look at Orchard.Core/Contents/Rules/ContentEvents.cs you'll see this:

    public interface IEventProvider : IEventHandler {
        void Describe(dynamic describe);
    }

    [OrchardFeature("Contents.Rules")]
    public class ContentEvents : IEventProvider {
        public Localizer T { get; set; }

        // ... Implementation ...
    }

So the definition of IEventProvider precisely matches the definition of the same in Orchard.Rules, and it appears that dependency injection is performing some kind of duck typing to allow this class to be injected in Orchard.Rules. You'll see the same in Orchard.Core/Contents/Rules/ContentForms.cs, and in various other modules that interact with Rules and Forms.

What I'm wondering is: does this apply to all IEventHandler dependencies (as opposed to IDependency), is it even special-cased for IActionProvider/IFormProvider/IEventProvider, or will it actually work for any dependency?

I'm surprised the team hasn't made more noise about this ability, since it potentially solves many of the difficulties currently associated with module dependencies. Does anyone have more information on what's going on here?

Edit: Is this what Bertrand is referring to about the message bus?

Coordinator
Nov 15, 2011 at 7:49 PM

Yes, that's the event bus. It's fairly general, but we have one specific implementation that matches messages to interface and method names. That enables you to call into an interface without actually taking a hard .net dependency on it.

Nov 15, 2011 at 7:55 PM

That's incredibly handy; it had occurred to me there must be some behavioural distinction between IDependency vs IEventHandler but I hadn't figured out what it was. This does only apply to IEventHandler derivatives, right?

Developer
Nov 15, 2011 at 8:04 PM

Just wow. I've never known such functionality is present in Orchard. Very nice, thanks a lot!

Although most likely I won't use this in this specific case (it looks creating a new module is needed anyway), but this looks like a very clever thing. But I'd also like to know whether this could be used with any interface.

Nov 15, 2011 at 8:05 PM

Well, it was right there in the Orchard docs: http://www.orchardproject.net/docs/How-Orchard-works.ashx#Event_Bus_13

That description doesn't specify whether it's only IEventHandler or all IDependencies - maybe it's both? Also that document hasn't been updated since 0.8, I was considering updating it but most of it looks still correct.

Developer
Dec 20, 2011 at 7:13 PM

As Sebastien described here, IEventHandler is also more than an IDependency in terms that when injecting an IEventHandler instance and calling its methods, actually the respective method of all the dependencies implementing IEventHandler will be called!