@Override public void execute(Consumer<AR> invocation) { wrappedAggregate.execute(invocation); }
@Override public Object handle(Message<?> message) throws Exception { return wrappedAggregate.handle(message); }
/** * Resolves the value to return when the given {@code command} has created the given {@code aggregate}. * This implementation returns the identifier of the created aggregate. * <p> * This method may be overridden to change the return value of this Command Handler * * @param command The command being executed * @param createdAggregate The aggregate that has been created as a result of the command * @return The value to report as result of the command */ protected Object resolveReturnValue(CommandMessage<?> command, Aggregate<T> createdAggregate) { return createdAggregate.identifier(); }
/** * 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 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; }
/** * 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 Long version() { return wrappedAggregate.version(); }
@Override public boolean isDeleted() { return wrappedAggregate.isDeleted(); }
@Override public Class<? extends AR> rootType() { return wrappedAggregate.rootType(); } }
@Override public <R> R invoke(Function<AR, R> invocation) { return wrappedAggregate.invoke(invocation); }
@Override public String type() { return wrappedAggregate.type(); }
@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); } }
@Override protected void validateOnLoad(Aggregate<T> aggregate, Long expectedVersion) { CurrentUnitOfWork.get().onRollback(u -> cache.remove(aggregate.identifierAsString())); super.validateOnLoad(aggregate, expectedVersion); }
@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; }
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); } }
@CommandHandler public void handle(CreditDestinationBankAccountCommand command) { try { Aggregate<BankAccount> bankAccountAggregate = repository.load(command.getBankAccountId()); bankAccountAggregate.execute(bankAccount -> bankAccount .credit(command.getAmount(), command.getBankTransferId())); } catch (AggregateNotFoundException exception) { eventBus.publish(asEventMessage(new DestinationBankAccountNotFoundEvent(command.getBankTransferId()))); } } }
@Override public void send(Message<?> message, ScopeDescriptor scopeDescription) throws Exception { if (canResolve(scopeDescription)) { String aggregateIdentifier = ((AggregateScopeDescriptor) scopeDescription).getIdentifier().toString(); try { load(aggregateIdentifier).handle(message); } catch (AggregateNotFoundException e) { logger.debug("Aggregate (with id: [{}]) cannot be loaded. Hence, message '[{}]' cannot be handled.", aggregateIdentifier, message); } } }
@Override public Object identifier() { return wrappedAggregate.identifier(); }
@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); }
@CommandHandler public void handle(DebitSourceBankAccountCommand command) { try { Aggregate<BankAccount> bankAccountAggregate = repository.load(command.getBankAccountId()); bankAccountAggregate.execute(bankAccount -> bankAccount .debit(command.getAmount(), command.getBankTransferId())); } catch (AggregateNotFoundException exception) { eventBus.publish(asEventMessage(new SourceBankAccountNotFoundEvent(command.getBankTransferId()))); } }