如何实现无代码冗余的Web服务故障转移机制?
解决重复服务调用与解析代码的方案
看起来你现在被重复的服务调用和响应解析代码困扰了——明明逻辑完全一致,却因为服务客户端、响应类型不同不得不写两套几乎一样的代码。我们可以通过抽象通用逻辑、泛型封装和接口解耦来彻底解决这个问题,让代码更简洁、可维护。
第一步:统一响应解析逻辑
先看你的解析函数,核心逻辑都是处理ResponsePayload字符串,差异只是参数类型。我们可以把核心解析逻辑提取成独立方法,完全脱离具体的响应类型:
// 通用的payload解析逻辑,只关注业务处理 private void ParseResponsePayload(string payload) { if (payload.Contains("NODATA")) { ResultMessage.Text = "No data was returned by the service request."; } else { var _response = ParseResponseRecord(payload); if (_response.ResultCode == MSVResultCode.Invalid) { ResultMessage.Text = _response.Result; ResultMessage.CssClass = "text-danger"; } else if (_response.ResultCode == MSVResultCode.Valid) { var _payload = payload.Split(new[] { Environment.NewLine }, StringSplitOptions.None); var patients = ParsePatientRecords(_payload); PatientsRepeater.DataSource = patients; PatientsRepeater.DataBind(); ResultPanel.Visible = true; } } }
接下来,原来的两个解析方法就可以简化成“适配器”,只负责提取payload并调用通用方法:
private void ParseServiceResponse(Service.OperationResponse response) { ParseResponsePayload(response.ResponsePayload); } private void ParseFailoverServiceResponse(ServiceFailover.OperationResponse response) { ParseResponsePayload(response.ResponsePayload); }
如果你的服务响应类是自动生成的(比如WCF客户端),无法直接修改实现接口,也可以用扩展方法统一获取payload的方式,让代码更优雅:
public static class ServiceResponseExtensions { public static string GetPayload(this Service.OperationResponse response) => response.ResponsePayload; public static string GetPayload(this ServiceFailover.OperationResponse response) => response.ResponsePayload; } // 此时解析方法可以写成: private void ParseServiceResponse(Service.OperationResponse response) { ParseResponsePayload(response.GetPayload()); }
第二步:抽象服务调用逻辑
接下来处理重复的try/catch和客户端创建逻辑。我们可以用泛型+委托封装一个通用的故障转移调用方法,适配不同的客户端类型:
/// <summary> /// 通用的故障转移服务调用方法 /// </summary> /// <typeparam name="TClient">服务客户端类型(必须实现IDisposable)</typeparam> /// <typeparam name="TResponse">服务响应类型</typeparam> /// <param name="createPrimary">创建主服务客户端的委托</param> /// <param name="callPrimary">调用主服务操作的委托</param> /// <param name="createFailover">创建故障转移服务客户端的委托</param> /// <param name="callFailover">调用故障转移服务操作的委托</param> /// <returns>服务响应结果</returns> private async Task<TResponse> CallWithFailover<TClient, TResponse>( Func<TClient> createPrimary, Func<TClient, Task<TResponse>> callPrimary, Func<TClient> createFailover, Func<TClient, Task<TResponse>> callFailover) where TClient : IDisposable { try { using (var client = createPrimary()) { return await callPrimary(client); } } catch (System.ServiceModel.EndpointNotFoundException) { // 主端点不可用时,自动切换到故障转移端点 using (var client = createFailover()) { return await callFailover(client); } } }
现在你的SendTestRequestToWebService方法可以简化成:
private async Task SendTestRequestToWebService(string foo, string bar) { var response = await CallWithFailover( // 创建主服务客户端 () => new Service.ServiceGatewayClient(), // 调用主服务的Operation方法(同步方法可改用Task.FromResult包装) client => client.OperationAsync(foo, bar), // 创建故障转移服务客户端 () => new ServiceFailover.ServiceGatewayClient(), // 调用故障转移服务的Operation方法 client => client.OperationAsync(foo, bar) ); // 根据响应类型调用对应的解析方法 if (response is Service.OperationResponse serviceResp) ParseServiceResponse(serviceResp); else if (response is ServiceFailover.OperationResponse failoverResp) ParseFailoverServiceResponse(failoverResp); }
进阶方案:用接口实现完全解耦
如果想要更彻底的解耦(符合SOLID原则),可以定义一个服务接口,把主服务和故障转移服务都封装成接口实现,再创建一个故障转移代理类:
1. 定义服务接口和响应接口
// 统一的服务响应接口 public interface IServiceResponse { string ResponsePayload { get; } } // 统一的服务操作接口 public interface IServiceGateway { Task<IServiceResponse> OperationAsync(string foo, string bar); }
2. 实现主服务和故障转移服务的适配器
// 主服务适配器 public class PrimaryServiceAdapter : IServiceGateway { public async Task<IServiceResponse> OperationAsync(string foo, string bar) { using (var client = new Service.ServiceGatewayClient()) { var innerResp = await client.OperationAsync(foo, bar); // 把自动生成的响应包装成IServiceResponse实现 return new PrimaryResponseWrapper(innerResp); } } // 内部包装类,实现IServiceResponse private class PrimaryResponseWrapper : IServiceResponse { private readonly Service.OperationResponse _inner; public PrimaryResponseWrapper(Service.OperationResponse inner) => _inner = inner; public string ResponsePayload => _inner.ResponsePayload; } } // 故障转移服务适配器 public class FailoverServiceAdapter : IServiceGateway { public async Task<IServiceResponse> OperationAsync(string foo, string bar) { using (var client = new ServiceFailover.ServiceGatewayClient()) { var innerResp = await client.OperationAsync(foo, bar); return new FailoverResponseWrapper(innerResp); } } private class FailoverResponseWrapper : IServiceResponse { private readonly ServiceFailover.OperationResponse _inner; public FailoverResponseWrapper(ServiceFailover.OperationResponse inner) => _inner = inner; public string ResponsePayload => _inner.ResponsePayload; } }
3. 创建故障转移代理类
public class FailoverServiceProxy : IServiceGateway { private readonly IServiceGateway _primary; private readonly IServiceGateway _failover; public FailoverServiceProxy(IServiceGateway primary, IServiceGateway failover) { _primary = primary; _failover = failover; } public async Task<IServiceResponse> OperationAsync(string foo, string bar) { try { return await _primary.OperationAsync(foo, bar); } catch (System.ServiceModel.EndpointNotFoundException) { return await _failover.OperationAsync(foo, bar); } } }
4. 最终调用代码
现在你的调用代码会非常简洁,而且完全和具体服务实现解耦:
// 在构造函数中初始化服务代理 private readonly IServiceGateway _serviceGateway; public YourForm() { InitializeComponent(); _serviceGateway = new FailoverServiceProxy( new PrimaryServiceAdapter(), new FailoverServiceAdapter() ); } private async void GetPatient_Click(object sender, EventArgs e) { if (_testing) { await SendTestRequestToWebService(foo, bar); } } private async Task SendTestRequestToWebService(string foo, string bar) { var response = await _serviceGateway.OperationAsync(foo, bar); // 直接调用通用解析方法,不用再区分响应类型 ParseResponsePayload(response.ResponsePayload); }
这种方案的好处是:后续如果新增其他故障转移端点,只需要新增一个IServiceGateway实现即可,不需要修改原有调用逻辑,扩展性极强。
内容的提问来源于stack exchange,提问作者Ortund
相关产品推荐
相关产品推荐

