@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; } }
@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(); }
@Override @Transactional public SaveUpdateResult save(String aggregateType, List<EventTypeAndData> events, Optional<AggregateCrudSaveOptions> saveOptions) { List<EventIdTypeAndData> eventsWithIds = events.stream().map(this::toEventWithId).collect(Collectors.toList()); String entityId = saveOptions.flatMap(AggregateCrudSaveOptions::getEntityId).orElse(idGenerator.genId().asString()); Int128 entityVersion = last(eventsWithIds).getId(); try { jdbcTemplate.update(String.format("INSERT INTO %s (entity_type, entity_id, entity_version) VALUES (?, ?, ?)", entityTable), aggregateType, entityId, entityVersion.asString()); } catch (DuplicateKeyException e) { throw new EntityAlreadyExistsException(); } for (EventIdTypeAndData event : eventsWithIds) jdbcTemplate.update(String.format("INSERT INTO %s (event_id, event_type, event_data, entity_type, entity_id, triggering_event, metadata) VALUES (?, ?, ?, ?, ?, ?, ?)", eventTable), event.getId().asString(), event.getEventType(), event.getEventData(), aggregateType, entityId, saveOptions.flatMap(AggregateCrudSaveOptions::getTriggeringEvent).map(EventContext::getEventToken).orElse(null), event.getMetadata().orElse(null) ); return new SaveUpdateResult(new EntityIdVersionAndEventIds(entityId, entityVersion, eventsWithIds.stream().map(EventIdTypeAndData::getId).collect(Collectors.toList())), new PublishableEvents(aggregateType, entityId, eventsWithIds)); }
@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); }
@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()); }
Int128 eventId = saveResult.getEntityVersion(); String eventData = accountCreatedEventData; if (event.getId().equals(eventId.asString()) && eventData.equals(event.getEventData())) { foundEvent = true; } else if (event.getId().equals(otherSaveResult.getEntityIdVersionAndEventIds().getEventIds().get(0).asString())) { fail("Found event inserted into other schema");
@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 shouldReadUnprocessedEventsAfterStartup() throws InterruptedException { BlockingQueue<PublishedEvent> publishedEvents = new LinkedBlockingDeque<>(); String accountCreatedEventData = generateAccountCreatedEvent(); EntityIdVersionAndEventIds entityIdVersionAndEventIds = saveEvent(accountCreatedEventData); CdcProcessor<PublishedEvent> cdcProcessor = createCdcProcessor(); cdcProcessor.start(publishedEvents::add); waitForEvent(publishedEvents, entityIdVersionAndEventIds.getEntityVersion(), LocalDateTime.now().plusSeconds(20), accountCreatedEventData); cdcProcessor.stop(); }
@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()); }
@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); }
@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 shouldReadPublishedEvent() 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(); } }
@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()); }
@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; } }
@Test public void shouldSaveAndLoadEventMetadata() throws InterruptedException { String saveMetadata = "MyMetaData"; String updateMetadata = "MyMetaDataForUpdate"; LinkedBlockingQueue<SerializedEvent> events = new LinkedBlockingQueue<>(); aggregateEvents.subscribe("shouldSaveAndLoadEventMetadata", singletonMap(aggregateType, singleton("MyEventType")), new SubscriberOptions(), se -> { events.add(se); return new CompletableFuture<Object>(); }); EntityIdVersionAndEventIds eidv = eventStore.save(aggregateType, singletonList(new EventTypeAndData("MyEventType", "{}", Optional.of(saveMetadata))), Optional.empty()); LoadedEvents findResult = eventStore.find(aggregateType, eidv.getEntityId(), Optional.of(new AggregateCrudFindOptions())); assertEquals(Optional.of(saveMetadata), findResult.getEvents().get(0).getMetadata()); EntityIdVersionAndEventIds updateResult = eventStore.update( new EntityIdAndType(eidv.getEntityId(), aggregateType), eidv.getEntityVersion(), singletonList(new EventTypeAndData("MyEventType", "{}", Optional.of(updateMetadata))), Optional.empty()); LoadedEvents findResult2 = eventStore.find(aggregateType, eidv.getEntityId(), Optional.of(new AggregateCrudFindOptions())); assertEquals(Optional.of(saveMetadata), findResult2.getEvents().get(0).getMetadata()); assertEquals(Optional.of(updateMetadata), findResult2.getEvents().get(1).getMetadata()); assertContainsEventWithMetadata(eidv.getEventIds().get(0), saveMetadata, events); assertContainsEventWithMetadata(updateResult.getEventIds().get(0), updateMetadata, events); }
@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 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(); }
return new SaveUpdateResult(new EntityIdVersionAndEventIds(entityId, updatedEntityVersion, eventsWithIds.stream().map(EventIdTypeAndData::getId).collect(Collectors.toList())),
@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()); }
@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; } }