对于 .NET Core 应用程序中的相同表使用两个单独的存储库实例(一个作为作用域,一个作为单例)是一个好的设计吗?

问题描述 投票:0回答:1

我正在开发一个有两个用例的 .NET Core 应用程序。一个是普通的 HTTP post API 端点,我在执行业务逻辑后在一些 SQL Server 表中创建一些记录。

另一个工作流程是 RabbitMQ 消费者,它将定期消费消息并更新同一组表。

对于 API 端点,我将存储库实例作为范围依赖项注入。但对于 RabbitMQ 消费者,我被迫将所有依赖项用作单例,因为消费者本身就是一个单例。单例的另一个原因是消费者使用 TPL 数据流管道,它也需要单例以确保消息的有序消费。以下是存储库界面。

public interface ISessionRepository
    {
        Task<Result<Session>> GetAsync(int sessionId);

        Task<Result<bool>> UpdateAsync(Session session);
    }

我有上述界面的两份不同名称的副本。但实现使用相同的表。单例存储库实现使用 Dapper 进行更新,而作用域存储库实例使用实体框架进行更新。

HTTP 工作流程和 RabbitMQ 工作流程可能会重叠,这意味着当消费者尝试更新表时,HTTP 工作流程也可能会尝试更新或删除表记录。

这是一个严重的设计缺陷吗?重叠的可能性很小,可以通过处理数据库异常并在我的存储库实现中实现重试来解决这个问题吗?或者有没有其他方法可以处理这种情况。

.net .net-core
1个回答
0
投票

可能的设计问题

并发控制:如果没有适当的并发控制,HTTP API 和 RabbitMQ 使用者的同时更新可能会导致数据不一致、竞争条件,甚至数据丢失。

事务管理:Dapper(在单例场景中使用)和实体框架(在范围场景中使用)之间的不同处理机制可能会导致事务管理方式上的差异。

可扩展性:随着应用程序的增长,RabbitMQ 消费者中的单例方法可能会成为瓶颈,跨两个不同流有效管理状态可能会变得越来越复杂。

解决问题

乐观并发控制:在 SQL Server 表中实施乐观并发控制。这通常涉及在表中使用版本号或时间戳列。当您执行更新时,您会检查该版本自您上次阅读以来是否没有更改。如果存在冲突,您可以重试该操作。这种方法得到了实体框架的良好支持,也可以使用 Dapper 来实现。

统一数据访问策略:考虑在整个应用程序中使用单一数据访问策略(Dapper 或实体框架)。这可以简化事务管理并确保应用程序不同部分的行为一致。

数据库锁:作为一种更直接的解决方案,您可以实现数据库级锁以确保一次只有一个操作可以修改一条记录。但是,这会降低应用程序的吞吐量,因此应谨慎使用。

处理重试和死锁:在存储库方法中实现重试逻辑以处理瞬态数据库错误,包括死锁。该策略应包括指数退避,以避免加剧任何争用问题。

最终一致性:如果始终不需要严格一致性,请考虑采用异步处理 RabbitMQ 消费者更新的方法,并且您的应用程序可以容忍最终一致性。这可以减少数据库的直接压力,但需要仔细设计以确保系统完整性。

域事件:不要直接从两个不同的源更新表,而是考虑使用以一致方式处理的域事件,无论它们源自 HTTP API 还是 RabbitMQ 使用者。这有助于保持业务逻辑和数据操作的一致方法。

结论: 虽然处理数据库异常和实现重试可以缓解一些问题,但通常最好解决底层设计以确保数据一致性和应用程序可扩展性。分析使用 Dapper 和实体框架的必要性并转向统一的方法可以显着降低复杂性和潜在问题。

请记住,正确的解决方案取决于您的具体要求,包括可接受的一致性、吞吐量和可扩展性水平。如果重叠最小且受控,则您当前的方法可能足以改进错误处理和重试。但是,如果您预计增长或重叠增加,请考虑采用更稳健的策略来确保一致性和可靠性。

© www.soinside.com 2019 - 2024. All rights reserved.