在它可以 return 具体 class 之前调用异步方法的工厂
Factory that calls an async method before it can return concrete class
我正在为一段代码苦苦挣扎,可以使用一些指针。
我正在为前端网站创建 API。这个API可以根据租户调用各种后台系统。调用哪个系统保存在数据库中的租户条目中。
我的 API 项目由几个不同的项目组成:
- API
- 业务逻辑
- 数据
- Dto
我的 API 调用我的逻辑层,例如检索客户对象。在此调用中传递了一个 tenantId(取自授权身份声明)。我的逻辑做了一些检查
然后想调用租户特定的后端系统。为此,我需要执行以下操作:
- 从数据库中检索租户设置
- 找出配置的后端,并创建该数据的实例class
- 调用此数据 class,并检索客户
数据class实现了接口IBackendConnector。
因为我的所有逻辑 classes 都可能调用租户特定数据,所以我想创建一个接受 tenantId 的工厂,并且 returns 一个 class 实现 IBackendConnector .
检索租户的数据库调用是异步的,API 调用本身是异步的。
我创建了一个名为 'DataFactory' 的抽象 class,它具有(public 和私有)属性 IBackendConnector,我们将其命名为 Connector。它还有一个需要 tenantid 的构造函数,它保存为
私人 属性。每个逻辑 class 都实现了这个 DataFactory。当我需要调用后端时,我可以简单地调用一些漂亮干净的代码:
Connector.MethodIWhishToCall();
在public属性的getter里我检查private里有没有值属性,如果没有,就需要检索tenant设置,并创建正确的数据 class,所以我们只创建
class 在我们需要的时候。这是我失败的地方,因为我无法在此 属性 getter 中调用异步方法(没有死锁)。
我对这段代码做了很多改动,我对我当前使用的解决方案不满意,这是一个抽象 class,在每个逻辑 class 中实现(这需要它),其中有一个静态异步方法。
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();
最终目标:所有逻辑 classes 必须能够调用正确的数据 class 而不必担心是哪个数据 class,也不必先手动检索设置并将其传递给工厂。工厂
应该能够检索设置(如果设置为空)和 return 正确的 IBackendConnector 实现。执行此操作的最佳实践/最佳模式是什么?
TL/DR:试图实现一个工厂模式,需要调用一个异步方法来决定具体的 class 到 return。解决此问题的最佳方法是什么?
我没有在您的代码中看到定义抽象的任何充分理由 class,因为它定义的唯一方法是静态的。
所以我建议的第一件事是让你的 class 非抽象。
让我们将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
。
现在是客户端代码。
var factory = new ExternalDataFactory(new TenantLogic());
IBackendDataConnector dataConnector =
await factory.CreateDataHandlerAsync(tenantId);
return await dataConnector.DoSomethingAsync();
最后但同样重要的是,我也会考虑如何在 CreateDataHandlerAsync
方法中摆脱 switch/case
。
我认为这个版本可以是一个很好的起点 - 它会实现你的最终目标 - 在支持新环境时,你将实现具体数据 class,添加一个新的 case 语句(说真的,我虽然会删除它)并且您的所有客户都可以享受工厂支持新环境的好处。
希望这对您有所帮助。
编辑
进一步了解这一点,我想再补充一件事。
我真的不认为 ExternalDataFactory
应该了解租户,如何找到租户等
我认为工厂的消费者代码应该处理租户的检索并将环境传递给 factory
,这反过来会创建一个具体的数据 class 和 return。
正如我之前所说,如果我们能够摆脱 switch/case
。
,我们可以使工厂更加灵活和优雅 (IMO)
我们先重新定义相关的数据连接器classes/interfaces
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;
}
}
关于重构的几句话。
如前所述,现在工厂方法不会 know/bother 通过其 ID 等获取租户。它所能做的就是根据传递的环境(它从传递的设置中读取)创建具体数据 class进去)。
现在每个具体数据 class 都可以说明它是否可以处理给定的环境,所以我们只需将调用从工厂委托给它。每个具体数据 class 实现都必须定义环境 属性(由于继承自抽象基础 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);
我正在为一段代码苦苦挣扎,可以使用一些指针。
我正在为前端网站创建 API。这个API可以根据租户调用各种后台系统。调用哪个系统保存在数据库中的租户条目中。
我的 API 项目由几个不同的项目组成:
- API
- 业务逻辑
- 数据
- Dto
我的 API 调用我的逻辑层,例如检索客户对象。在此调用中传递了一个 tenantId(取自授权身份声明)。我的逻辑做了一些检查 然后想调用租户特定的后端系统。为此,我需要执行以下操作:
- 从数据库中检索租户设置
- 找出配置的后端,并创建该数据的实例class
- 调用此数据 class,并检索客户
数据class实现了接口IBackendConnector。
因为我的所有逻辑 classes 都可能调用租户特定数据,所以我想创建一个接受 tenantId 的工厂,并且 returns 一个 class 实现 IBackendConnector . 检索租户的数据库调用是异步的,API 调用本身是异步的。
我创建了一个名为 'DataFactory' 的抽象 class,它具有(public 和私有)属性 IBackendConnector,我们将其命名为 Connector。它还有一个需要 tenantid 的构造函数,它保存为 私人 属性。每个逻辑 class 都实现了这个 DataFactory。当我需要调用后端时,我可以简单地调用一些漂亮干净的代码:
Connector.MethodIWhishToCall();
在public属性的getter里我检查private里有没有值属性,如果没有,就需要检索tenant设置,并创建正确的数据 class,所以我们只创建 class 在我们需要的时候。这是我失败的地方,因为我无法在此 属性 getter 中调用异步方法(没有死锁)。
我对这段代码做了很多改动,我对我当前使用的解决方案不满意,这是一个抽象 class,在每个逻辑 class 中实现(这需要它),其中有一个静态异步方法。
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();
最终目标:所有逻辑 classes 必须能够调用正确的数据 class 而不必担心是哪个数据 class,也不必先手动检索设置并将其传递给工厂。工厂 应该能够检索设置(如果设置为空)和 return 正确的 IBackendConnector 实现。执行此操作的最佳实践/最佳模式是什么?
TL/DR:试图实现一个工厂模式,需要调用一个异步方法来决定具体的 class 到 return。解决此问题的最佳方法是什么?
我没有在您的代码中看到定义抽象的任何充分理由 class,因为它定义的唯一方法是静态的。 所以我建议的第一件事是让你的 class 非抽象。
让我们将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
。
现在是客户端代码。
var factory = new ExternalDataFactory(new TenantLogic());
IBackendDataConnector dataConnector =
await factory.CreateDataHandlerAsync(tenantId);
return await dataConnector.DoSomethingAsync();
最后但同样重要的是,我也会考虑如何在 CreateDataHandlerAsync
方法中摆脱 switch/case
。
我认为这个版本可以是一个很好的起点 - 它会实现你的最终目标 - 在支持新环境时,你将实现具体数据 class,添加一个新的 case 语句(说真的,我虽然会删除它)并且您的所有客户都可以享受工厂支持新环境的好处。
希望这对您有所帮助。
编辑
进一步了解这一点,我想再补充一件事。
我真的不认为 ExternalDataFactory
应该了解租户,如何找到租户等
我认为工厂的消费者代码应该处理租户的检索并将环境传递给 factory
,这反过来会创建一个具体的数据 class 和 return。
正如我之前所说,如果我们能够摆脱 switch/case
。
我们先重新定义相关的数据连接器classes/interfaces
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;
}
}
关于重构的几句话。
如前所述,现在工厂方法不会 know/bother 通过其 ID 等获取租户。它所能做的就是根据传递的环境(它从传递的设置中读取)创建具体数据 class进去)。
现在每个具体数据 class 都可以说明它是否可以处理给定的环境,所以我们只需将调用从工厂委托给它。每个具体数据 class 实现都必须定义环境 属性(由于继承自抽象基础 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);