View Full Version : Dynamic Routing problem
MichelG
09-07-2006, 12:02 PM
Hey all,
I'm completely new to spring.net, but so far I'm loving it. I'm having the following problem:
I have a webservice that acts as a router. Each webmethod could get it's data from different datasources (database, another webservice, xmlfile, ... whatever). Therefore, a common interface called IStrategy is implmented by the different strategies (DatabaseStrategy, WebServiceStrategy, XmlStrategy, ...).
This is method dependent, that means, if I call Webservice.Foo, the webmethod might call DatabseStragtegy.Foo. If I call Webservice.Bar, the webmethod might call XmlStrategy.Bar...
Is this something I can do with the spring framework. What I was trying was something like this:
<object id="dbStrategy" type="DatabaseStrategy />
<object id="xmlStrategy" type="XmlStrategy />
<object id="wsStrategy" type="WebServiceStrategy />
<object id="dbPointcut" type="Spring.Aop.Support.SdkRegularExpressionMethodPoint cut, Spring.Aop">
<property name="patterns">
<list>
<value>Foo</value>
</list>
</property>
</object>
<object id="xmlPointcut" type="Spring.Aop.Support.SdkRegularExpressionMethodPoint cut, Spring.Aop">
<property name="patterns">
<list>
<value>Bar</value>
</list>
</property>
</object>
But then, I don't know how to wire these things together. Can anyone gently point me in the right direction?
Bruno Baia
09-07-2006, 04:17 PM
Hi Michel,
I've added a sample code to the Forum question (http://opensource.atlassian.com/confluence/spring/display/NET/Forum+Questions) page :
SpringSample.zip (http://opensource.atlassian.com/confluence/spring/download/attachments/708/SpringSample.zip?version=1)
-Bruno
MichelG
09-07-2006, 05:00 PM
Whoaaa man. A complete solution just for me? I'm very impressed. You are the best. That is exactly what I'm looking for ... almost :cool:
There's one more extra challenge. What about choosing my strategy based on the value of a parameter passed in the request? I know that this will take a performance hit since it will have to evaluate at each call what to execute. In my particular case that is not so bad, compared to the performance hit of calling a webservice or even a file.
To be really explicit: DoSomething(1) calls XmlStragtegy and DoSomething(2) calls DbStrategy. Can it be done?
Bruno Baia
09-08-2006, 12:33 PM
Hi,
Whoaaa man. A complete solution just for me? I'm very impressed. You are the best. That is exactly what I'm looking for ... almost :cool:
I already got this problem, it's a kind of "dynamic" injection.
There's one more extra challenge. What about choosing my strategy based on the value of a parameter passed in the request? I know that this will take a performance hit since it will have to evaluate at each call what to execute. In my particular case that is not so bad, compared to the performance hit of calling a webservice or even a file.
To be really explicit: DoSomething(1) calls XmlStragtegy and DoSomething(2) calls DbStrategy. Can it be done?
Sure, you can do a lot of stuff...
I see 2 solutions :
- Using dynamic pointcuts
"Dynamic pointcuts are costlier to evaluate than static pointcuts. They take into account method arguments, as well as static information. This means that they must be evaluated with every method invocation; the result cannot be cached, as arguments will vary."
In the example you got, I used a static pointcut : NameMatchMethodPointcut (which inherit from StaticMethodMatcherPointcut).
But you can use a dynamic pointcut that take into account method arguments :
Here is an example using the new Spring.Expressions framework :
(You can also make a DynamicPoincutAdvisor for each strategy with your own code, etc...)
public class StrategyDynamicPoincutAdvisor : DynamicMethodMatcherPointcutAdvisor
{
private string _pattern;
public string Pattern
{
get { return _pattern; }
set { _pattern = value; }
}
/// <summary>
/// Is there a runtime (dynamic) match for the supplied method ?
/// </summary>
public override bool Matches(MethodInfo method, Type targetType, object[] args)
{
bool matches = false;
IExpression expr = Expression.Parse(Pattern);
try
{
// Evaluate the expression using the arguments as the current context
matches = (bool)expr.GetValue(args);
}
catch (Exception e)
{
throw new ArgumentException("Pattern expression cannot be evaluate or not return a
boolean.", "Pattern", e);
}
return matches;
}
}
<object id="DatabaseStrategyAdvisor" type="SpringSample.Advice.StrategyDynamicPoincutAdvisor,
SpringSample">
<property name="Advice" ref="DatabaseStrategyAdvice" />
<property name="Pattern" value="[0] == 2" />
</object>
<object id="WebServiceStrategyAdvisor" type="SpringSample.Advice.StrategyDynamicPoincutAdvisor,
SpringSample">
<property name="Advice" ref="WebServiceStrategyAdvice" />
<property name="Pattern" value="[0] > 2 and 10 > [0]" />
</object>
<object id="XmlStrategyAdvisor" type="SpringSample.Advice.StrategyDynamicPoincutAdvisor,
SpringSample">
<property name="Advice" ref="XmlStrategyAdvice" />
<property name="Pattern" value="2 > [0] and [0] >= 0" />
</object>
Here '[0]' is the indexer that allow you to access 'args' elements.
Console.Out.WriteLine(service.GetSomething(1));
Console.Out.WriteLine(service.GetSomething(2));
Console.Out.WriteLine(service.GetSomething(3));
- Matching arguments directly in the advice :
public class AnotherStrategyAdvice : IMethodInterceptor
{
/// <summary>
/// Intercept IStrategyAware object.
/// </summary>
public object Invoke(IMethodInvocation invocation)
{
if (invocation.This is IStrategyAware)
{
int i = (int) invocation.Arguments[0];
IStrategy strategy = null;
switch (i)
{
case 1: strategy = new DatabaseStrategy(); break;
case 2: strategy = new WebServiceStrategy(); break;
case 3: strategy = new DatabaseStrategy(); break;
}
// Before to invoke method, inject strategy
((IStrategyAware)invocation.This).Strategy = strategy;
// Invoke method
object result = invocation.Proceed();
//After method invocation, set strategy to null
((IStrategyAware)invocation.This).Strategy = null;
return result;
}
else
{
throw new ArgumentException("StrategyAdvice should be applied on IStrategyAware
objects.");
}
}
}
As a note, you can also use attributes to select a method on which you want to apply an aspect using the AttributeMatchMethodPointcut.
It allows you to do something like that :
[Strategy(Type=StrategyType.WebService)] // or something like [DatabaseStrategy]
public string GetSomething(int i)
{
// ...
}
Hope this help,
Bruno
MichelG
09-13-2006, 03:59 PM
Did I say you were the best? I'm sticking to my previous statement. You ARE the man.
MichelG
09-20-2006, 02:03 PM
I have a question about your initial solution:
The StrategyAdvice looks like this:
public class StrategyAdvice : IMethodInterceptor
{
private IStrategy _strategy;
public IStrategy Strategy
{
get { return _strategy; }
set { _strategy = value; }
}
/// <summary>
/// Intercept IStrategyAware object.
/// </summary>
public object Invoke(IMethodInvocation invocation)
{
if (invocation.This is IStrategyAware)
{
// Before to invoke method, inject strategy
((IStrategyAware)invocation.This).Strategy = Strategy;
// Invoke method
object result = invocation.Proceed();
//After method invocation, set strategy to null
((IStrategyAware)invocation.This).Strategy = null;
return result;
}
else
{
throw new ArgumentException
("StrategyAdvice should be applied on IStrategyAware objects.");
}
}
}
The Strategy property is set in the configuration. The Strategy of IStrategyAware however, is set at runtime on each call. This is a problem for thread-safety. In a webservice, different requests are handled simultanuously. Since everything is a singleton if not specified, this actually overwrites the existing IStrategyAware.Strategy.
The first thing I tried was making the Service a prototype, so that each call would create a new instance of StrategyAdvice.
<object
id="MyService"
type="Spring.Aop.Framework.ProxyFactoryObject"
singleton="false"
>
<property name="target">
<object type="SpringSample.Service.SomeService, SpringSample" singleton="false" />
</property>
...
This however is not allowed, a ProxyFactoryObject must be a singleton. How can I achieve complete thread safety here??
Bruno Baia
09-20-2006, 03:09 PM
The first thing I tried was making the Service a prototype, so that each call would create a new instance of StrategyAdvice.
<object
id="MyService"
type="Spring.Aop.Framework.ProxyFactoryObject"
singleton="false"
>
<property name="target">
<object type="SpringSample.Service.SomeService, SpringSample" singleton="false" />
</property>
...
This however is not allowed, a ProxyFactoryObject must be a singleton. How can I achieve complete thread safety here??
The ProxyFactoryObject can be a prototype, for that you need to use a PrototypeTargetSource :
(When you use the Target property of the ProxyFactoryObject, it uses a SingletonTargetSource)
<object id="someService" type="SpringSample.Service.SomeService, SpringSample" singleton="false" />
<object id="MyService" type="Spring.Aop.Framework.ProxyFactoryObject">
<property name="targetSource">
<object type="Spring.Aop.Target.PrototypeTargetSource, Spring.Aop">
<property name="TargetObjectName" value="someService" />
</object>
</property>
...
-Bruno
vBulletin® v3.7.3, Copyright ©2000-2008, Jelsoft Enterprises Ltd.