在我看来,为对特定远程服务的所有访问编写一个大型类型的客户端是正确的方法。这正是 Microsoft 为类型化 http 客户端设想的使用模式。
同时我理解你的担忧,但情况并没有你想象的那么绝望。
首先你会得到一个巨大的接口,因此,一个巨大的实现类,但它们的职责是明确的:类型化客户端有责任定义一个代理来访问远程 Web 服务(学生服务在你的例子)。
类型化的客户端类实际上并不复杂:它可以是巨大的,当然,但它是无状态的,并且只是公开了访问远程 Web 服务端点的方法。每个方法都有明确且定义明确的职责:访问远程 Web 服务上的特定端点;这样的代码很少复杂。
唯一需要考虑的是从控制器或服务中使用接口IStudentClient。接口很大,所以如果你将它作为依赖注入到消费者类中,你将违反interface segregation principle。这个问题的一个可能的解决方案是对更小的接口进行建模,以满足消费者类的特定需求。
想象一下,您的远程 Web 服务公开的一个端点可以让您获取单个学生的详细信息(可能类似于 GET /students/{studentId})。这意味着IStudentClient 公开的方法之一将是GetStudentById(Guid studentId),它将GET 请求包装到/students/{studentId}。
此时你可以定义一个更小的接口,称为IStudentProvider,形状如下:
public interface IStudentProvider
{
StudentContract GetstudentById(Guid studentId);
}
现在您可以将较小的接口IStudentProvider 注入您的消费者类(例如,您在应用程序中定义的 MVC 控制器或服务类)。
要实现接口IStudentProvider,您可以执行以下操作:
public class HttpStudentProvider : IStudentProvider
{
private readonly IStudentClient client;
public HttpStudentProvider(IStudentClient client)
{
this.client = client;
}
public StudentContract GetstudentById(Guid studentId)
{
return this.client.GetStudentById(studentId);
}
}
重要免责声明:为了让讨论简单,我没有在接口上使用Task 类,但当然所有方法都应该返回Task<T> 并接受一个实例CancellationToken 作为参数,因为 http 调用是自然的异步操作,并且您确实不希望使用您的 http 客户端执行阻塞调用。
如何在 DI 容器上注册这些类
Microsoft DI 容器将为您提供一些扩展方法来注册类型化客户端。该服务将注册为临时依赖项,因此依赖于它的每个其他服务也必须注册为临时依赖项(以避免captive dependency issue)。
这是您应该注册服务的方式:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient<IStudentClient, StudentClient>();
services.AddTransient<IStudentProvider, HttpStudentProvider>();
}