private void validateIdentifier(String aggregateIdentifier, Aggregate<T> aggregate) { if (aggregateIdentifier != null && !aggregateIdentifier.equals(aggregate.identifierAsString())) { throw new AssertionError(String.format( "The aggregate used in this fixture was initialized with an identifier different than " + "the one used to load it. Loaded [%s], but actual identifier is [%s].\n" + "Make sure the identifier passed in the Command matches that of the given Events.", aggregateIdentifier, aggregate.identifierAsString())); } }
/** * Invoked when an the given {@code aggregate} instance has been detected that has been part of a rolled back Unit * of Work. This typically means that the state of the Aggregate instance has been compromised and cannot be * guaranteed to be correct. * <p> * This implementation throws an exception, effectively causing the unit of work to be rolled back. Subclasses that * can guarantee correct storage, even when specific instances are compromised, may override this method to suppress * this exception. * <p> * When this method is invoked, the {@link #doSave(Aggregate)}, {@link #doDelete(Aggregate)}, * {@link #postSave(Aggregate)} and {@link #postDelete(Aggregate)} are not invoked. Implementations may choose to * invoke these methods. * * @param aggregate The aggregate instance with illegal state */ protected void reportIllegalState(A aggregate) { throw new AggregateRolledBackException(aggregate.identifierAsString()); }
@Override public A newInstance(Callable<T> factoryMethod) throws Exception { UnitOfWork<?> uow = CurrentUnitOfWork.get(); AtomicReference<A> aggregateReference = new AtomicReference<>(); // a constructor may apply events, and the persistence of an aggregate must take precedence over publishing its events. uow.onPrepareCommit(x -> { A aggregate = aggregateReference.get(); // aggregate construction may have failed with an exception. In that case, no action is required on commit if (aggregate != null) { prepareForCommit(aggregate); } }); A aggregate = doCreateNew(factoryMethod); aggregateReference.set(aggregate); Assert.isTrue(aggregateModel.entityClass().isAssignableFrom(aggregate.rootType()), () -> "Unsuitable aggregate for this repository: wrong type"); Map<String, A> aggregates = managedAggregates(uow); Assert.isTrue(aggregates.putIfAbsent(aggregate.identifierAsString(), aggregate) == null, () -> "The Unit of Work already has an Aggregate with the same identifier"); uow.onRollback(u -> aggregates.remove(aggregate.identifierAsString())); return aggregate; }
@Override protected void validateOnLoad(Aggregate<T> aggregate, Long expectedVersion) { CurrentUnitOfWork.get().onRollback(u -> cache.remove(aggregate.identifierAsString())); super.validateOnLoad(aggregate, expectedVersion); }
/** * Checks the aggregate for concurrent changes. Throws a {@link ConflictingModificationException} when conflicting * changes have been detected. * <p> * This implementation throws a {@link ConflictingAggregateVersionException} if the expected version is not null * and the version number of the aggregate does not match the expected version * * @param aggregate The loaded aggregate * @param expectedVersion The expected version of the aggregate * @throws ConflictingModificationException when conflicting changes have been detected * @throws ConflictingAggregateVersionException the expected version is not {@code null} * and the version number of the aggregate does not match the expected * version */ protected void validateOnLoad(Aggregate<T> aggregate, Long expectedVersion) { if (expectedVersion != null && aggregate.version() != null && !expectedVersion.equals(aggregate.version())) { throw new ConflictingAggregateVersionException(aggregate.identifierAsString(), expectedVersion, aggregate.version()); } }
@Override protected LockAwareAggregate<T, A> doCreateNew(Callable<T> factoryMethod) throws Exception { A aggregate = doCreateNewForLock(factoryMethod); final String aggregateIdentifier = aggregate.identifierAsString(); Lock lock = lockFactory.obtainLock(aggregateIdentifier); try { CurrentUnitOfWork.get().onCleanup(u -> lock.release()); } catch (Throwable ex) { if (lock != null) { logger.debug("Exception occurred while trying to add an aggregate. Releasing lock.", ex); lock.release(); } throw ex; } return new LockAwareAggregate<>(aggregate, lock); }
@Override protected void validateOnLoad(Aggregate<T> aggregate, Long expectedVersion) { if (expectedVersion != null && expectedVersion < aggregate.version()) { DefaultConflictResolver conflictResolver = new DefaultConflictResolver(eventStore, aggregate.identifierAsString(), expectedVersion, aggregate.version()); ConflictResolution.initialize(conflictResolver); CurrentUnitOfWork.get().onPrepareCommit(uow -> conflictResolver.ensureConflictsResolved()); } else { super.validateOnLoad(aggregate, expectedVersion); } }
@SuppressWarnings({"unchecked"}) @Test public void testStoreAndLoadNewAggregate() throws Exception { UnitOfWork<?> uow = startAndGetUnitOfWork(); String originalId = repository.newInstance(() -> new JpaAggregate("Hello")).invoke(JpaAggregate::getIdentifier); uow.commit(); entityManager.flush(); entityManager.clear(); List<JpaAggregate> results = entityManager.createQuery("SELECT a FROM JpaAggregate a").getResultList(); assertEquals(1, results.size()); JpaAggregate aggregate = results.get(0); assertEquals(originalId, aggregate.getIdentifier()); uow = startAndGetUnitOfWork(); Aggregate<JpaAggregate> storedAggregate = repository.load(originalId); uow.commit(); assertEquals(storedAggregate.identifierAsString(), originalId); assertTrue(capturedEvents.isEmpty()); }
@Test public void testDeleteAnAggregate() { JpaAggregate agg = new JpaAggregate("First message"); entityManager.persist(agg); entityManager.flush(); entityManager.clear(); assertEquals((Long) 0L, agg.getVersion()); UnitOfWork<?> uow = startAndGetUnitOfWork(); Aggregate<JpaAggregate> aggregate = repository.load(agg.getIdentifier()); aggregate.execute(r -> r.setMessage("And again")); aggregate.execute(r -> r.setMessage("And more")); aggregate.execute(JpaAggregate::delete); uow.commit(); entityManager.flush(); entityManager.clear(); assertEquals(2, capturedEvents.size()); assertNull(entityManager.find(JpaAggregate.class, aggregate.identifierAsString())); }
@Test public void testUpdateAnAggregate() { JpaAggregate agg = new JpaAggregate("First message"); entityManager.persist(agg); entityManager.flush(); entityManager.clear(); UnitOfWork<?> uow = startAndGetUnitOfWork(); Aggregate<JpaAggregate> aggregate = repository.load(agg.getIdentifier()); aggregate.execute(r -> r.setMessage("And again")); aggregate.execute(r -> r.setMessage("And more")); uow.commit(); assertEquals((Long) 1L, aggregate.version()); assertEquals(2, capturedEvents.size()); assertNotNull(entityManager.find(JpaAggregate.class, aggregate.identifierAsString())); }
/** * Invoked when an the given {@code aggregate} instance has been detected that has been part of a rolled back Unit * of Work. This typically means that the state of the Aggregate instance has been compromised and cannot be * guaranteed to be correct. * <p> * This implementation throws an exception, effectively causing the unit of work to be rolled back. Subclasses that * can guarantee correct storage, even when specific instances are compromised, may override this method to suppress * this exception. * <p> * When this method is invoked, the {@link #doSave(Aggregate)}, {@link #doDelete(Aggregate)}, * {@link #postSave(Aggregate)} and {@link #postDelete(Aggregate)} are not invoked. Implementations may choose to * invoke these methods. * * @param aggregate The aggregate instance with illegal state */ protected void reportIllegalState(A aggregate) { throw new AggregateRolledBackException(aggregate.identifierAsString()); }
@Override public A newInstance(Callable<T> factoryMethod) throws Exception { UnitOfWork<?> uow = CurrentUnitOfWork.get(); AtomicReference<A> aggregateReference = new AtomicReference<>(); // a constructor may apply events, and the persistence of an aggregate must take precedence over publishing its events. uow.onPrepareCommit(x -> prepareForCommit(aggregateReference.get())); A aggregate = doCreateNew(factoryMethod); aggregateReference.set(aggregate); Assert.isTrue(aggregateModel.entityClass().isAssignableFrom(aggregate.rootType()), () -> "Unsuitable aggregate for this repository: wrong type"); Map<String, A> aggregates = managedAggregates(uow); Assert.isTrue(aggregates.putIfAbsent(aggregate.identifierAsString(), aggregate) == null, () -> "The Unit of Work already has an Aggregate with the same identifier"); uow.onRollback(u -> aggregates.remove(aggregate.identifierAsString())); return aggregate; }
@Override protected void validateOnLoad(Aggregate<T> aggregate, Long expectedVersion) { CurrentUnitOfWork.get().onRollback(u -> cache.remove(aggregate.identifierAsString())); super.validateOnLoad(aggregate, expectedVersion); }
/** * Checks the aggregate for concurrent changes. Throws a {@link ConflictingModificationException} when conflicting * changes have been detected. * <p> * This implementation throws a {@link ConflictingAggregateVersionException} if the expected version is not null * and the version number of the aggregate does not match the expected version * * @param aggregate The loaded aggregate * @param expectedVersion The expected version of the aggregate * @throws ConflictingModificationException when conflicting changes have been detected * @throws ConflictingAggregateVersionException the expected version is not {@code null} * and the version number of the aggregate does not match the expected * version */ protected void validateOnLoad(Aggregate<T> aggregate, Long expectedVersion) { if (expectedVersion != null && aggregate.version() != null && !expectedVersion.equals(aggregate.version())) { throw new ConflictingAggregateVersionException(aggregate.identifierAsString(), expectedVersion, aggregate.version()); } }
@Override protected LockAwareAggregate<T, A> doCreateNew(Callable<T> factoryMethod) throws Exception { A aggregate = doCreateNewForLock(factoryMethod); final String aggregateIdentifier = aggregate.identifierAsString(); Lock lock = lockFactory.obtainLock(aggregateIdentifier); try { CurrentUnitOfWork.get().onCleanup(u -> lock.release()); } catch (Throwable ex) { if (lock != null) { logger.debug("Exception occurred while trying to add an aggregate. Releasing lock.", ex); lock.release(); } throw ex; } return new LockAwareAggregate<>(aggregate, lock); }
@Override protected void validateOnLoad(Aggregate<T> aggregate, Long expectedVersion) { if (expectedVersion != null && expectedVersion < aggregate.version()) { DefaultConflictResolver conflictResolver = new DefaultConflictResolver(eventStore, aggregate.identifierAsString(), expectedVersion, aggregate.version()); ConflictResolution.initialize(conflictResolver); CurrentUnitOfWork.get().onPrepareCommit(uow -> conflictResolver.ensureConflictsResolved()); } else { super.validateOnLoad(aggregate, expectedVersion); } }