Solucionado (ver solução)
Solucionado
(ver solução)
2
respostas

[Dúvida] Gerenciamento de transação

Me deparei com um problema no uso da Clean Architecture. Imagina a seguinte situação:

Tenho um sistema de compra. Quando o usuário finalizar a compra deve-se criar um pedido e diminuir a quantidade no estoque do produto. Para isto acontecer deve-se usar dois repositories, um para criar o pedido outro para alterar a quantidade do produto. Estes dois repositories devem ser executados na mesma transação. Os dois repositories injetados na camada de aplicação devem ser atômicos. Não posso simplesmente anotar o método do use case com @Transaction porque o use case não deve conhecer nada do framework de persistência.

Qual a forma de fazer um controle de transação, principalmente quando o use case utiliza outro use case ou mais de um repository, sem ferir os princípios da Clean Architecture?

2 respostas
solução!

Oi Willian! Tudo bem?

Realmente, não é ideal que os casos de uso conheçam detalhes do framework de persistência. Aqui está uma abordagem que pode te ajudar:

  1. Utilização de um Serviço de Transação

    Você pode criar um serviço de transação que encapsula a lógica de início, commit e rollback das transações. Esse serviço pode ser injetado nos seus casos de uso sem que eles precisem conhecer os detalhes do framework de persistência.

    Exemplo Prático:

    • Criar uma interface de serviço de transação:

      public interface TransactionService {
          void executeInTransaction(Runnable action);
      }
      
    • Implementar a interface utilizando o framework de persistência:

    Se você estiver usando Spring, por exemplo:

    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    @Service
    public class SpringTransactionService implements TransactionService {
    
        @Override
        @Transactional
        public void executeInTransaction(Runnable action) {
            action.run();
        }
    }
    
    • Usar o serviço de transação nos seus casos de uso:
    public class PurchaseUseCase {
        private final TransactionService transactionService;
        private final OrderRepository orderRepository;
        private final ProductRepository productRepository;
    
        public PurchaseUseCase(TransactionService transactionService, OrderRepository orderRepository, ProductRepository productRepository) {
            this.transactionService = transactionService;
            this.orderRepository = orderRepository;
            this.productRepository = productRepository;
        }
    
        public void execute(PurchaseRequest request) {
            transactionService.executeInTransaction(() -> {
                Order order = createOrder(request);
                orderRepository.save(order);
                productRepository.updateStock(request.getProductId(), request.getQuantity());
            });
        }
    
        private Order createOrder(PurchaseRequest request) {
            // lógica para criar o pedido
        }
    }
    

Dessa forma, você mantém a separação de preocupações: o caso de uso não precisa conhecer detalhes sobre transações, e a lógica de transação é encapsulada em um serviço específico.

Espero ter ajudado e bons estudos!

Caso este post tenha lhe ajudado, por favor, marcar como solucionado ✓.

Nesse caso a interface TransactionService ficaria na camada de use case e sua implementação na infraestrutura?

Quando utilizo um listener de Message Queue creio que ele fique na camada de infraestrutura, correto? Nesse exemplo da interface TransacationService como deixar o listener do MQ e os use cases usando a mesma transação quando utiliza-se o two phase commit?

Obrigado