Open Closed

Transaction Management in ABP Framework with Hexagonal Architecture #8567


User avatar
0
BeytullahBB created

Hello,

I am currently developing an application using ABP Framework with Hexagonal (Ports & Adapters) architectural pattern. In our project, we are implementing the CQRS (Command Query Responsibility Segregation) pattern, utilizing separate DbContexts and their respective database connections for Command and Query operations.

Issue Encountered: I'm experiencing difficulties integrating ABP Framework's Unit of Work mechanism into our current architecture. Specifically, I want to implement transaction management at the controller level.

Expected Behavior:

  • Initiation of transaction scope at the controller level
  • Automatic rollback of all database operations (performed across multiple DbContexts) in case of any exception during the process
  • Commit of all changes when the transaction is successful

Due to limitations in our current infrastructure, I am unable to meet these requirements and need assistance on how to properly implement ABP Framework's Unit of Work pattern.

Problem Description: In our project using Hexagonal architecture and CQRS pattern, we were experiencing difficulties in managing transactions across multiple DbContexts. Specifically, we needed to initiate transaction scope at the controller level and roll back all operations in case of exceptions.

Implementation Details:

DbContext Configuration: Created two separate DbContexts for Command and Query operations Both DbContexts inherit from BaseDbContext (AbpDbContext), IBaseDbContext, and IAuditLoggingDbContext Configured OnConfiguring method to use the appropriate connection string based on request type (Command/Query) Use Case Layer Implementation: Created separate Use Case classes for each entity (Example: CreateEntityNameUseCase) Injected relevant repositories into Use Case classes using Dependency Injection Configured each Use Case to call its corresponding repository methods Repository Layer Implementation: Created separate Command and Query repositories for each entity Implemented repository classes with the following structure: EntityNameDbContextNameRepository : EfCoreDbContextNameRepository<EntityName, EntityId>, IEntityNameDbContextNameRepository Designed repository methods to operate directly on the respective DbContext Example Create method: public async Task CreateEntityNameAsync(EntityName entity) { xxCommandDbContext.Set().Add(entity); await xxCommandDbContext.SaveChangesAsync(); return entity; }

Key Points: Implemented CQRS pattern using separate DbContexts for Command and Query operations Maintained clear boundaries between layers following Hexagonal architecture principles Ensured consistent repository implementation across all entities Achieved loose coupling between components using Dependency Injection Adopted standardized approach to entity operations across the application Benefits of this Implementation:

Clear separation of read and write operations Scalable and maintainable codebase Consistent data access across the application Easy integration with ABP Framework's built-in features

Technologies Used:

  • ABP Framework
  • .NET Core
  • Entity Framework Core
  • CQRS Pattern
  • Hexagonal Architecture

Code views of some files are as in the links below;

https://prnt.sc/J-lLInOw3CTP https://prnt.sc/wC26zqblG6LN https://prnt.sc/iryqe56p7Rp0 https://prnt.sc/g_hRBHesKJql https://prnt.sc/yLowmTvtAkHN https://prnt.sc/KEcCC2IWOZg4 https://prnt.sc/V9CTqawtcAL2 https://prnt.sc/YzLbUE6lMfmH

  • ABP Framework version: v9.0.2
  • UI Type: -
  • Database System: EF Core (PostgreSQL)
  • Tiered (for MVC) or Auth Server Separated (for Angular): yes
  • Exception message and full stack trace:
  • Steps to reproduce the issue:

5 Answer(s)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    EF Core & ABP support this case, You can begin a new uow in a controller action.

    Once a new UOW started, it creates an ambient scope that is participated by all the database operations performed in the current scope and considered as a single transaction boundary. The operations are committed (on success) or rolled back (on exception) all together.

    • Initiation of transaction scope at the controller level
    • Automatic rollback of all database operations (performed across multiple DbContexts) in case of any exception during the process
    • Commit of all changes when the transaction is successful
    [HttpPost]
    [Route("add-input-output-types" )]
    public async Task<IActionResult> CreateInputOutputType([FromBody] CreateInputOutputTypeCommand dto)
    {
        using (var uow = UnitOfWorkManager.Begin(requiresNew: true, isTransactional: true))
        {
            try
            {
                // You application service call.
                await uow.CompleteAsync();
            }
            catch (Exception e)
            {
                Logger.LogException(e);
                await uow.RollbackAsync();
            }
        }
    }
    

  • User Avatar
    0
    BeytullahBB created

    I encountered the following issues during the modifications made in the CreateInputOutputTypeAsync method:

    1. Transaction Management Issue:

      • When an error occurs during the repository.create operation in CreateInputOutputTypeAsync method
      • The system fails to perform the rollback operation
      • As a result, data continues to be saved to the database even in error scenarios https://prnt.sc/scsx8SSwwZCC
    2. Usage Scenario Incompatibility:

      • The proposed method is no longer working compatibly with our current usage scenarios
      • This causes the method to malfunction across three different usage scenarios https://prnt.sc/Bl5txLcWPuE1

    I tried an alternative approach to solve these issues, but the fundamental problem with transaction management still persists. I would appreciate your thoughts and suggestions on this matter.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    You don't need to start multiple now. Just one is enough. .

    Please set requiresNew: true to true and RollbackAsync if you get an exception

    See:

  • User Avatar
    0
    BeytullahBB created

    hello, 3 different uses were actually because we couldn't use that. As can be seen in the image below, the parameters expected by the begin method in version 9.0.2 do not match the ones in the image. https://prnt.sc/3ADJBxdAvvWY https://prnt.sc/3ADJBxdAvvWY

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    Its extension method of IUnitOfWorkManager

    See https://github.com/abpframework/abp/blob/dev/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWorkManagerExtensions.cs#L9-L24

Made with ❤️ on ABP v9.2.0-preview. Updated on January 08, 2025, 14:09