问题描述
我正在编写一个基于 spring-boot 的项目,其中有一些同步(例如 RESTI API 调用)和异步(JMS)代码段(我使用的代理是 ActiveMQ 的 dockerized 实例,以防万一技巧/解决方法)。
我目前正在努力解决的问题之一是:我的应用程序收到一个 REST api 调用(我将其称为“同步调用”),它进行一些处理,然后将 JMS 消息发送到队列(异步),其消息然后处理和处理(假设我有很重的负载要执行,所以这就是为什么我希望它是异步的)。
运行应用程序时一切正常,异步消息按预期入队和出队。 当我在编写测试时(并且我正在测试整个服务,其中包括快速连续的同步和异步调用)碰巧测试代码太快,并且消息仍在等待出队(我们正在谈论毫秒,但这就是问题)。
基本上,一旦我收到来自 API 调用的响应,该消息仍在队列中,因此,例如,如果我进行查询以检查其是否存在 -> ka-boom 测试失败,因为(显然)它没有找到对象(可能同时正在处理和创建)。
有什么方法或模式可以让我的测试等待异步消息出队?如果需要,我可以将代码附加到我的实现中,这是一个学士学位论文项目。
我暂时使用的一个明显的解决方案是在方法调用和断言部分之间添加一百毫秒的睡眠(希望一切都完成并保持),但老实说我有点不喜欢这个解决方案,因为它对我来说似乎很不确定.另外,在开发代码和测试之间创建一个闩锁对我来说听起来不太好。
public TransferResponseDTO transfer(Long userId,TransferRequestDTO transferRequestDTO) {
//Preconditions.checkArgument(transferRequestDTO.amount.compareto(BigDecimal.ZERO) < 0);
Preconditions.checkArgument(userHelper.existsById(userId));
Preconditions.checkArgument(walletHelper.existsByUserIdAndSymbol(userId,transferRequestDTO.symbol));
TransferMessage message = new TransferMessage();
message.userId = userId;
message.symbol = transferRequestDTO.symbol;
message.destination = transferRequestDTO.destination;
message.amount = transferRequestDTO.amount;
messageService.send(message);
TransferResponseDTO response = new TransferResponseDTO();
response.status = PENDING;
return response;
}
这是接收消息的代码(尽管您不需要它):
public void handle(TransferMessage transferMessage) {
Wallet source = walletHelper.findByUserIdAndSymbol(transferMessage.userId,transferMessage.symbol);
Wallet destination = walletHelper.findById(transferMessage.destination);
try {
walletHelper.withdraw(source,transferMessage.amount);
} catch (InsufficientBalanceException ex) {
String u = userHelper.findEmailByUserId(transferMessage.userId);
EmailMessage email = new EmailMessage();
email.subject = "Insufficient Balance in your account";
email.to = u;
email.text = "Your transfer of " + transferMessage.amount + " " + transferMessage.symbol + " has been DECLINED due to insufficient balance.";
messageService.send(email);
}
walletHelper.deposit(destination,transferMessage.amount);
String u = userHelper.findEmailByUserId(transferMessage.userId);
EmailMessage email = new EmailMessage();
email.subject = "Transfer executed";
email.to = u;
email.text = "Your transfer of " + transferMessage.amount + " " + transferMessage.symbol + " has been ACCEPTED.";
messageService.send(email);
}
对不起,如果代码听起来“有点粗略或错误”,这是一个原始实现。 如果是这样的话,我愿意编写一个实用程序与大家分享,但是,正如您可能已经注意到的,我现在缺乏想法。
解决方法
我是一名 ActiveMQ 开发人员,主要致力于 ActiveMQ Artemis(ActiveMQ 的下一代代理)。考虑到代理的异步性质,我们在测试套件中一直遇到此类问题,我们开发了一个小 utility class 来自动化和简化基本轮询操作。
例如,启动代理是异步的,因此我们的测试通常包含断言以确保代理在继续之前启动。使用老式 Java 6 语法,它看起来像这样:
Wait.assertTrue(new Condition() {
@Override
public boolean isSatisfied() throws Exception {
return server.isActive();
}
});
使用 Java 8 lambda 如下所示:
Wait.assertTrue(() -> server.isActive());
或者使用 Java 8 方法参考:
Wait.assertTrue(server::isActive);
该实用程序非常灵活,因为您使用的 Condition
可以测试您想要的任何内容,只要它最终返回 boolean
。此外,它与使用 Thread.sleep()
不同(如您所述)是确定性的,并且它将测试代码与“产品”代码分开。
在您的情况下,您可以检查是否可以找到由您的 JMS 进程创建的“对象”。如果未找到,则它可以继续检查,直到找到对象或超时结束。