/** * Reads the events for the given aggregateIdentifier from the eventStore. this method may be overridden to * add pre or postprocessing to the loading of an event stream * * @param aggregateIdentifier the identifier of the aggregate to load * @return the domain event stream for the given aggregateIdentifier */ protected DomainEventStream readEvents(String aggregateIdentifier) { return eventStore.readEvents(aggregateIdentifier); }
@Override public void run() { DomainEventStream eventStream = eventStore.readEvents(identifier); // a snapshot should only be stored if the snapshot replaces at least more than one event long firstEventSequenceNumber = eventStream.peek().getSequenceNumber(); DomainEventMessage snapshotEvent = createSnapshot(aggregateType, identifier, eventStream); if (snapshotEvent != null && snapshotEvent.getSequenceNumber() > firstEventSequenceNumber) { eventStore.storeSnapshot(snapshotEvent); } } }
@Test public void testPublicationOrderIsMaintained_AggregateAdded() { String aggregateId = UUID.randomUUID().toString(); GenericDomainEventMessage<StubAggregateCreatedEvent> event = new GenericDomainEventMessage<>("test", aggregateId, 0, new StubAggregateCreatedEvent(aggregateId)); when(eventStore.readEvents(aggregateId)).thenReturn(DomainEventStream.of(event)); doAnswer(invocation -> { System.out.println("Published event: " + invocation.getArguments()[0].toString()); return Void.class; }).when(eventStore).publish(isA(EventMessage.class)); commandBus.dispatch(asCommandMessage(new UpdateStubAggregateWithExtraEventCommand(aggregateId))); InOrder inOrder = inOrder(eventStore, eventStore, eventStore); inOrder.verify(eventStore).publish(isA(DomainEventMessage.class)); inOrder.verify(eventStore).publish(argThat(new NotADomainEventMatcher())); inOrder.verify(eventStore).publish(isA(DomainEventMessage.class)); }
@DeadlineHandler(deadlineName = "payloadlessDeadline") public void on() { eventStore.publish(asEventMessage(new SpecificDeadlineOccurredEvent(null))); } }
@ReadOperation public boolean hasData() { try (TrackingEventStream trackingEventStream = eventStore.openStream(null)) { return trackingEventStream.hasNextAvailable(); } }
@Override protected AnnotatedAggregate<T> doLoadWithLock(String aggregateIdentifier, Long expectedVersion) { T aggregateRoot = entityManagerProvider.getEntityManager().find(getAggregateType(), identifierConverter.apply(aggregateIdentifier), LockModeType.PESSIMISTIC_WRITE); if (aggregateRoot == null) { throw new AggregateNotFoundException(aggregateIdentifier, format("Aggregate [%s] with identifier [%s] not found", getAggregateType().getSimpleName(), aggregateIdentifier)); } AnnotatedAggregate<T> aggregate = AnnotatedAggregate.initialize(aggregateRoot, aggregateModel(), eventBus, repositoryProvider); if (eventBus instanceof EventStore) { Optional<Long> sequenceNumber = ((EventStore) eventBus).lastSequenceNumberFor(aggregateIdentifier); sequenceNumber.ifPresent(aggregate::initSequence); } return aggregate; }
@SuppressWarnings("unchecked") @Test public void testInterceptorWithChainProceeding() { commandGateway.sendAndWait(asCommandMessage(new CreateMyAggregateCommand("id"))); commandGateway.sendAndWait(asCommandMessage(new ClearMyAggregateStateCommand("id", true))); ArgumentCaptor<EventMessage<?>> eventCaptor = ArgumentCaptor.forClass(EventMessage.class); verify(eventStore, times(3)).publish(eventCaptor.capture()); assertEquals(new MyAggregateCreatedEvent("id"), eventCaptor.getAllValues().get(0).getPayload()); assertEquals(new AnyCommandInterceptedEvent(ClearMyAggregateStateCommand.class.getName()), eventCaptor.getAllValues().get(1).getPayload()); assertEquals(new MyAggregateStateClearedEvent("id"), eventCaptor.getAllValues().get(2).getPayload()); }
@Override default Optional<Long> lastSequenceNumberFor(String aggregateIdentifier) { return readEvents(aggregateIdentifier).asStream().map(DomainEventMessage::getSequenceNumber) .max(Long::compareTo); } }
@Override public void run() { DomainEventStream eventStream = eventStore.readEvents(identifier); // a snapshot should only be stored if the snapshot replaces at least more than one event long firstEventSequenceNumber = eventStream.peek().getSequenceNumber(); DomainEventMessage snapshotEvent = createSnapshot(aggregateType, identifier, eventStream); if (snapshotEvent != null && snapshotEvent.getSequenceNumber() > firstEventSequenceNumber) { eventStore.storeSnapshot(snapshotEvent); } } }
DomainEventStream storedEvents = eventStore.readEvents(aggregateIdentifier); assertTrue(storedEvents.hasNext()); while (storedEvents.hasNext()) { verify(eventStore, times(3)).publish(anyEventList());
@SuppressWarnings("unchecked") @Test public void testInterceptorWithoutChainProceeding() { commandGateway.sendAndWait(asCommandMessage(new CreateMyAggregateCommand("id"))); commandGateway.sendAndWait(asCommandMessage(new ClearMyAggregateStateCommand("id", false))); ArgumentCaptor<EventMessage<?>> eventCaptor = ArgumentCaptor.forClass(EventMessage.class); verify(eventStore, times(2)).publish(eventCaptor.capture()); assertEquals(new MyAggregateCreatedEvent("id"), eventCaptor.getAllValues().get(0).getPayload()); assertEquals(new MyAggregateStateNotClearedEvent("id"), eventCaptor.getAllValues().get(1).getPayload()); }
private List<DomainEventMessage<?>> unexpectedEvents() { if (events == null) { if (expectedVersion >= actualVersion) { return Collections.emptyList(); } events = eventStore.readEvents(aggregateIdentifier, expectedVersion + 1).asStream() .filter(event -> event.getSequenceNumber() <= actualVersion).collect(toList()); } return events; }
@Override public void run() { DomainEventStream eventStream = eventStore.readEvents(identifier); // a snapshot should only be stored if the snapshot replaces at least more than one event long firstEventSequenceNumber = eventStream.peek().getSequenceNumber(); DomainEventMessage snapshotEvent = createSnapshot(aggregateType, identifier, eventStream); if (snapshotEvent != null && snapshotEvent.getSequenceNumber() > firstEventSequenceNumber) { eventStore.storeSnapshot(snapshotEvent); } } }
DomainEventStream storedEvents = eventStore.readEvents(aggregateIdentifier); assertTrue(storedEvents.hasNext()); verify(eventStore, times(3)).publish(anyEventList());
@SuppressWarnings("unchecked") @Test public void testInterceptorWithNestedEntity() { commandGateway.sendAndWait(asCommandMessage(new CreateMyAggregateCommand("id"))); commandGateway.sendAndWait(asCommandMessage(new MyNestedCommand("id", "state"))); ArgumentCaptor<EventMessage<?>> eventCaptor = ArgumentCaptor.forClass(EventMessage.class); verify(eventStore, times(4)).publish(eventCaptor.capture()); assertEquals(new MyAggregateCreatedEvent("id"), eventCaptor.getAllValues().get(0).getPayload()); assertEquals(new AnyCommandMatchingPatternInterceptedEvent(MyNestedCommand.class.getName()), eventCaptor.getAllValues().get(1).getPayload()); assertEquals(new AnyCommandInterceptedEvent(MyNestedCommand.class.getName()), eventCaptor.getAllValues().get(2).getPayload()); assertEquals(new MyNestedEvent("id", "state intercepted"), eventCaptor.getAllValues().get(3).getPayload()); }
/** * Open an event stream containing all domain events belonging to the given {@code aggregateIdentifier}. * <p> * The returned stream is <em>finite</em>, ending with the last known event of the aggregate. If the event store * holds no events of the given aggregate an empty stream is returned. * <p> * The default implementation invokes {@link #readEvents(String)} and then filters out events with a sequence number * smaller than {@code firstSequenceNumber}. * * @param aggregateIdentifier the identifier of the aggregate whose events to fetch * @param firstSequenceNumber the expected sequence number of the first event in the returned stream * @return a stream of all currently stored events of the aggregate */ default DomainEventStream readEvents(String aggregateIdentifier, long firstSequenceNumber) { DomainEventStream wholeStream = readEvents(aggregateIdentifier); return DomainEventStream .of(wholeStream.asStream().filter(event -> event.getSequenceNumber() >= firstSequenceNumber), wholeStream::getLastSequenceNumber); }
@SuppressWarnings("unchecked") @Test public void testInterceptorThrowingAnException() { commandGateway.sendAndWait(asCommandMessage(new CreateMyAggregateCommand("id"))); try { commandGateway.sendAndWait(asCommandMessage(new InterceptorThrowingCommand("id"))); fail("Expected exception"); } catch (InterceptorException e) { // we are expecting this } ArgumentCaptor<EventMessage<?>> eventCaptor = ArgumentCaptor.forClass(EventMessage.class); verify(eventStore, times(1)).publish(eventCaptor.capture()); assertEquals(new MyAggregateCreatedEvent("id"), eventCaptor.getAllValues().get(0).getPayload()); }
@Test public void orderInEventStore() { configuration.commandGateway().sendAndWait(command); assertEquals(expectedDescriptions(command), configuration.eventStore() .readEvents(aggregateIdentifier) .asStream() .map(Message::getPayload) .map(MyEvent.class::cast) .map(MyEvent::getDescription) .collect(Collectors.toList())); }
@SuppressWarnings("unchecked") @Test public void testInterceptor() { commandGateway.sendAndWait(asCommandMessage(new CreateMyAggregateCommand("id"))); String result = commandGateway .sendAndWait(asCommandMessage(new UpdateMyAggregateStateCommand("id", "state"))); ArgumentCaptor<EventMessage<?>> eventCaptor = ArgumentCaptor.forClass(EventMessage.class); verify(eventStore, times(3)).publish(eventCaptor.capture()); assertEquals(new MyAggregateCreatedEvent("id"), eventCaptor.getAllValues().get(0).getPayload()); assertEquals(new AnyCommandInterceptedEvent(UpdateMyAggregateStateCommand.class.getName()), eventCaptor.getAllValues().get(1).getPayload()); assertEquals(new MyAggregateStateUpdatedEvent("id", "state intercepted"), eventCaptor.getAllValues().get(2).getPayload()); assertEquals("aggregateUpdateResult", result); }
logger.debug("Aggregate {} not in first level cache, loading fresh one from Event Store", aggregateIdentifier); DomainEventStream eventStream = eventStore.readEvents(aggregateIdentifier); SnapshotTrigger trigger = snapshotTriggerDefinition.prepareTrigger(aggregateFactory.getAggregateType()); if (!eventStream.hasNext()) {