业务思考-异步业务组织

Posted 浩天之家

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了业务思考-异步业务组织相关的知识,希望对你有一定的参考价值。

在最近的开发过程中,遇到这样一个问题,细想蛮有意思的,在此记录一下。

在进行具体讨论前,先将业务抽象,方便后续讨论。比方说,有一个业务Business,它依赖三个子命令ABC,每个命令都是异步收发且有先后顺序要求,暂且以A->B->C为依赖关系,每个指令都可能出错(响应超时或者响应错误),并且只要有一个出错,后续请求不必再发送,直接返回错误。

以上是业务背景,下面来看下如何组织上述逻辑关系。

代码组织方式

按照异步收发框架以及需求,这块逻辑,可以这样写:

void Business()
{      // 第一步,发送命令A
	SendCommandA();
}

// 底层异步响应回调函数
void OnReply(CReq* pReq)
{
	switch(pReq)
	{
		case CommandA:
			{     // 收到A响应后,正确则继续发送B命令,错误则回复错误。
				pReq->isRespRight() ? SendCommandB() : OnFinish(ECheckResult_Error, "指令A的错误信息");  
			}
			break;
		case CommandB:
			{
				pReq->isRespRight() ? SendCommandC()  : OnFinish(ECheckResult_Error, "指令B的错误信息");
			}
			break;
		case CommandC:
			{
			     pReq->isRespRight() ? OnFinish(ECheckResult_OK, "正确结果")  : OnFinish(ECheckResult_Error, "指令B的错误信息");		
			}
			break;
		default:
			assert(0);	// 未知情况
			break;
	}
}

上述写法,直观明了,前一个指令响应成功,再发下一个指令,否则提示错误。这种写法不好之处在于指令与指令之间耦合太紧。改进方案是利用消息来拆分调用关系。例如收到CommandA的正确响应,发出一个类似OnReceiveCmdA的异步消息。在消息中,带上CommandA指令响应的相关信息。业务自身订阅该消息并进行后续处理。这样一来,可降低命令之间的依赖,增加灵活性。

这里,我想提到的是另外一点,在工程中,看到其他同事有写这样的响应处理:

// 其他的省略
	case CommandA:
	{
		if (pReq->isRespRight())
		{
			OnFinish(EQryResult_CommandA_OK, pReq->GetRightRet());
		}
		else
		{
			OnFinish(EQryResult_CommandA_SysError, pReq->GetErrorMsg());
		}
	}
	break; 	
}

enum EQryResult
{
	EQryResult_CommandA_OK,
	EQryResult_CommandB_OK,
	// ...
	EQryResult_CommandA_SysError
}

void OnFinish(EQryResult eRet, string& strMsg)
{
	switch(eRet)
	{
		case EQryResult_CommandA_OK:
			{
				SendCommandB();
			}
			break;
		case EQryResult_CommandA_SysError:
			{
				// tip user 
			}
			breakl
	}
}

上面这种组织逻辑的思路,是针对每种情况,无论是正确的还是错误的,都定义对应的类型码,提供一个统一的OnFinish处理函数入口。通过入参,配合类型码来完成后续的操作。

这种组织逻辑的方法,笔者感觉不好,思考了下,有以下几点不好的地方:

  • 混淆职责。这种将正确处理和错误处理放在同一个函数中,混淆不同的职责。笔者认为,在这种异步处理,错误可以统一处理,但正确情况,各个指令有各自的应用场景,不适合统一处理。
  • 可扩展性差。后续随着业务的变动,可能会修改已有分支或者新增分支,不管如何,这样的修改,对于测试、运维或者产品来说,属于黑盒修改,即使在修改说明中明确了改动方法,这也只稍微缓解了测试人员的测试焦虑,增加后续的测试成本。
  • 可读性差。在异步消息处理时,上下文信息很重要,上面这种做法,在响应时又封装一层,看似方便了调用方,实则苦了实现方和阅读方。这种只简化了调用方的做法,隐藏后续紧密关联的业务处理,让阅读者要跳入到OnFinish函数,再根据具体入参,找到对应的处理分支,才能检查正确性,这种封装,降低代码可读性,不推荐。

小结

这种异步处理,建议错误情况统一处理,正确情况,通过消息来隔离各个具体的处理流程,每一个具体的处理,用类似OnRespCommandA这样的函数包裹起来,明确表明职责,这是较为稳妥的组织方法。

以上是关于业务思考-异步业务组织的主要内容,如果未能解决你的问题,请参考以下文章

接口异步调用导致的一个低概率问题引发的思考。

面向对象思维的编程方式

高性能异步事件驱动的NIO框架,结合英雄传说项目深入剖析Netty

分布式场景下Kafka消息顺序性的思考

对错误码的设计思考

从5秒到1秒,核心业务优化思考