views:

381

answers:

5

I'm using StructureMap at the moment, generally with convention-based (Scan()) auto-configuration, and I'm looking to add decorator-based caching into the pipeline.

If I configure it manually that is fine, but Scan() is just so convenient when you get lots of dependencies... I'm toying with noting cache suggestions on the interface(s), for example:

public interface IFoo {
    [CacheDuration(20)] // cache for 20 minutes
    string[] DoSomethingReusable();

    SomeType DoSomethingNonReusable(int key); // not cached
}

with the idea being that by adding a custom "convention" to StructureMap's scanning (pretty easy) it can spot that one-or-more methods are decorated for caching, and automatically inject a generated caching decorator into that type's pipeline (generating a cache key from the interface/method name and parameter values).

On the plus side it makes adding caching very painless - just decorate the interface a little; but is is a code smell? And/or am I duplicating something that is already solved?

+2  A: 

The only drawback I see here is that there is no central spot where objects are cached. Usually there is one specific layer where caching is done. With your technique, this is not the case.

I also think it's the responsibility of your code to decide whether something should be cached or not, not that of a class/interface.

But to answer your question, is this a code smell? I'd say no, but it might be confusing for new developers to get the hang of this system once it's used in a lot of places.

Gerrie Schenck
+1  A: 

I can't say I totally understand everything, but for my 2 cents, it seems arguably okay; but I just wonder whether or not it's easy to change it without recompilation. And maybe you would wish to change that cache duration via config entry, and not a compile. I'd probably store that data somewhere where I can configure easier. (Perhaps I've misunderstood though ...)

Noon Silk
The thing there is; *all* changes (code/config/etc) go through the same process (build server, validation etc); it is just as simple to push a code change up through the system as it is a change to a configuration file. Except in code I have static type checking ;-p
Marc Gravell
@Marc Indeed; but perhaps you might want to change it while running (i.e. the value might be stored in a db?). I guess I like to have all "config" in the same kind of area, then you can change it all trivially. I.e. if you want to change all your 20s to 40s, or similar. Just the first thing I thought of anyway. I don't hate the idea, but if it were me, I think I'd let that be configurable somewhere (db, if your underlying app uses one).
Noon Silk
+6  A: 

Attributes are OK if you're aware of two major factors:

  • inherently decentralized structure, instead of in one place you have your metadata sprinkled on each and every component it touches

  • rigid compiler constant structure. If you suddenly decide that your cache duration should be 10, not 20, you have to recompile all your code. You could route attributes to things like config, but that's working around the attributes, and at this point you should really reconsider whether using them was the best idea in the first place.

As long as you're aware of these issues, and OK with them, than go ahead.

Krzysztof Koźmic
+1 This answer raises a few points I hadn't thought of. More ammo against attributes :)
Mark Seemann
Re the second; for our environment rebuilding the entity is no different to rebuilding the top-level code that registers/configures things.
Marc Gravell
it might not be, important part is - they're compile time constants, with all limitations that go with it.
Krzysztof Koźmic
+1  A: 

I tend to agree with what was said about the caching having to be decided by your code but another scenario that I see that you might want to take into consideration is the "What needs to be cached, the entire object?

You might want to toy with the idea of having properties to set which members you don't want to cache of an object.

Maybe an Attribute

[Cache(Duration=20, Location(...))]

?

PieterG
I think this will get out of hand fast (more attributes). In that case your better off setting up specific classes to handle specific "methods" of caching that accept objects with various interfaces specifying how they wish to be cached (or some such scheme).
Noon Silk
I totally agree :) Thanks for the insight.
PieterG
+2  A: 

As far as I understand the question, you are mainly considering adding the Attributes to make container registration easier. You are still implementing the caches using the Decorator design pattern, which is, IMO, the correct way to implement caching.

We do the same in Safewhere, but we use convention-based registration instead. We also use Castle Windsor, so I don't know if this is possible with StructureMap, but we simply scan the appropriate assemblies after anything named Caching*Repository and register those as Decorators for the real Repositories. We also have a convention-based unit test that verifies that all required Caching Repositories are present.

Whether adding the custom attribute is a code smell or not depends on the degree of reusability you require of your code. My rule of thumb is that I want to be able to wire everything up with Poor Man's DI. I still use a DI Container instead, but this rule provides me with a sanity check.

In general I don't like coupling my code to a particular container, but I can't really figure out whether you are doing that here. It depends on whether or not you need to reference StructureMap to define the custom attribute. If you must reference Structure Map, I would consider it a smell.

Mark Seemann
No, the attribute is not container specific.
Marc Gravell