EventStore.java
package com.reallifedeveloper.common.application.eventstore;
import static com.reallifedeveloper.common.domain.LogUtil.removeCRLF;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import com.reallifedeveloper.common.domain.ErrorHandling;
import com.reallifedeveloper.common.domain.ObjectSerializer;
import com.reallifedeveloper.common.domain.event.DomainEvent;
/**
* An {@code EventStore} saves {@link DomainEvent DomainEvents} in a database as {@link StoredEvent StoredEvents}.
*
* @author RealLifeDeveloper
*/
public final class EventStore {
private static final Logger LOG = LoggerFactory.getLogger(EventStore.class);
private final ObjectSerializer<String> serializer;
private final StoredEventRepository repository;
/**
* Creates a new {@code EventStore} with the given serializer and repository.
*
* @param serializer the {@code DomainEventSerializer} to use to serialize and deserialize {@code DomainEvents}
* @param repository the {@code StoredEventRepository} to use to work with persisted {@code StoredEvents}
*/
@SuppressFBWarnings(value = "CRLF_INJECTION_LOGS", justification = "Logging only of objects, not user data")
public EventStore(ObjectSerializer<String> serializer, StoredEventRepository repository) {
ErrorHandling.checkNull("Arguments must not be null: serializer=%s, repository=%s", serializer, repository);
LOG.info("EventStore: serializer={}, repository={}", serializer, repository);
this.serializer = serializer;
this.repository = repository;
}
/**
* Adds a new {@link StoredEvent} representing the given {@link DomainEvent} to the event store.
*
* @param event the {@code DomainEvent} to add
* @return the saved {@code StoredEvent} representing {@code event}
*/
public StoredEvent add(DomainEvent event) {
if (LOG.isTraceEnabled()) {
LOG.trace("add: event={}", removeCRLF(event));
}
ErrorHandling.checkNull("event must not be null", event);
String serializedEvent = serializer.serialize(event);
StoredEvent storedEvent = new StoredEvent(event.getClass().getName(), serializedEvent, event.eventOccurredOn(),
event.eventVersion());
return repository.save(storedEvent);
}
/**
* Gives all {@code StoredEvents} with IDs greater than {@code storedEventId}, i.e., all events that occurred after the event with the
* given ID.
*
* @param storedEventId return all events with IDs greater than this
* @return a list of {@code StoredEvents} with IDs greater than or equal to {@code firstStoredEventId}
*/
public List<StoredEvent> allEventsSince(long storedEventId) {
LOG.trace("allEventsSince: storedEventId={}", storedEventId);
return repository.allEventsSince(storedEventId);
}
/**
* Gives all {@code StoredEvents} with IDs greater than or equal to {@code firstStoredEventId} and less than or equals to
* {@code lastStoredEventId}, i.e., all events that occurred between the events with the given IDs, inclusive.
*
* @param firstStoredEventId ID of the first {@code StoredEvent} to retrieve
* @param lastStoredEventId ID of the last {@code StoredEvent} to retrieve
* @return a list of all {@code StoredEvents} with IDs between {@code firstStoredEventId} and {@code lastStoredEventId}, inclusive
*/
public List<StoredEvent> allEventsBetween(long firstStoredEventId, long lastStoredEventId) {
LOG.trace("allEventsBetween: firstStoredEventId={}, lastStoredEventId={}", firstStoredEventId, lastStoredEventId);
return repository.allEventsBetween(firstStoredEventId, lastStoredEventId);
}
/**
* Converts a {@link StoredEvent} back to its original {@code DomainEvent}.
* <p>
* This is only guaranteed to work if the same kind of {@code EventStore}, using the same type of {@code DomainEventSerializer}, was
* used to add the {@code DomainEvent}.
*
* @param storedEvent the {@code StoredEvent} to convert
* @param <T> the type of {@code DomainEvent} to return
* @return the original {@code DomainEvent} represented by {@code storedEvent}
* @throws IllegalArgumentException if {@code storedEvent} is {@code null}
* @throws IllegalStateException if loading of the class {@code T} failed
*/
public <T extends DomainEvent> T toDomainEvent(StoredEvent storedEvent) {
if (LOG.isTraceEnabled()) {
LOG.trace("toDomainEvent: storedEvent={}", removeCRLF(storedEvent));
}
ErrorHandling.checkNull("storedEvent must not be null", storedEvent);
try {
@SuppressWarnings("unchecked")
Class<T> eventClass = (Class<T>) Class.forName(storedEvent.eventType());
return serializer.deserialize(storedEvent.eventBody(), eventClass);
} catch (ClassNotFoundException e) {
throw new IllegalStateException("Failed to load class " + storedEvent.eventType(), e);
}
}
/**
* Gives the ID of the most recently added {@code StoredEvents}.
*
* @return the ID of the most recently added {@code StoredEvent}
*/
public long lastStoredEventId() {
LOG.trace("lastStoredEventId");
return repository.lastStoredEventId().orElse(0L);
}
}