Castle Dynamic Proxy tutorial part X: Interface proxies with target interface

This is part ten of my tuto­r­ial on Cas­tle Dynamic Proxy.

The three kinds of prox­ies we talked about so far (class proxy, inter­face proxy with and with­out tar­get) are the only kinds of prox­ies most users will ever use. There is how­ever one more kind – inter­face prox­ies with tar­get interface.

When I say most users will never use it, by most I mean roughly 99%. I, per­son­ally haven’t used it. Whole Cas­tle project stack uses it in only one place that I know of – WCF facil­ity. This proxy kind is used in really rare cases, and you can feel free to skip read­ing this part, because you prob­a­bly won’t need infor­ma­tion pro­vided here anyway.

For those two of you who decided to go along any­way, here’s the expla­na­tion of what it is, and how it’s dif­fer­ent from the other kinds. As you might infer from the name, it’s quite sim­i­lar to inter­face proxy with tar­get. It’s basi­cally dif­fer­ent in one impor­tant way: it lets you swap tar­get of invo­ca­tion for a dif­fer­ent imple­men­ta­tion of the tar­get inter­face. Let’s look at an exam­ple to clear things up a lit­tle bit.

Let’s say we have an appli­ca­tion that saves some stuff into some stor­age. We have our pri­mary stor­age that we want to use most of the time, but when it for some rea­son is down, we want to use a sec­ondary stor­age. For exam­ple we have a data­base on a remote server, but when it’s down we want to use an in mem­ory database.

Any­way, in our triv­i­al­ized exam­ple tests might look like this:

public class Tests
{
    private PrimaryStorage _primaryStorage;
    private SecondaryStorage _secondaryStorage;
    private StorageFactory _sut;

    public Tests()
    {
        _primaryStorage = new PrimaryStorage {IsUp = true};
        _sut = new StorageFactory(_primaryStorage);
        _secondaryStorage = new SecondaryStorage();
        _sut.SecondaryStorage = _secondaryStorage;
    }

    [Fact]
    public void Save_should_use_primaryStorage_when_it_is_up()
    {
        IStorage storage = _sut.GetStorage();
        string message = "message";
        storage.Save(message);

        Assert.Empty(_secondaryStorage.Items);
        Assert.NotEmpty(_primaryStorage.Items);
        Assert.Equal(message, _primaryStorage.Items.First());
    }

    [Fact]
    public void Save_should_use_secondaryStorage_when_primaryStorage_is_down()
    {
        _primaryStorage.IsUp = false;
        IStorage storage = _sut.GetStorage();
        string message = "message";
        storage.Save(message);

        Assert.Empty(_primaryStorage.Items);
        Assert.NotEmpty(_secondaryStorage.Items);
        Assert.Equal(message, _secondaryStorage.Items.First());
    }

    [Fact]
    public void Save_should_go_back_to_primaryStorage_when_is_goes_from_down_to_up()
    {
        IStorage storage = _sut.GetStorage();
        string message1 = "message1";
        string message2 = "message2";
        string message3 = "message3";

        storage.Save(message1);
        _primaryStorage.IsUp = false;
        storage.Save(message2);
        _primaryStorage.IsUp = true;
        storage.Save(message3);
        IList<object> primary = _primaryStorage.Items;
        IList<object> secondary = _secondaryStorage.Items;

        Assert.Equal(2, primary.Count);
        Assert.Equal(1, secondary.Count);
        Assert.Contains(message1, primary);
        Assert.Contains(message3, primary);
        Assert.Contains(message2, secondary);
    }
}

In the tests Stor­age­Fac­tory class is the class that encap­su­lates the prox­y­ing logic, and Pri­ma­ryS­tor­age and Sec­ondaryS­tor­age are just sam­ple imple­men­ta­tion of the IStor­age inter­face. The inter­face is as sim­ple as it can get:

public interface IStorage
{
    void Save(object data);
}

The imple­men­ta­tion are also very slim for the pur­pose of the exam­ple. Sec­ondaryS­tor­age stores the mes­sages in the list, and exposes it for the tests as a property:

public class SecondaryStorage : IStorage
{
    private IList<object> _items = new List<object>();

    public IList<object> Items
    {
        get { return _items; }
    }

    public void Save(object data)
    {
        _items.Add(data);
    }
}

Pri­ma­ryS­tor­age addi­tion­ally has IsUp prop­erty that we’ll use to deter­mine whether it’s good to use, or not. In the later case we save to the sec­ondary stor­age, as can be seen in the tests.

public class PrimaryStorage : IStorage
{
    private IList<object> _items = new List<object>();

    public IList<object> Items
    {
        get { return _items; }
    }

    public bool IsUp { get; set; }

    public void Save(object data)
    {
        _items.Add(data);
    }
}

So far we haven’t seen any­thing inter­est­ing. The tests, pro­vided they pass, which they do with our final imple­men­ta­tion, clearly show that indeed when IsUp prop­erty of pri­mary stor­age is false, mes­sages get saved to the sec­ondary stor­age. If we look at Stor­age­Fac­tory, we’ll see that there is noth­ing we haven’t seen before in this tuto­r­ial. Obvi­ously it cre­ates the proxy using method we haven’t used before, but except for that, this is all pretty stan­dard stuff.

