/** * 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 public void execute(Consumer<AR> invocation) { wrappedAggregate.execute(invocation); }
@Override public Object handle(Message<?> message) throws Exception { return wrappedAggregate.handle(message); }
private void assertValidWorkingAggregateState(Aggregate<T> eventSourcedAggregate, MatchAllFieldFilter fieldFilter, Aggregate<T> workingAggregate) { HashSet<ComparationEntry> comparedEntries = new HashSet<>(); if (!workingAggregate.rootType().equals(eventSourcedAggregate.rootType())) { throw new AxonAssertionError(String.format("The aggregate loaded based on the generated events seems to " + "be of another type than the original.\n" + "Working type: <%s>\nEvent Sourced type: <%s>", workingAggregate.rootType().getName(), eventSourcedAggregate.rootType().getName())); } ensureValuesEqual(workingAggregate.invoke(Function.identity()), eventSourcedAggregate.invoke(Function.identity()), eventSourcedAggregate.rootType().getName(), comparedEntries, fieldFilter); }
@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())); }
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())); } }
@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 public Long version() { return wrappedAggregate.version(); }
@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())); }
@CommandHandler public MyAggregate handle(MyCommand command) throws Exception { Aggregate<MyAggregate> aggregate; try { aggregate = repository.load(command.getAggregateIdentifier()); } catch (AggregateNotFoundException e) { aggregate = repository.newInstance(() -> new MyAggregate(command.getAggregateIdentifier())); } aggregate.execute(a -> a.handle(command, commandGateway)); return aggregate.invoke(Function.identity()); } }
@Override public <R> R invoke(Function<AR, R> invocation) { return wrappedAggregate.invoke(invocation); }
@Override public boolean isDeleted() { return wrappedAggregate.isDeleted(); }
@Override public Class<? extends AR> rootType() { return wrappedAggregate.rootType(); } }
/** * 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 public Aggregate<T> load(String aggregateIdentifier, Long expectedVersion) { ((CommandHandlingEntry) CurrentUnitOfWork.get()).registerAggregateIdentifier(aggregateIdentifier); Aggregate<T> aggregate = load(aggregateIdentifier); if (expectedVersion != null && aggregate.version() > expectedVersion) { throw new ConflictingAggregateVersionException(aggregateIdentifier, expectedVersion, aggregate.version()); } return aggregate; }
@Test public void orderInEventSourcedAggregate() { Repository<MyAggregate> repository = configuration.repository(MyAggregate.class); configuration.commandGateway().sendAndWait(command); UnitOfWork unitOfWork = DefaultUnitOfWork.startAndGet(GenericCommandMessage.asCommandMessage("loading")); MyAggregate loadedAggregate = repository.load(aggregateIdentifier).invoke(Function.identity()); unitOfWork.commit(); assertEquals(expectedDescriptions(command), loadedAggregate.getHandledCommands()); }
private void doCommit(A aggregate) { if (managedAggregates(CurrentUnitOfWork.get()).containsValue(aggregate)) { if (aggregate.isDeleted()) { doDelete(aggregate); } else { doSave(aggregate); } if (aggregate.isDeleted()) { postDelete(aggregate); } else { postSave(aggregate); } } else { reportIllegalState(aggregate); } }
@Override public Class<? extends AR> rootType() { return wrappedAggregate.rootType(); } }
@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); } }