},
// 2. RPC
(isRetry, paymentRequest) -> {
return executePayment(paymentRequest, isRetry);
},
// 3. Post RPC - record response information to database
(isRetry, paymentResponse) -> {
return recordPaymentResponse(paymentResponse);
});
}
这是源代码的简化版本:public <R extends Object, S extends Object, A extends IdempotencyRequest> Response process(
String idempotencyKey,
UriInfo uriInfo,
SetupExecutable<A> preRpcExecutable, // Pre-RPC lambda
ProcessExecutable<R, A> rpcExecutable, // RPC lambda
PostProcessExecutable<R, S> postRpcExecutable) // Post-RPC lambda
throws YourCustomException {
try {
// Find previous request (for retries), otherwise create
IdempotencyRequest idempotencyRequest = createOrFindRequest(idempotencyKey, apiUri);
Optional<Response> responseoptional = findIdempotencyResponse(idempotencyRequest);
// Return the response for any deterministic end-states, such as
// non-retryable errors and previously successful responses
if (responseOptional.isPresent) {
return responseOptional.get;
}
boolean isRetry = idempotencyRequest.isRetry;
A requestObject = ;
// STEP 1: Pre-RPC phase:
// Typically used to create transaction and related sub-entities
// Skipped if request is a retry
if(!isRetry) {
// Before a request is made to the external service, we record
// the request and idempotency commit in a single DB transaction
requestObject =
dbTransactionManager.execute(
tc -> {
final A preRpcResource = preRpcExecutable.execute;
updateIdempotencyResource(idempotencyKey, preRpcResource);
return preRpcResource;
});
} else {
requestObject = findRequestObject(idempotencyRequest);
}
// STEP 2: RPC phase:
// One or more network calls to the service. May include
// additional idempotency logic in the case of a retry
// Note: NO database transactions should exist in this executable
R rpcResponse = rpcExecutable.execute(isRetry, requestObject);
// STEP 3: Post-RPC phase:
// Response is recorded and idempotency information is updated,
// such as releasing the lease on the idempotency key. Again,
// all in one single DB transaction
S response = dbTransactionManager.execute(
tc -> {
final S postRpcResponse = postRpcExecutable.execute(isRetry, rpcResponse);
updateIdempotencyResource(idempotencyKey, postRpcResponse);
return postRpcResponse;
});
return serializeResponse(response);
} catch (Throwable exception) {
// If CustomException, return error code and response based on
// ‘retryable’ or ‘non-retryable’. Otherwise, classify as ‘retryable’
// and return a 500.
}
}
我们没有实现嵌套数据库事务 , 而是将Orpheus和应用程序中的数据库指令组合成单个数据库事务 , 传递Java 闭包 。
开发人员必须预先考虑好 , 才能保代码的可读性和可维护性 。他们还需要始终如一地评估适当的依赖关系和数据传递 。现在需要将API调用重构为三个部分 , 这可能会限制开发人员编写代码的方式 。实际上 , 某些复杂的API调用实际上很难有效地分解为三步 。我们的服务实现了一个有限状态机 , 每次转换都是使用StatefulJ的幂等步骤 , 可以在API调用中安全地复用幂等调用 。
处理异常 - 重试还是不重试?使用像Orpheus这样的框架 , 服务器应该知道何时可以重试请求 , 而何时不行 。要做到这一点 , 应该以细致的处理异常 , 异常被分类为“可重试”或“不可重试”两大类 。这无疑为开发人员增加了一层复杂性 , 如果他们错误使用 , 就会产生副作用 。
例如 , 假设下游服务暂时宕机 , 经常被错误地标记为“不可重试” 。这样请求将无限期地“失败” , 并且后续重试请求将永远返回不可重试错误 。相反 , 如果异常被标记为“可重试”(而实际应该是“不可重试”且需要人工干预) , 则可能会发生双重付款 。
推荐阅读
- 大家都在说的分布式系统到底是什么?
- 支付开发填坑记之支付宝
- 怎么用集分宝 集分宝在支付的时候怎么使用
- DNS即域名系统怎样工作?看这位“翻译官”如何转换域名和IP地址
- 软件架构-解密电商系统-秒杀的原理和开发思路
- 智能电视两大系统UI对比,原来系统体验好和无广告才是重中之重
- 科技涨价板块的核心个股名单
- 如何更改微信乘车码支付方式
- 微信支付电子小票上线
- carplay是什么功能