public class StorageFactory
{
    private readonly IStorage _primaryStorage;
    private ProxyGenerator _generator;

    public StorageFactory(IStorage primaryStorage)
    {
        _primaryStorage = primaryStorage;
        _generator = new ProxyGenerator();
    }

    public IStorage SecondaryStorage { private get; set; }

    public IStorage GetStorage()
    {
        var interceptor = new StorageInterceptor(SecondaryStorage);
        object storage = _generator.CreateInterfaceProxyWithTargetInterface(typeof(IStorage), _primaryStorage, interceptor);
        return storage as IStorage;
    }
}

The really inter­est­ing stuff hap­pens in Stor­ageIn­ter­cep­tor class.

public class StorageInterceptor : IInterceptor
{
    private readonly IStorage _secondaryStorage;

    public StorageInterceptor(IStorage secondaryStorage)
    {
        _secondaryStorage = secondaryStorage;
    }

    public void Intercept(IInvocation invocation)
    {
        var primaryStorage = invocation.InvocationTarget as PrimaryStorage;
        if (primaryStorage.IsUp == false)
        {
            ChangeToSecondaryStorage(invocation);
        }
        invocation.Proceed();
    }

    private void ChangeToSecondaryStorage(IInvocation invocation)
    {
        var changeProxyTarget = invocation as IChangeProxyTarget;
        changeProxyTarget.ChangeInvocationTarget(_secondaryStorage);
    }
}

Here you can see how the tar­get sub­sti­tu­tion is achieved. For inter­face prox­ies with tar­get inter­face invo­ca­tions imple­ment addi­tional inter­face – IChange­Prox­y­Tar­get. This is true for only this kind of prox­ies. The inter­face is very sim­ple – it has only one method:

dptutorial_10_ichangeproxytarget

The sig­na­ture sais it accepts System.Object instances, but in real­ity the new tar­get must be an imple­menter of tar­get inter­face of the proxy (hence proxy with tar­get inter­face). The fact that it’s exposed by invo­ca­tion object and the way it’s inte­grated into the pipeline has few inter­est­ing implications.

First, you can change the tar­get for that invo­ca­tion, not for the whole proxy. This is impor­tant, not so obvi­ous fact. Even if you change the tar­get, it will be only for that one invo­ca­tion of this one method. Next time the method (or any other method) gets invoked it will be the orig­i­nal tar­get that you’ll receive as the tar­get of invo­ca­tion, not the one you set last time.

Sec­ond, this design makes the switch oper­a­tion com­pletely trans­par­ent to every­body else. Proxy object has no idea any­thing changed, because it does not affect it in any way. invo­ca­tion object does not care about it. Any inter­cep­tor that is after the cur­rent one in the invo­ca­tion pipeline will have no idea (unless, of course you notify it in some other way) that the object it receives as tar­get of invo­ca­tion is not the one set originally.

If you do want to lever­age this fea­ture, con­sider putting the inter­cep­tor that may change the tar­get in front of the pipeline (unless in your sce­nario other approach makes more sense). This will help you avoid cases where you waste cycles doing some work on a tar­get that gets dis­carded, and then other inter­cep­tors get tar­get object that is not fully com­pli­ant with rules you set. For exam­ple if you have three inter­cep­tors in the pipeline: first one per­forms low level val­i­da­tion (not null etc), sec­ond one swaps tar­get and the third one does log­ging, the third one may work with assump­tion, that tar­get object has been val­i­dated and assume it is valid, which may not be the case if tar­get object was swapped by the sec­ond inter­cep­tor, in which case the first inter­cep­tor will not get the chance to val­i­date it.

Tech­no­rati Tags: ,
  • http://diegworld.blogspot.com/ Diego

    Hi,

    10x for the article..good stuff.
    I think I might be in the 1% group..
    I'll do some test­ing tomor­row & get back to you on this..

    Diego

  • Sud

    Hi,
    This was a great arti­cle. I have the fol­low­ing prob­lem:
    1. A web ser­vice reg­is­ters with a par­ent ser­vice with a WCF Call­back­Con­tract which is of same type as the orig­i­nal Ser­vice­Con­tract (the par­ent also imple­ments the same Ser­vice­Con­tract)
    2. The par­ent stores the call­back ref­er­ence some­where in a dic­tio­nary
    3. When an exter­nal client invokes a par­ent oper­a­tion on the Ser­vice­Con­tract, the par­ent should dynam­i­cally del­e­gate the call to the reg­is­tered child through the Call­back (based on some busi­ness logic)

    Now, I want to move step 3 to an "inter­cep­tor" mech­a­nism and DP seems like a right choice (since I would have mul­ti­ple such ser­vices going through the reg­is­tra­tion and del­e­ga­tion process)

    Effec­tively, I want to setup an inter­cep­tor on the WCF server side to inter­cept the calls and then del­e­gate to chil­dren via the CallbackContract.

    I looked at WCF exten­sions, but it doesn't seem the right choice since I want to dynam­i­cally change the tar­get for the same interface.

    Any help is greatly appreciated.

    Thanks.

  • http://kozmic.pl/Default.aspx Krzysztof Koźmic

    @Sud

    so what is it exactly that you're hav­ing prob­lems with?