我正在开展酒店预订项目。新功能要求客户可以评论酒店。每个评论都有一个评级(0 - > 5)。在搜索酒店页面上,每家酒店都显示其平均评分。
添加注释非常简单,可以发布CommentAddedEvent:
class CommentAddedEvent {
private String hotelId;
private double rating;
//other attributes omitted
}
在查询方面,此事件持续存在注释行:
create table t_comment {
hotel_id number,
rating number(2,1)
}
但我们放慢了如何显示酒店的平均评分。以下是我们的潜在解决方案:
a)数据库集成
在搜索酒店查询方面,使用sql函数获取平均值:
select h.id, h.name,....,select avg(c.rating) from t_commentc , t_hotel
where.... group by c.hotel_id.....
这看起来很简单,但我们认为它会在测试中引入更多精力,并可能导致一些性能问题。
b)EventHandler计算平均值
添加订阅CommentAddedEvent的事件处理程序并计算平均值:
public void on(CommentAddedEvent event) {
Hotel hotel = ....
double total = hotel.rating * hotel.comments;
double average = (total + event.rating)/(hotel.comments + 1)
//update hotel
}
测试很简单,但这个解决方案似乎不是幂等的。当事情失败时,事件处理程序可以处理重复事件。
c)预定任务
添加预定任务,汇总每家酒店的评论。但是,自从上一个任务以来,一些酒店没有被评论,这是效率低下的。
d)计划任务与事件处理程序混合
使用事件处理程序标记自上次任务以来评论的酒店:
public void on(CommentAddedEvent event) {
int count = uncalculatedCommentsOnHotel(...);
count++;
//update count
}
根据计数大于零的酒店安排汇总任务。
解决方案D是幂等的,似乎更有效。我们是否存在一些缺陷或任何其他解决方案?
答案 0 :(得分:3)
我会选择实现一个计算平均值的事件处理程序(选项b)。你有几种克服幂等性问题的策略。
通过使用相关标识符在处理程序中添加条件检查,使消息处理程序具有幂等性。在这种情况下,您可以跟踪已为每个酒店处理的评论的ID,以防止并仅处理新评论。
public void on(CommentAddedEvent event)
{
Hotel hotel = ....
if (hotel.CommentIds.Contains(event.CommentId))
return; // comment has already been processed
double total = hotel.rating * hotel.comments;
double average = (total + event.rating)/(hotel.comments + 1)
hotel.CommentIds.Add(event.CommentId);
//update hotel
}
在基础架构级别重复删除消息。收到事件后,可以针对事件存储进行检查。如果消息已经出现在事件存储中,则可以安全地丢弃它。这将确保每个事件处理程序最多只接收一次事件。
通过将重复数据删除责任移到基础结构上,这意味着您不需要使每个事件处理程序具有幂等性。这将允许原始选项b处理程序按原样工作。
进一步阅读