@Override public Long resolveParameterValue(Message message) { if (message instanceof DomainEventMessage) { return ((DomainEventMessage) message).getSequenceNumber(); } return null; }
@Override public Long getLastSequenceNumber() { return event.getSequenceNumber(); } };
private DomainEventMessage<?> readNext() { DomainEventMessage<?> next = iterator.next(); this.sequenceNumber = next.getSequenceNumber(); return next; }
@Override public DomainEventStream readEvents(String aggregateIdentifier, long firstSequenceNumber) { AtomicReference<Long> sequenceNumber = new AtomicReference<>(); Stream<? extends DomainEventMessage<?>> stream = events.values().stream().filter(event -> event instanceof DomainEventMessage<?>) .map(event -> (DomainEventMessage<?>) event) .filter(event -> aggregateIdentifier.equals(event.getAggregateIdentifier()) && event.getSequenceNumber() >= firstSequenceNumber) .peek(event -> sequenceNumber.set(event.getSequenceNumber())); return DomainEventStream.of(stream, sequenceNumber::get); }
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; }
/** * Create a new DomainEventStream with events obtained from the given {@code list}. * * @param list list that serves as a source of events in the resulting DomainEventStream * @return A DomainEventStream containing all events returned by the list */ static DomainEventStream of(List<? extends DomainEventMessage<?>> list) { return list.isEmpty() ? of(Stream.empty(), () -> null) : of(list.stream(), () -> list.isEmpty() ? null : list.get(list.size() - 1).getSequenceNumber()); }
@Override public DomainEventMessage<?> next() { if (!hasNext()) { return null; } DomainEventMessage<?> next = delegate.next(); lastSequenceNumber = next.getSequenceNumber(); return next; }
@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); } } }
/** * Publish an event to the aggregate root and its entities first and external event handlers (using the given * event bus) later. * * @param msg the event message to publish */ protected void publish(EventMessage<?> msg) { if (msg instanceof DomainEventMessage) { lastKnownSequence = ((DomainEventMessage) msg).getSequenceNumber(); } inspector.publish(msg, aggregateRoot); publishOnEventBus(msg); }
/** * Construct a new event entry from a published domain event message to enable storing the event or sending it to a * remote location. * <p> * The given {@code serializer} will be used to serialize the payload and metadata in the given {@code * eventMessage}. The type of the serialized data will be the same as the given {@code contentType}. * * @param eventMessage The event message to convert to a serialized event entry * @param serializer The serializer to convert the event * @param contentType The data type of the payload and metadata after serialization */ public AbstractSnapshotEventEntry(DomainEventMessage<?> eventMessage, Serializer serializer, Class<T> contentType) { super(eventMessage, serializer, contentType); type = eventMessage.getType(); aggregateIdentifier = eventMessage.getAggregateIdentifier(); sequenceNumber = eventMessage.getSequenceNumber(); }
/** * Initialize a DomainEventMessage originating from an aggregate. * * @param trackingToken Tracking token of the event * @param delegate Delegate domain event containing other event data */ public GenericTrackedDomainEventMessage(TrackingToken trackingToken, DomainEventMessage<T> delegate) { this(trackingToken, delegate.getType(), delegate.getAggregateIdentifier(), delegate.getSequenceNumber(), delegate, delegate.getTimestamp()); }
@Override public DomainEventStream readEvents(String aggregateIdentifier, long firstSequenceNumber) { return DomainEventStream.concat(storageEngine.readEvents(aggregateIdentifier, firstSequenceNumber), DomainEventStream.of( stagedDomainEventMessages(aggregateIdentifier) .filter(m -> m.getSequenceNumber() >= firstSequenceNumber))); }
@Override public DomainEventMessage<?> next() { if (!hasNext()) { return null; } DomainEventMessage<?> next = streams.peekFirst().next(); lastSequenceNumber = next.getSequenceNumber(); return next; }
@Override public <T> Message<T> convertToOutboundMessage(EventMessage<T> event) { Map<String, Object> headers = new HashMap<>(); event.getMetaData().forEach(headers::put); headers.put(MESSAGE_ID, event.getIdentifier()); if (event instanceof DomainEventMessage) { headers.put(AGGREGATE_ID, ((DomainEventMessage) event).getAggregateIdentifier()); headers.put(AGGREGATE_SEQ, ((DomainEventMessage) event).getSequenceNumber()); headers.put(AGGREGATE_TYPE, ((DomainEventMessage) event).getType()); } return new GenericMessage<>(event.getPayload(), new SettableTimestampMessageHeaders(headers, event.getTimestamp().toEpochMilli())); }
@Override protected void storeSnapshot(DomainEventMessage<?> snapshot, Serializer serializer) { try { entityManager().merge(createSnapshotEntity(snapshot, serializer)); deleteSnapshots(snapshot.getAggregateIdentifier(), snapshot.getSequenceNumber()); if (explicitFlush) { entityManager().flush(); } } catch (Exception e) { handlePersistenceException(e, snapshot); } }
@Test @SuppressWarnings("OptionalGetWithoutIsPresent") public void testLoad_LargeAmountOfEvents() { int eventCount = testSubject.batchSize() + 10; testSubject.appendEvents(createEvents(eventCount)); assertEquals(eventCount, testSubject.readEvents(AGGREGATE).asStream().count()); assertEquals(eventCount - 1, testSubject.readEvents(AGGREGATE).asStream().reduce((a, b) -> b).get().getSequenceNumber()); }
@Test @SuppressWarnings("OptionalGetWithoutIsPresent") public void testReadPartialStream() { testSubject.appendEvents(createEvents(5)); assertEquals(2L, testSubject.readEvents(AGGREGATE, 2).asStream().findFirst().get().getSequenceNumber()); assertEquals(4L, testSubject.readEvents(AGGREGATE, 2).asStream().reduce((a, b) -> b).get().getSequenceNumber()); assertEquals(3L, testSubject.readEvents(AGGREGATE, 2).asStream().count()); }
@Override protected void storeSnapshot(DomainEventMessage<?> snapshot, Serializer serializer) { transactionManager.executeInTransaction(() -> { try { executeUpdates( getConnection(), e -> handlePersistenceException(e, snapshot), connection -> appendSnapshot(connection, snapshot, serializer), connection -> deleteSnapshots( connection, snapshot.getAggregateIdentifier(), snapshot.getSequenceNumber() ) ); } catch (ConcurrencyException e) { // Ignore duplicate key issues in snapshot. It just means a snapshot already exists } }); }
@Before public void setUp() { tokenStore = spy(new InMemoryTokenStore()); mockHandler = mock(EventMessageHandler.class); when(mockHandler.canHandle(any())).thenReturn(true); eventHandlerInvoker = SimpleEventHandlerInvoker.builder() .eventHandlers(singletonList(mockHandler)) .sequencingPolicy(event -> { if (event instanceof DomainEventMessage) { return ((DomainEventMessage) event) .getSequenceNumber(); } return event.getIdentifier(); }) .build(); eventBus = EmbeddedEventStore.builder().storageEngine(new InMemoryEventStorageEngine()).build(); // A processor config, with a policy which guarantees segmenting by using the sequence number. configureProcessor(TrackingEventProcessorConfiguration.forParallelProcessing(2)); }
@Test public void testStoreAndLoadSnapshot() { testSubject.storeSnapshot(createEvent(0)); testSubject.storeSnapshot(createEvent(1)); testSubject.storeSnapshot(createEvent(3)); testSubject.storeSnapshot(createEvent(2)); assertTrue(testSubject.readSnapshot(AGGREGATE).isPresent()); assertEquals(3, testSubject.readSnapshot(AGGREGATE).get().getSequenceNumber()); }