@Override public <T extends Aggregate<T>> EntityIdAndVersion save(Class<T> clasz, List<Event> events, Optional<SaveOptions> saveOptions) { Optional<String> serializedMetadata = saveOptions.flatMap(SaveOptions::getEventMetadata).map(JSonMapper::toJson); List<EventTypeAndData> serializedEvents = events.stream().map(event -> toEventTypeAndData(event, serializedMetadata)).collect(Collectors.toList()); try { EntityIdVersionAndEventIds result = aggregateCrud.save(clasz.getName(), serializedEvents, toAggregateCrudSaveOptions(saveOptions)); if (activityLogger.isDebugEnabled()) activityLogger.debug("Saved entity: {} {} {}", clasz.getName(), result.getEntityId(), toSerializedEventsWithIds(serializedEvents, result.getEventIds())); return result.toEntityIdAndVersion(); } catch (RuntimeException e) { activityLogger.error(String.format("Save entity failed: %s", clasz.getName()), e); throw e; } }
@Override public <T extends Aggregate<T>> EntityIdAndVersion save(Class<T> clasz, List<Event> events, Optional<SaveOptions> saveOptions) { Optional<String> serializedMetadata = saveOptions.flatMap(SaveOptions::getEventMetadata).map(JSonMapper::toJson); List<EventTypeAndData> serializedEvents = events.stream().map(event -> toEventTypeAndData(event, serializedMetadata)).collect(Collectors.toList()); try { EntityIdVersionAndEventIds result = aggregateCrud.save(clasz.getName(), serializedEvents, toAggregateCrudSaveOptions(saveOptions)); if (activityLogger.isDebugEnabled()) activityLogger.debug("Saved entity: {} {} {}", clasz.getName(), result.getEntityId(), toSerializedEventsWithIds(serializedEvents, result.getEventIds())); return result.toEntityIdAndVersion(); } catch (RuntimeException e) { activityLogger.error(String.format("Save entity failed: %s", clasz.getName()), e); throw e; } }
@Override public <T extends Aggregate<T>> CompletableFuture<EntityIdAndVersion> save(Class<T> clasz, List<Event> events, Optional<SaveOptions> saveOptions) { Optional<String> serializedMetadata = saveOptions.flatMap(so -> withSchemaMetadata(clasz, so.getEventMetadata())).map(JSonMapper::toJson); List<EventTypeAndData> serializedEvents = events.stream().map(event -> toEventTypeAndData(event, serializedMetadata)).collect(Collectors.toList()); CompletableFuture<EntityIdVersionAndEventIds> outcome = aggregateCrud.save(clasz.getName(), serializedEvents, AggregateCrudMapping.toAggregateCrudSaveOptions(saveOptions)); if (activityLogger.isDebugEnabled()) return CompletableFutureUtil.tap(outcome, (result, throwable) -> { if (throwable == null) activityLogger.debug("Saved entity: {} {} {}", clasz.getName(), result.getEntityId(), AggregateCrudMapping.toSerializedEventsWithIds(serializedEvents, result.getEventIds())); else activityLogger.error(String.format("Save entity failed: %s", clasz.getName()), throwable); }).thenApply(EntityIdVersionAndEventIds::toEntityIdAndVersion); else return outcome.thenApply(EntityIdVersionAndEventIds::toEntityIdAndVersion); }
@Override public <T extends Aggregate<T>> CompletableFuture<EntityIdAndVersion> save(Class<T> clasz, List<Event> events, Optional<SaveOptions> saveOptions) { Optional<String> serializedMetadata = saveOptions.flatMap(so -> withSchemaMetadata(clasz, so.getEventMetadata())).map(JSonMapper::toJson); List<EventTypeAndData> serializedEvents = events.stream().map(event -> toEventTypeAndData(event, serializedMetadata)).collect(Collectors.toList()); CompletableFuture<EntityIdVersionAndEventIds> outcome = aggregateCrud.save(clasz.getName(), serializedEvents, AggregateCrudMapping.toAggregateCrudSaveOptions(saveOptions)); if (activityLogger.isDebugEnabled()) return CompletableFutureUtil.tap(outcome, (result, throwable) -> { if (throwable == null) activityLogger.debug("Saved entity: {} {} {}", clasz.getName(), result.getEntityId(), AggregateCrudMapping.toSerializedEventsWithIds(serializedEvents, result.getEventIds())); else activityLogger.error(String.format("Save entity failed: %s", clasz.getName()), throwable); }).thenApply(EntityIdVersionAndEventIds::toEntityIdAndVersion); else return outcome.thenApply(EntityIdVersionAndEventIds::toEntityIdAndVersion); }
@Override public <T extends Aggregate<T>> EntityIdAndVersion update(Class<T> clasz, EntityIdAndVersion entityIdAndVersion, List<Event> events, Optional<UpdateOptions> updateOptions) { try { Optional<String> serializedEventMetadata = updateOptions.flatMap(UpdateOptions::getEventMetadata).map(JSonMapper::toJson); List<EventTypeAndData> serializedEvents = events.stream().map(event -> toEventTypeAndData(event, serializedEventMetadata)).collect(Collectors.toList()); EntityIdVersionAndEventIds result = aggregateCrud.update(new EntityIdAndType(entityIdAndVersion.getEntityId(), clasz.getName()), entityIdAndVersion.getEntityVersion(), serializedEvents, toAggregateCrudUpdateOptions(updateOptions)); if (activityLogger.isDebugEnabled()) activityLogger.debug("Updated entity: {} {} {}", clasz.getName(), result.getEntityId(), toSerializedEventsWithIds(serializedEvents, result.getEventIds())); return result.toEntityIdAndVersion(); } catch (RuntimeException e) { if (activityLogger.isDebugEnabled()) activityLogger.error(String.format("Update entity failed: %s %s", clasz.getName(), entityIdAndVersion), e); throw e; } }
@Override public <T extends Aggregate<T>> CompletableFuture<EntityIdAndVersion> update(Class<T> clasz, EntityIdAndVersion entityIdAndVersion, List<Event> events, Optional<UpdateOptions> updateOptions) { Optional<String> serializedMetadata = updateOptions.flatMap(UpdateOptions::getEventMetadata).map(JSonMapper::toJson); List<EventTypeAndData> serializedEvents = events.stream().map(event -> toEventTypeAndData(event, serializedMetadata)).collect(Collectors.toList()); CompletableFuture<EntityIdVersionAndEventIds> outcome = aggregateCrud.update(new EntityIdAndType(entityIdAndVersion.getEntityId(), clasz.getName()), entityIdAndVersion.getEntityVersion(), serializedEvents, AggregateCrudMapping.toAggregateCrudUpdateOptions(updateOptions)); if (activityLogger.isDebugEnabled()) return CompletableFutureUtil.tap(outcome, (result, throwable) -> { if (throwable == null) activityLogger.debug("Updated entity: {} {} {}", clasz.getName(), result.getEntityId(), AggregateCrudMapping.toSerializedEventsWithIds(serializedEvents, result.getEventIds())); else activityLogger.error(String.format("Update entity failed: %s %s", clasz.getName(), entityIdAndVersion), throwable); }).thenApply(EntityIdVersionAndEventIds::toEntityIdAndVersion); else return outcome.thenApply(EntityIdVersionAndEventIds::toEntityIdAndVersion); }
@Override public <T extends Aggregate<T>> CompletableFuture<EntityIdAndVersion> update(Class<T> clasz, EntityIdAndVersion entityIdAndVersion, List<Event> events, Optional<UpdateOptions> updateOptions) { Optional<String> serializedMetadata = updateOptions.flatMap(UpdateOptions::getEventMetadata).map(JSonMapper::toJson); List<EventTypeAndData> serializedEvents = events.stream().map(event -> toEventTypeAndData(event, serializedMetadata)).collect(Collectors.toList()); CompletableFuture<EntityIdVersionAndEventIds> outcome = aggregateCrud.update(new EntityIdAndType(entityIdAndVersion.getEntityId(), clasz.getName()), entityIdAndVersion.getEntityVersion(), serializedEvents, AggregateCrudMapping.toAggregateCrudUpdateOptions(updateOptions)); if (activityLogger.isDebugEnabled()) return CompletableFutureUtil.tap(outcome, (result, throwable) -> { if (throwable == null) activityLogger.debug("Updated entity: {} {} {}", clasz.getName(), result.getEntityId(), AggregateCrudMapping.toSerializedEventsWithIds(serializedEvents, result.getEventIds())); else activityLogger.error(String.format("Update entity failed: %s %s", clasz.getName(), entityIdAndVersion), throwable); }).thenApply(EntityIdVersionAndEventIds::toEntityIdAndVersion); else return outcome.thenApply(EntityIdVersionAndEventIds::toEntityIdAndVersion); }
@Test public void shouldReadNewEventsOnly() throws InterruptedException { BlockingQueue<PublishedEvent> publishedEvents = new LinkedBlockingDeque<>(); CdcProcessor<PublishedEvent> cdcProcessor = createCdcProcessor(); cdcProcessor.start(publishedEvent -> { publishedEvents.add(publishedEvent); onEventSent(publishedEvent); }); String accountCreatedEventData = generateAccountCreatedEvent(); EntityIdVersionAndEventIds entityIdVersionAndEventIds = saveEvent(accountCreatedEventData); waitForEvent(publishedEvents, entityIdVersionAndEventIds.getEntityVersion(), LocalDateTime.now().plusSeconds(10), accountCreatedEventData); cdcProcessor.stop(); publishedEvents.clear(); cdcProcessor.start(publishedEvent -> { publishedEvents.add(publishedEvent); onEventSent(publishedEvent); }); List<String> excludedIds = entityIdVersionAndEventIds.getEventIds().stream().map(Int128::asString).collect(Collectors.toList()); accountCreatedEventData = generateAccountCreatedEvent(); entityIdVersionAndEventIds = updateEvent(entityIdVersionAndEventIds.getEntityId(), entityIdVersionAndEventIds.getEntityVersion(), accountCreatedEventData); waitForEventExcluding(publishedEvents, entityIdVersionAndEventIds.getEntityVersion(), LocalDateTime.now().plusSeconds(10), accountCreatedEventData, excludedIds); cdcProcessor.stop(); }
@Test public void testFind() { EventTypeAndData eventTypeAndData = new EventTypeAndData(testEventType, testEventData, Optional.empty()); SaveUpdateResult saveUpdateResult = eventuateJdbcAccess.save(testAggregate, Collections.singletonList(eventTypeAndData), Optional.empty()); LoadedEvents loadedEvents = eventuateJdbcAccess.find(testAggregate, saveUpdateResult.getEntityIdVersionAndEventIds().getEntityId(), Optional.empty()); Assert.assertEquals(1, loadedEvents.getEvents().size()); }
@Test public void updateShouldCompleteWithOptimisticLockingException() throws ExecutionException, InterruptedException { EntityIdVersionAndEventIds eidv = eventStore.save(aggregateType, singletonList(new EventTypeAndData("MyEventType", "{}", Optional.empty())), Optional.of(new AggregateCrudSaveOptions().withEventContext(ectx))); shouldCompletedExceptionally(OptimisticLockingException.class, () -> eventStore.update(new EntityIdAndType(eidv.getEntityId(), aggregateType), new Int128(0,0), singletonList(new EventTypeAndData("MyEventType", "{}", Optional.empty())), Optional.of(new AggregateCrudUpdateOptions()))); }
@Test public void shouldSendPublishedEventsToKafka() { CdcDataPublisher<PublishedEvent> cdcDataPublisher = createCdcKafkaPublisher(); cdcDataPublisher.start(); cdcProcessor.start(cdcDataPublisher::handleEvent); String accountCreatedEventData = generateAccountCreatedEvent(); EntityIdVersionAndEventIds entityIdVersionAndEventIds = saveEvent(accountCreatedEventData); KafkaConsumer<String, String> consumer = createConsumer(eventuateKafkaConfigurationProperties.getBootstrapServers()); consumer.partitionsFor(getEventTopicName()); consumer.subscribe(Collections.singletonList(getEventTopicName())); waitForEventInKafka(consumer, entityIdVersionAndEventIds.getEntityId(), LocalDateTime.now().plusSeconds(40)); cdcDataPublisher.stop(); }
@Test public void findShouldCompleteWithDuplicateTriggeringEventException() throws ExecutionException, InterruptedException { EntityIdVersionAndEventIds eidv = eventStore.save(aggregateType, singletonList(new EventTypeAndData("MyEventType", "{}", Optional.empty())), Optional.of(new AggregateCrudSaveOptions().withEventContext(ectx))); shouldCompletedExceptionally(DuplicateTriggeringEventException.class, () -> eventStore.find(aggregateType, eidv.getEntityId(), Optional.of(new AggregateCrudFindOptions().withTriggeringEvent(ectx)))); }
@Test public void shouldSaveAndLoadSnapshot() { EntityIdVersionAndEventIds eidv = eventStore.save(aggregateType, singletonList(new EventTypeAndData("MyEventType", "{}", Optional.empty())), Optional.of(new AggregateCrudSaveOptions().withEventContext(ectx))); EntityIdVersionAndEventIds updateResult = eventStore.update( new EntityIdAndType(eidv.getEntityId(), aggregateType), eidv.getEntityVersion(), singletonList(new EventTypeAndData("MyEventType", "{}", Optional.empty())), Optional.of(new AggregateCrudUpdateOptions().withSnapshot(new SerializedSnapshot("X", "Y")))); LoadedEvents findResult = eventStore.find(aggregateType, eidv.getEntityId(), Optional.of(new AggregateCrudFindOptions())); assertTrue(findResult.getSnapshot().isPresent()); assertTrue(findResult.getEvents().isEmpty()); }
@Test public void testUpdate() { EventTypeAndData eventTypeAndData = new EventTypeAndData(testEventType, testEventData, Optional.empty()); SaveUpdateResult saveUpdateResult = eventuateJdbcAccess.save(testAggregate, Collections.singletonList(eventTypeAndData), Optional.empty()); EntityIdAndType entityIdAndType = new EntityIdAndType(saveUpdateResult.getEntityIdVersionAndEventIds().getEntityId(), testAggregate); eventTypeAndData = new EventTypeAndData("testEventType2", "testEventData2", Optional.empty()); eventuateJdbcAccess.update(entityIdAndType, saveUpdateResult.getEntityIdVersionAndEventIds().getEntityVersion(), Collections.singletonList(eventTypeAndData), Optional.of(new AggregateCrudUpdateOptions(Optional.empty(), Optional.of(new SerializedSnapshot("", ""))))); List<Map<String, Object>> events = jdbcTemplate.queryForList(readAllEventsSql()); Assert.assertEquals(2, events.size()); List<Map<String, Object>> entities = jdbcTemplate.queryForList(readAllEntitiesSql()); Assert.assertEquals(1, entities.size()); List<Map<String, Object>> snapshots = jdbcTemplate.queryForList(readAllSnapshots()); Assert.assertEquals(1, snapshots.size()); LoadedEvents loadedEvents = eventuateJdbcAccess.find(testAggregate, saveUpdateResult.getEntityIdVersionAndEventIds().getEntityId(), Optional.empty()); Assert.assertTrue(loadedEvents.getSnapshot().isPresent()); }
@Test @PactVerification(fragment="create") public void shouldCreate() throws URISyntaxException, ExecutionException, InterruptedException { EntityIdVersionAndEventIds saveResult = save().get(); assertEquals(RequestResponseJsonObjects.ENTITY_ID, saveResult.getEntityId()); assertEquals(new Int128(1,2), saveResult.getEntityVersion()); //assertEquals(Collections.singletonList(new Int128(3,4)), saveResult.getEventIds()); }
@Test @PactVerification(fragment="update") public void shouldUpdate() throws URISyntaxException, ExecutionException, InterruptedException { EntityIdVersionAndEventIds updateResult = update().get(); assertEquals(RequestResponseJsonObjects.ENTITY_ID, updateResult.getEntityId()); assertEquals(new Int128(1,2), updateResult.getEntityVersion()); //assertEquals(Collections.singletonList(new Int128(3,4)), updateResult.getEventIds()); }
@Test @PactVerification(fragment="createWithId") public void shouldCreateWithId() throws URISyntaxException, ExecutionException, InterruptedException { EntityIdVersionAndEventIds saveResult = saveWithId().get(); assertEquals(RequestResponseJsonObjects.createId, saveResult.getEntityId()); assertEquals(new Int128(1,2), saveResult.getEntityVersion()); //assertEquals(Collections.singletonList(new Int128(3,4)), saveResult.getEventIds()); }
@Test @PactVerification(fragment="updateWithTriggeringEvent") public void shouldUpdateWithTriggeringEvent() throws URISyntaxException, ExecutionException, InterruptedException { EntityIdVersionAndEventIds updateResult = updateWithEventContext().get(); assertEquals(RequestResponseJsonObjects.ENTITY_ID, updateResult.getEntityId()); assertEquals(new Int128(1,2), updateResult.getEntityVersion()); //assertEquals(Collections.singletonList(new Int128(3,4)), updateResult.getEventIds()); }
@Test public void shouldGetEvents() throws InterruptedException { MySqlBinaryLogClient<PublishedEvent> mySqlBinaryLogClient = makeMySqlBinaryLogClient(); try { BlockingQueue<PublishedEvent> publishedEvents = new LinkedBlockingDeque<>(); mySqlBinaryLogClient.start(Optional.empty(), publishedEvents::add); String accountCreatedEventData = generateAccountCreatedEvent(); EntityIdVersionAndEventIds saveResult = saveEvent(accountCreatedEventData); String accountDebitedEventData = generateAccountDebitedEvent(); EntityIdVersionAndEventIds updateResult = updateEvent(saveResult.getEntityId(), saveResult.getEntityVersion(), accountDebitedEventData); // Wait for 10 seconds LocalDateTime deadline = LocalDateTime.now().plusSeconds(10); waitForEvent(publishedEvents, saveResult.getEntityVersion(), deadline, accountCreatedEventData); waitForEvent(publishedEvents, updateResult.getEntityVersion(), deadline, accountDebitedEventData); } finally { mySqlBinaryLogClient.stop(); } }
@Test public void shouldGetEvents() throws InterruptedException{ PostgresWalClient<PublishedEvent> postgresWalClient = new PostgresWalClient<>(postgresWalMessageParser, dataSourceURL, dbUserName, dbPassword, eventuateConfigurationProperties.getBinlogConnectionTimeoutInMilliseconds(), eventuateConfigurationProperties.getMaxAttemptsForBinlogConnection(), eventuateConfigurationProperties.getPostgresWalIntervalInMilliseconds(), eventuateConfigurationProperties.getPostgresReplicationStatusIntervalInMilliseconds(), eventuateConfigurationProperties.getPostgresReplicationSlotName()); BlockingQueue<PublishedEvent> publishedEvents = new LinkedBlockingDeque<>(); postgresWalClient.start(Optional.empty(), publishedEvents::add); String accountCreatedEventData = generateAccountCreatedEvent(); EntityIdVersionAndEventIds saveResult = saveEvent(accountCreatedEventData); String accountDebitedEventData = generateAccountDebitedEvent(); EntityIdVersionAndEventIds updateResult = updateEvent(saveResult.getEntityId(), saveResult.getEntityVersion(), accountDebitedEventData); // Wait for 10 seconds LocalDateTime deadline = LocalDateTime.now().plusSeconds(10); waitForEvent(publishedEvents, saveResult.getEntityVersion(), deadline, accountCreatedEventData); waitForEvent(publishedEvents, updateResult.getEntityVersion(), deadline, accountDebitedEventData); postgresWalClient.stop(); }