在多租户环境中解决正确接口实现的策略
本文关键字:接口 实现 策略 解决 环境 | 更新日期: 2023-09-27 18:17:37
给定此接口:
public interface ILoanCalculator
{
decimal Amount { get; set; }
decimal TermYears { get; set; }
int TermMonths { get; set; }
decimal IntrestRatePerYear { get; set; }
DateTime StartDate { get; set; }
decimal MonthlyPayments { get; set; }
void Calculate();
}
和它的 2 个暗示:
namespace MyCompany.Services.Business.Foo
{
public interface ILoanCalculator : Common.ILoanCalculator
{
}
public class LoanCalculator : ILoanCalculator
{
public decimal Amount { get; set; }
public decimal TermYears { get; set; }
public int TermMonths { get; set; }
public decimal IntrestRatePerYear { get; set; }
public DateTime StartDate { get; set; }
public decimal MonthlyPayments { get; set; }
public void Calculate()
{
throw new NotImplementedException();
}
}
}
namespace MyCompany.Services.Business.Bar
{
public interface ILoanCalculator : Common.ILoanCalculator
{
}
public class LoanCalculator : ILoanCalculator
{
public decimal Amount { get; set; }
public decimal TermYears { get; set; }
public int TermMonths { get; set; }
public decimal IntrestRatePerYear { get; set; }
public DateTime StartDate { get; set; }
public decimal MonthlyPayments { get; set; }
public void Calculate()
{
throw new NotImplementedException();
}
}
}
给定上面的简单代码,假设计算方法的实现因公司而异。在初始化期间加载程序集并调用正确程序集的正确方法的正确方法是什么?我已经弄清楚了最简单的部分是确定请求适用于哪家公司,现在我只需要调用与当前业务相对应的正确方法。
谢谢斯蒂芬
更新的示例代码
向@Scott大喊大叫,以下是我必须进行的更改,以使接受的答案正常工作。
在这种情况下,我必须使用程序集解析程序来查找我的类型。请注意,我使用了一个属性来标记我的程序集,以便基于它的筛选更简单且不易出错。
public T GetInstance<T>(string typeName, object value) where T : class
{
// Get the customer name from the request items
var customer = Request.GetItem("customer") as string;
if (customer == null) throw new Exception("Customer has not been set");
// Create the typeof the object from the customer name and the type format
var assemblyQualifiedName = string.Format(typeName, customer);
var type = Type.GetType(
assemblyQualifiedName,
(name) =>
{
return AppDomain.CurrentDomain.GetAssemblies()
.Where(a => a.GetCustomAttributes(typeof(TypeMarkerAttribute), false).Any()).FirstOrDefault();
},
null,
true);
if (type == null) throw new Exception("Customer type not loaded");
// Create an instance of the type
var instance = Activator.CreateInstance(type) as T;
// Check the instance is valid
if (instance == default(T)) throw new Exception("Unable to create instance");
// Populate it with the values from the request
instance.PopulateWith(value);
// Return the instance
return instance;
}
标记属性
[AttributeUsage(AttributeTargets.Assembly)]
public class TypeMarkerAttribute : Attribute { }
插件组装中的用法
[assembly: TypeMarker]
最后,对静态 MyType 稍作更改以支持限定名称
public static class MyTypes
{
// assemblyQualifiedName
public static string LoanCalculator = "SomeName.BusinessLogic.{0}.LoanCalculator, SomeName.BusinessLogic.{0}, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null";
}
我认为没有一个简单或特别优雅的解决方案,因为ServiceStack基于具体的类而不是通过接口解析它的服务,这超出了Funq的能力。然而,这并非不可能。
您需要具有要用作 DTO 的每个接口的默认实现,因为 ServiceStack 使用具体类进行解析。
因此,基本上在这里,我们有一个DefaultCalculator
它将为我们提供进入我们的行动方法的途径。
[Route("/Calculate","GET")]
public class DefaultCalculator : ILoanCalculator
{
public decimal Amount { get; set; }
public decimal TermYears { get; set; }
public int TermMonths { get; set; }
public decimal IntrestRatePerYear { get; set; }
public DateTime StartDate { get; set; }
public decimal MonthlyPayments { get; set; }
public void Calculate()
{
throw new NotImplementedException();
}
}
然后我们的操作方法几乎像往常一样使用,除了我们调用一个方法GetInstance<T>
我们在MyServiceBase
中实现该方法,该服务从中扩展,而不是Service
,因为它可以更轻松地跨服务共享此方法。
public class TestService : MyServiceBase
{
public decimal Get(DefaultCalculator request)
{
// Get the instance of the calculator for the current customer
var calculator = GetInstance<ILoanCalculator>(MyTypes.LoanCalculator, request);
// Perform the action
calculator.Calculate();
// Return the result
return calculator.MonthlyPayments;
}
}
MyServiceBase
我们实现了方法GetInstance<T>
,该方法负责根据客户名称解析正确的实例,T
,在这种情况下是 ILoanCalculator
.
该方法的工作原理是:
从
Request.GetItem("customer")
确定客户名称。当前方法需要在标识客户的位置使用Request.SetItem
方法在Request
Items
集合上设置客户标识符。或者也许将识别机制转移到这种方法中。在已知客户名称的情况下,可以根据传入的类型名称模板构建完整的类型名称。
MyCompany.Services.Business.Foo.LoanCalculator
Foo
是客户的地方。如果在启动时已加载包含程序集,则这应该解析类型。然后创建该类型的实例作为
T
即接口,ILoanCalculator
然后进行安全检查,以确保一切正常。
然后填充请求中的值,这些值也属于
DefaultCalculator
类型为ILoanCalculator
。返回实例。
public class MyServiceBase : Service
{
public T GetInstance<T>(string typeName, object value)
{
// Get the customer name from the request items
var customer = Request.GetItem("customer") as string;
if(customer == null) throw new Exception("Customer has not been set");
// Create the typeof the object from the customer name and the type format
var type = Type.GetType(string.Format(typeName, customer));
// Create an instance of the type
var instance = Activator.CreateInstance(type) as T;
// Check the instance is valid
if(instance == default(T)) throw new Exception("Unable to create instance");
// Populate it with the values from the request
instance.PopulateWith(value);
// Return the instance
return instance;
}
}
您可以选择添加实例缓存,以避免必须为每个请求使用 Activator.CreateInstance
。
如果要动态创建许多不同的类型,则可能希望将它们的类型字符串组织到静态类中:
public static class MyTypes
{
public static string LoanCalculator = "MyCompany.Services.Business.{0}.LoanCalculator";
public static string AnotherType = "MyCompany.Services.Business.{0}.AnotherType";
//...//
}
然后,剩下要做的就是确保将具有不同客户实现的程序集加载到应用程序中,这可以从AppHost
Configure
方法中执行。
foreach(var pluginFileName in Directory.GetFiles("Plugins", "*.dll"))
Assembly.Load(File.ReadAllBytes(pluginFileName));
显然,此方法依赖于特定格式的完整类型名称来与客户匹配。还有其他方法,但我相信这很简单。
我希望这有所帮助。