我正在使用一段代码,并且可以使用一些指针。
我正在为前端网站创建API。此API可以基于租户调用各种后端系统。要调用的系统保存在数据库的租户条目中。
我的API项目包含几个不同的项目:
我的API调用我的逻辑层,例如检索客户对象。在此调用中,传递了tenantId(取自授权身份声明)。我的逻辑做了一些检查 然后想要调用租户特定的后端系统。为此,我需要执行以下操作:
数据类实现了一个接口IBackendConnector。
因为我的所有逻辑类都可能调用特定于租户的数据,所以我想创建一个接受tenantId的工厂,并返回一个实现IBackendConnector的类。 调用数据库以检索租户是异步的,API调用本身是异步的。
我创建了一个名为' DataFactory'的抽象类。具有(公共和私有)属性IBackendConnector,让它命名为Connector。它还有一个构造函数,它需要一个tenantid,它保存为 私人财产。每个逻辑类都实现此DataFactory。我需要调用后端的那一刻,我可以简单地调用一些漂亮而干净的代码:
Connector.MethodIWhishToCall();
在公共属性的getter中,我检查私有属性中是否有值,如果没有,则需要检索租户设置,并创建正确的数据类,因此我们只创建 我们需要的时候上课。这是我失败的地方,因为我无法在此属性getter中调用异步方法(没有死锁)。
我更改了这段代码,并且对我使用的当前解决方案不满意,这是一个在每个逻辑类(需要它)中实现的抽象类,带有静态其中的异步方法。
public abstract class ExternalDataFactory
{
public static async Task<IBackendDataConnector> GetDataHandler(string tenantId)
{
var logic = new TenantLogic();
var tenant = await logic.GetTenantById(tenantId);
var settings = tenant.Settings;
if (settings == null)
throw new ArgumentNullException("Settings");
switch (settings.EnvironmentType)
{
case Constants.EnvironmentType.ExampleA:
return new DataHandlerClass(settings);
case Constants.EnvironmentType.ExampleB:
throw new NotImplementedException();
default:
throw new Exception("Invalid EnvironmentType");
};
}
}
使用此处理程序的每个方法都调用以下代码:
var handler = await GetDataHandler(tenantId);
return await handler.DoSomething();
或者在一行中:
return await (await GetDataHandler(tenantId)).DoSomething();
最终目标:所有逻辑类必须能够调用正确的数据类,而不必担心哪个数据类,或者必须先手动检索设置并将其传递给工厂。工厂 应该能够检索设置(如果设置为null)并返回正确的IBackendConnector实现。什么是最佳实践/最佳模式?
TL / DR:尝试实现需要调用异步方法的工厂模式,以决定返回哪个具体类。最好的方法是什么?
答案 0 :(得分:1)
我没有看到代码中有任何理由来定义抽象类,只有它定义的方法是静态的。 所以我建议的第一件事就是让你的课堂不抽象。
让我们将GetDataHandler
重命名为CreateDataHandlerAsync
,以清楚地向您的工厂的消费者表明此方法为async
。您还可以看到我从方法签名中删除了static
。
对我而言,它做了太多事情,让TenantLogic
成为这个工厂的依赖。
public interface ITenantLogic
{
Task<Tenant> GetTenantByIdAsync(int tenantId);
}
public class TenantLogic: ITenantLogic
{
public async Task<Tenant> GetTenantByIdAsync(int tenantId)
{
// logic to get the tenant goes here
}
}
现在让我们在工厂中将其定义为依赖
public class ExternalDataFactory
{
private readonly ITenantLogic _tenantLogic;
public ExternalDataFactory(ITenantLogic tenantLogic)
{
if(tenantLogic == null) throw new ArgumentNullException("tenantLogic");
_tenantLogic = tenantLogic;
}
public async Task<IBackendDataConnector> CreateDataHandlerAsync(string tenantId)
{
var tenant = await _tenantLogic.GetTenantByIdAsync(tenantId);
var settings = tenant.Settings;
if (settings == null)
throw new ArgumentException("Specified tenant has no settings defined");
switch (settings.EnvironmentType)
{
case Constants.EnvironmentType.ExampleA:
return new DataHandlerClass(settings);
case Constants.EnvironmentType.ExampleB:
throw new NotImplementedException();
default:
throw new Exception("Invalid EnvironmentType");
};
}
}
我建议您更改DoSomething
的方法名称,因为它正在运行async
使其成为DoSomethingAsync()
。根据经验,我建议将Async
postfix到你的所有异步方法。
现在是客户端代码。
var factory = new ExternalDataFactory(new TenantLogic());
IBackendDataConnector dataConnector =
await factory.CreateDataHandlerAsync(tenantId);
return await dataConnector.DoSomethingAsync();
最后但同样重要的是,我还会考虑如何在switch/case
方法中摆脱CreateDataHandlerAsync
。
我认为这个版本可以是一个很好的起点 - 它将达到你的最终目标 - 在支持新环境后,你将实现具体的数据类,添加一个新的case语句(严肃的说,我会删除它)并且您的所有客户都可以享受工厂支持新环境的好处。
希望这有帮助。
进一步研究这个我想再补充一点。
我真的不认为ExternalDataFactory
应该了解房客,如何找房客等。
我认为工厂的消费者代码应该处理租户的检索并将环境传递到factory
,这反过来会创建一个具体的数据类并将其返回。
正如我之前所说,我们可以使工厂更加灵活。优雅(IMO),如果我们可以摆脱switch/case
。
让我们首先重新定义与数据连接器相关的类/接口
public interface IBackendDataConnector
{
public Task<Something> DoSomethingAsync(Settings settings);
public bool CanHandleEnvironment(EnvironmentType environment);
}
public abstract class DataHandlerAbstractBase: IBackendDataConnector
{
protected abstract EnvironmentType Environment { get; }
// interface API
public abstract async Task<Something> DoSomethingAsync(Settings settings);
public virtual bool CanHandleEnvironment(EnvironmentType environment)
{
return environment == Environment;
}
}
public class DataHandlerClass: DataHandlerAbstractBase
{
protected override EnvironmentType Environment
{
get { return EnvironmentType.ExampleA; }
}
public override async Task<Something> DoSomethingAsync(Settings settings)
{
// implementation goes here
}
}
现在有了上述内容,让我们重新审视ExternalDataFactory
public class ExternalDataFactory
{
private readonly IEnumerable<IBackendDataConnector> _dataConnectors =
new [] {new DataHandlerClass() /* other implementations */}
public IBackendDataConnector CreateDataHandler(Settings setting)
{
IBackendDataConnector connector = _dataConnectors
.FirstOrDefault(
c => c.CanHandleEnvironment(setting.EnvironmentType));
if (connector == null)
{
throw new ArgumentException("Unsupported environment type");
}
return connector;
}
}
很少有关于重构的话。
如前所述,工厂方法无法通过它的id等知道/打扰租户等所有它能做的就是根据传递的环境创建具体的数据类(它从传入的环境中读取) )。
现在每个具体的数据类都可以说它是否可以处理给定的环境,所以我们只需从工厂委托调用它。每个具体的数据类实现都必须定义Environment属性(由于继承自抽象基类DataHandlerAbstractBase
。
通过这些更改,现在客户将需要照顾租户并将其传递给工厂
var tenantLogic = new TenantLogic();
var tenant = await tenantLogic.GetTenantByIdAsync(tenantId);
var factory = new ExternalDataFactory();
IBackendDataConnector dataConnector =
factory.CreateDataHandler(tenant.Settings);
return await dataConnector.DoSomethingAsync(tenant.Settings);