org.zalando.nakadiproducer.eventlog.impl.EventLogWriterImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of nakadi-producer Show documentation
Show all versions of nakadi-producer Show documentation
Reliable transactional Nakadi event producer
package org.zalando.nakadiproducer.eventlog.impl;
import static java.util.stream.Collectors.*;
import static org.zalando.nakadiproducer.eventlog.impl.EventDataOperation.CREATE;
import static org.zalando.nakadiproducer.eventlog.impl.EventDataOperation.DELETE;
import static org.zalando.nakadiproducer.eventlog.impl.EventDataOperation.SNAPSHOT;
import static org.zalando.nakadiproducer.eventlog.impl.EventDataOperation.UPDATE;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.zalando.fahrschein.Preconditions;
import org.zalando.nakadiproducer.eventlog.CompactionKeyExtractor;
import org.zalando.nakadiproducer.flowid.FlowIdComponent;
import org.zalando.nakadiproducer.eventlog.EventLogWriter;
import javax.transaction.Transactional;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class EventLogWriterImpl implements EventLogWriter {
private static final CompactionKeyExtractor NOOP_EXTRACTOR =
CompactionKeyExtractor.ofOptional("doesn't matter", o -> Optional.empty());
private final EventLogRepository eventLogRepository;
private final ObjectMapper objectMapper;
private final FlowIdComponent flowIdComponent;
private final Map extractorsByEventType;
private final boolean deleteAfterWrite;
public EventLogWriterImpl(EventLogRepository eventLogRepository,
ObjectMapper objectMapper,
FlowIdComponent flowIdComponent,
List keyExtractors,
boolean deleteAfterWrite) {
this.eventLogRepository = eventLogRepository;
this.objectMapper = objectMapper;
this.flowIdComponent = flowIdComponent;
this.extractorsByEventType = keyExtractors.stream()
.collect(groupingBy(
CompactionKeyExtractor::getEventType,
collectingAndThen(toList(), EventLogWriterImpl::joinCompactors)));
this.deleteAfterWrite = deleteAfterWrite;
}
/**
* Helper function (used in the constructor) to join a (non-empty) list of compaction key extractors
* (for the same event type) into a single one. If that list has just one element, it is returned.
* Otherwise, a new extractor is created whose retrieval method will just iterate through all the
* extractors, ask them for the key and returns any that is non-empty.
*
* @param list a list of extractors, non-empty.
* @return a single extractor based on the list which will return a key when any of the extractors returns one.
*/
private static CompactionKeyExtractor joinCompactors(List list) {
Preconditions.checkArgument(!list.isEmpty());
if(list.size() == 1) {
// the most common case: just one extractor per event type.
return list.get(0);
} else {
return CompactionKeyExtractor.ofOptional(list.get(0).getEventType(),
o -> list.stream()
.flatMap(ex -> ex.tryGetKeyFor(o).stream())
.findAny()
);
}
}
@Override
@Transactional
public void fireCreateEvent(final String eventType, final String dataType, final Object data) {
final EventLog eventLog = createDataEventLog(eventType, CREATE, dataType, data);
persist(eventLog);
}
@Override
@Transactional
public void fireCreateEvents(final String eventType, final String dataType, final Collection> data) {
persistSeveral(createDataEventLogs(eventType, CREATE, dataType, data));
}
@Override
@Transactional
public void fireUpdateEvent(final String eventType, final String dataType, final Object data) {
final EventLog eventLog = createDataEventLog(eventType, UPDATE, dataType, data);
persist(eventLog);
}
@Override
@Transactional
public void fireUpdateEvents(final String eventType, final String dataType, final Collection> data) {
persistSeveral(createDataEventLogs(eventType, UPDATE, dataType, data));
}
@Override
@Transactional
public void fireDeleteEvent(final String eventType, final String dataType, final Object data) {
final EventLog eventLog = createDataEventLog(eventType, DELETE, dataType, data);
persist(eventLog);
}
@Override
@Transactional
public void fireDeleteEvents(final String eventType, final String dataType, final Collection> data) {
persistSeveral(createDataEventLogs(eventType, DELETE, dataType, data));
}
@Override
@Transactional
public void fireSnapshotEvent(final String eventType, final String dataType, final Object data) {
final EventLog eventLog = createDataEventLog(eventType, SNAPSHOT, dataType, data);
persist(eventLog);
}
@Override
@Transactional
public void fireSnapshotEvents(final String eventType, final String dataType, final Collection> data) {
persistSeveral(createDataEventLogs(eventType, SNAPSHOT, dataType, data));
}
@Override
@Transactional
public void fireBusinessEvent(final String eventType, Object payload) {
final EventLog eventLog = createEventLog(eventType, payload, getCompactionKeyFor(eventType, payload));
persist(eventLog);
}
@Override
@Transactional
public void fireBusinessEvents(final String eventType, final Collection> payload) {
final Collection eventLogs = createBusinessEventLogs(eventType, payload);
persistSeveral(eventLogs);
}
private void persist(EventLog eventLog) {
persistSeveral(Collections.singleton(eventLog));
}
private void persistSeveral(Collection eventLogs) {
if(deleteAfterWrite) {
eventLogRepository.persistAndDelete(eventLogs);
} else {
eventLogRepository.persist(eventLogs);
}
}
private Collection createBusinessEventLogs(final String eventType,
final Collection> eventPayloads) {
CompactionKeyExtractor extractor = getKeyExtractorFor(eventType);
return eventPayloads.stream()
.map(payload -> createEventLog(eventType, payload,
extractor.getKeyOrNull(payload)))
.collect(toList());
}
private Collection createDataEventLogs(
final String eventType,
final EventDataOperation eventDataOperation,
final String dataType,
final Collection> data
) {
CompactionKeyExtractor extractor = getKeyExtractorFor(eventType);
String dataOp = eventDataOperation.toString();
return data.stream()
.map(payload -> createEventLog(
eventType,
new DataChangeEventEnvelope(dataOp, dataType, payload),
extractor.getKeyOrNull(payload)))
.collect(toList());
}
private EventLog createDataEventLog(String eventType, EventDataOperation dataOp, String dataType, Object data) {
return createEventLog(eventType,
new DataChangeEventEnvelope(dataOp.toString(), dataType, data),
getCompactionKeyFor(eventType, data));
}
private String getCompactionKeyFor(String eventType, Object payload) {
return getKeyExtractorFor(eventType).getKeyOrNull(payload);
}
private CompactionKeyExtractor getKeyExtractorFor(String eventType) {
return extractorsByEventType.getOrDefault(eventType, NOOP_EXTRACTOR);
}
private EventLog createEventLog(final String eventType, final Object eventPayload, String compactionKey) {
final EventLog eventLog = new EventLog();
eventLog.setEventType(eventType);
try {
eventLog.setEventBodyData(objectMapper.writeValueAsString(eventPayload));
} catch (final JsonProcessingException e) {
throw new IllegalStateException("could not map object to json: " + eventPayload.toString(), e);
}
eventLog.setCompactionKey(compactionKey);
eventLog.setFlowId(flowIdComponent.getXFlowIdValue());
return eventLog;
}
}