SerializedObject<?> payload = snapshot.serializePayload(serializer, dataType); SerializedObject<?> metaData = snapshot.serializeMetaData(serializer, dataType); final String sql = "INSERT INTO " + schema.snapshotTable() + " (" + String.join(", ", schema.eventIdentifierColumn(), schema.aggregateIdentifierColumn(), schema.metaDataColumn()) + ") VALUES (?,?,?,?,?,?,?,?,?)"; preparedStatement.setString(1, snapshot.getIdentifier()); preparedStatement.setString(2, snapshot.getAggregateIdentifier()); preparedStatement.setLong(3, snapshot.getSequenceNumber()); preparedStatement.setString(4, snapshot.getType()); writeTimestamp(preparedStatement, 5, snapshot.getTimestamp()); preparedStatement.setString(6, payload.getType().getName()); preparedStatement.setString(7, payload.getType().getRevision());
/** * Returns a {@link Predicate} for a {@link ConflictResolver} that responds affirmative if the payload of any event * in a list of unseen events is of given {@code payloadType} and matches the given {@code messageFilter}. If the * returned predicate matches an unseen event the ConflictResolver will throw an exception. * * @param payloadType the type of event payload to filter for * @param payloadFilter predicate for the payload of a single event * @return a Predicate to detect conflicts in unseen aggregate events */ @SuppressWarnings("unchecked") public static <T> Predicate<List<DomainEventMessage<?>>> payloadMatching(Class<T> payloadType, Predicate<? super T> payloadFilter) { return events -> events.stream().filter(event -> payloadType.isAssignableFrom(event.getPayloadType())) .map(event -> (T) event.getPayload()).anyMatch(payloadFilter::test); }
/** * 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()); }
/** * 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(); }
@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); }
@Override public Long resolveParameterValue(Message message) { if (message instanceof DomainEventMessage) { return ((DomainEventMessage) message).getSequenceNumber(); } return null; }
CounterChangedEvent counterChangedEvent = (CounterChangedEvent) event.getPayload(); if (counterChangedEvent.getCounter() == 1) { commandBus.dispatch(asCommandMessage(new ChangeCounterCommand(domainEvent.getAggregateIdentifier(), counterChangedEvent.getCounter() + 1)), reportErrorCallback); commandBus.dispatch(asCommandMessage(new ChangeCounterCommand(domainEvent.getAggregateIdentifier(), counterChangedEvent.getCounter() + 2)), reportErrorCallback); if (next.getPayload() instanceof CounterChangedEvent) { CounterChangedEvent event = (CounterChangedEvent) next.getPayload(); assertEquals(event.getCounter(), next.getSequenceNumber());
@Test @DirtiesContext @SuppressWarnings({"unchecked", "OptionalGetWithoutIsPresent"}) public void testStoreAndLoadEventsWithUpcaster() { EventUpcaster mockUpcasterChain = mock(EventUpcaster.class); when(mockUpcasterChain.upcast(isA(Stream.class))).thenAnswer(invocation -> { Stream<?> inputStream = (Stream) invocation.getArguments()[0]; return inputStream.flatMap(e -> Stream.of(e, e)); }); testSubject = createEngine(mockUpcasterChain); testSubject.appendEvents(createEvents(4)); List<DomainEventMessage> upcastedEvents = testSubject.readEvents(AGGREGATE).asStream().collect(toList()); assertEquals(8, upcastedEvents.size()); Iterator<DomainEventMessage> iterator = upcastedEvents.iterator(); while (iterator.hasNext()) { DomainEventMessage event1 = iterator.next(), event2 = iterator.next(); assertEquals(event1.getAggregateIdentifier(), event2.getAggregateIdentifier()); assertEquals(event1.getSequenceNumber(), event2.getSequenceNumber()); assertEquals(event1.getPayload(), event2.getPayload()); assertEquals(event1.getMetaData(), event2.getMetaData()); } }
@Override public Object getSequenceIdentifierFor(EventMessage event) { if (event instanceof DomainEventMessage) { return ((DomainEventMessage) event).getAggregateIdentifier(); } return null; } }
protected EventMessage<?>[] createEvents(String aggregateId, int startSequenceNumber, int count) { return IntStream.range(startSequenceNumber, startSequenceNumber + count) .mapToObj(sequenceNumber -> createEvent(aggregateId, sequenceNumber)) .peek(event -> serializer().ifPresent(serializer -> { event.serializePayload(serializer, byte[].class); event.serializeMetaData(serializer, byte[].class); })).toArray(EventMessage[]::new); }
@Test public void testReturnedEventMessageBehavior() { testSubject.appendEvents(createEvent().withMetaData(singletonMap("key", "value"))); DomainEventMessage<?> messageWithMetaData = testSubject.readEvents(AGGREGATE).next(); /// we make sure persisted events have the same MetaData alteration logic DomainEventMessage<?> altered = messageWithMetaData.withMetaData(singletonMap("key2", "value")); DomainEventMessage<?> combined = messageWithMetaData.andMetaData(singletonMap("key2", "value")); assertTrue(altered.getMetaData().containsKey("key2")); altered.getPayload(); assertFalse(altered.getMetaData().containsKey("key")); assertTrue(altered.getMetaData().containsKey("key2")); assertTrue(combined.getMetaData().containsKey("key")); assertTrue(combined.getMetaData().containsKey("key2")); assertNotNull(messageWithMetaData.getPayload()); assertNotNull(messageWithMetaData.getMetaData()); assertFalse(messageWithMetaData.getMetaData().isEmpty()); }
@Test @SuppressWarnings("OptionalGetWithoutIsPresent") public void testLoadTrackedEvents() throws InterruptedException { testSubject.appendEvents(createEvents(4)); assertEquals(4, testSubject.readEvents(null, false).count()); // give the clock some time to make sure the last message is really last Thread.sleep(10); DomainEventMessage<?> eventMessage = createEvent("otherAggregate", 0); testSubject.appendEvents(eventMessage); assertEquals(5, testSubject.readEvents(null, false).count()); assertEquals(eventMessage.getIdentifier(), testSubject.readEvents(null, false).reduce((a, b) -> b).get().getIdentifier()); }
/** * 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 AbstractDomainEventEntry(DomainEventMessage<?> eventMessage, Serializer serializer, Class<T> contentType) { super(eventMessage, serializer, contentType); type = eventMessage.getType(); aggregateIdentifier = eventMessage.getAggregateIdentifier(); sequenceNumber = eventMessage.getSequenceNumber(); }
@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); } }
@Override public Long getLastSequenceNumber() { return event.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()); }
CounterChangedEvent counterChangedEvent = (CounterChangedEvent) event.getPayload(); if (counterChangedEvent.getCounter() == 1) { commandBus.dispatch(asCommandMessage(new ChangeCounterCommand(domainEvent.getAggregateIdentifier(), counterChangedEvent.getCounter() + 1)), reportErrorCallback); commandBus.dispatch(asCommandMessage(new ChangeCounterCommand(domainEvent.getAggregateIdentifier(), counterChangedEvent.getCounter() + 2)), reportErrorCallback); while (storedEvents.hasNext()) { DomainEventMessage next = storedEvents.next(); if (next.getPayload() instanceof CounterChangedEvent) { CounterChangedEvent event = (CounterChangedEvent) next.getPayload(); assertEquals(event.getCounter(), next.getSequenceNumber());
@Override public String resolveParameterValue(Message message) { if (message instanceof DomainEventMessage) { return ((DomainEventMessage) message).getAggregateIdentifier(); } throw new IllegalArgumentException(); }
SerializedObject<?> payload = event.serializePayload(serializer, dataType); SerializedObject<?> metaData = event.serializeMetaData(serializer, dataType); preparedStatement.setString(1, event.getIdentifier()); preparedStatement.setString(2, event.getAggregateIdentifier()); preparedStatement.setLong(3, event.getSequenceNumber()); preparedStatement.setString(4, event.getType()); writeTimestamp(preparedStatement, 5, event.getTimestamp()); preparedStatement.setString(6, payload.getType().getName()); preparedStatement.setString(7, payload.getType().getRevision());
@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())); }