I would like to know if it is possible to start a transaction to search for my entity, update the information, then call the JpaRepository save and upon return get the new version (I'm using @Version), as I would like to use the most current version to put in the my productUpdated event for example. If not, I will need to create a method just so that when I instantiate the event's record class, call product.getVersion() + 1, since there will always be a previous version, remembering that I need this because I save the event in a table also in the same transaction. Follow my current code
private Product update(final Product aProduct) { final var aResult = this.productRepository.save(ProductJpaEntity.toEntity(aProduct)); return aResult.toDomain();}
this test:
transactionTemplate.execute(status -> { try { final var aProductFindById = this.productRepository.findById(aProduct.getId().getValue()) .get().toDomain(); aProductFindById.updateStatus(ProductStatus.INACTIVE); final var aProductUpdated = this.productGateway.update(aProductFindById); // version in this moment is 0 System.out.println(aProductUpdated.getVersion()); this.outboxEventRepository.save(OutboxEventEntity.from(new TestListenerDomainEvent( String.valueOf(aProductUpdated.getVersion()) ))); return null; } catch (Throwable t) { status.setRollbackOnly(); this.productRepository.findById(aProduct.getId().getValue()) .ifPresent(a -> System.out.println(a.getVersion())); throw t; } });
After completing the transaction I can now get the new version value, if I can't get it directly from the save / update method return, I will need to create a method just to get the current version and add + 1. Of course, if there is no other solution.
I implemented an abstraction, here are the classes and here is how I'm using it at the moment, even though the event publisher throws an exception it doesn't rollback, I can get the newest version which would be the one that comes after the update, but it ends up not giving the rollback
public class TransactionResult<T> { private final T successResult; private final Error errorResult; private TransactionResult(T aSuccessResult, Error aErrorResult) { this.successResult = aSuccessResult; this.errorResult = aErrorResult; } public static <T> TransactionResult<T> success(T aSuccessResult) { return new TransactionResult<>(aSuccessResult, null); } public static <T> TransactionResult<T> failure(Error aErrorResult) { return new TransactionResult<>(null, aErrorResult); } public boolean isSuccess() { return this.successResult != null; } public boolean isFailure() { return this.errorResult != null; } public T getSuccessResult() { return this.successResult; } public Error getErrorResult() { return this.errorResult; } }
TransactionManager class:
public interface TransactionManager {<T> TransactionResult<T> execute(Supplier<T> action);}
TransactionManagerImpl:
@Componentpublic class TransactionManagerImpl implements TransactionManager { private final PlatformTransactionManager manager; public TransactionManagerImpl(PlatformTransactionManager manager) { this.manager = manager; } @Override public <T> TransactionResult<T> execute(Supplier<T> action) { final var aTransactionTemplate = new TransactionTemplate(manager); try { T result = aTransactionTemplate.execute(status -> { try { return action.get(); } catch (Exception e) { status.setRollbackOnly(); throw e; } }); return TransactionResult.success(result); } catch (Exception e) { return TransactionResult.failure(new Error(e.getMessage())); } }}
event publisher impl:
@Componentpublic class EventPublisherImpl implements EventPublisher { @Override public <T extends DomainEvent> void publish(T event) { throw new UnsupportedOperationException("Error: Not implemented yet"); }}
My usecase:
public Either<NotificationHandler, UpdateProductOutput> execute(UpdateProductCommand input) { final var aNotification = NotificationHandler.create(); final var aProduct = this.productGateway.findById(input.id()) .orElseThrow(NotFoundException.with(Product.class, input.id())); if (aProduct.getStatus().equals(ProductStatus.DELETED)) { throw ProductIsDeletedException.with(aProduct.getId()); } final var aCategoryId = input.categoryId() == null || input.categoryId().isBlank() ? aProduct.getCategoryId() : this.categoryGateway.findById(input.categoryId()) .orElseThrow(NotFoundException.with(Category.class, input.categoryId())) .getId(); final var aName = input.name() == null || input.name().isBlank() ? aProduct.getName() : input.name(); final var aPrice = input.price() == null ? aProduct.getPrice() : input.price(); final var aProductUpdated = aProduct.update( aName, input.description(), aPrice, input.quantity(), aCategoryId ); aProductUpdated.validate(aNotification); if (aNotification.hasError()) { return Either.left(aNotification); } final var aResult = this.transactionManager.execute(() -> { final var aProductSaved = this.productGateway.update(aProductUpdated); System.out.println("after call update in usecase -> " + aProductSaved.getVersion()); this.eventPublisher.publish(ProductUpdatedEvent.from(aProductSaved)); return aProductSaved; }); if (aResult.isFailure()) { aNotification.append(new Error("error to update product")); return Either.left(aNotification); } return Either.right(UpdateProductOutput.from(aProductUpdated)); }
it returns failure, but the transaction continues without rolling back.