io.axoniq.eventstore.client.axon.AxonIQEventStore Maven / Gradle / Ivy
/*
* Copyright (c) 2017. AxonIQ
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.axoniq.eventstore.client.axon;
import com.google.protobuf.ByteString;
import io.axoniq.eventstore.Event;
import io.axoniq.eventstore.client.AppendEventTransaction;
import io.axoniq.eventstore.client.EventStoreClient;
import io.axoniq.eventstore.client.EventStoreConfiguration;
import io.axoniq.eventstore.client.util.FlowControllingStreamObserver;
import io.axoniq.eventstore.grpc.EventWithToken;
import io.axoniq.eventstore.grpc.GetAggregateEventsRequest;
import io.axoniq.eventstore.grpc.GetEventsRequest;
import org.axonframework.common.Assert;
import org.axonframework.eventhandling.EventMessage;
import org.axonframework.eventsourcing.DomainEventMessage;
import org.axonframework.eventsourcing.GenericDomainEventMessage;
import org.axonframework.eventsourcing.eventstore.*;
import org.axonframework.messaging.unitofwork.CurrentUnitOfWork;
import org.axonframework.serialization.MessageSerializer;
import org.axonframework.serialization.SerializedObject;
import org.axonframework.serialization.Serializer;
import org.axonframework.serialization.upcasting.event.EventUpcaster;
import org.axonframework.serialization.upcasting.event.NoOpEventUpcaster;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
import static org.axonframework.common.ObjectUtils.getOrDefault;
/**
* Axon EventStore implementation that connects to the AxonIQ Event Store Server to store and retrieve Events.
*
* @author Zoltan Altfatter
* @author Marc Gathier
* @author Allard Buijze
*/
public class AxonIQEventStore extends AbstractEventStore {
private static final Logger logger = LoggerFactory.getLogger(AxonIQEventStore.class);
/**
* Initialize the Event Store using given {@code configuration} and given {@code serializer}.
*
* The Event Store will delay creating the connection until the first activity takes place.
*
* @param configuration The configuration describing the servers to connect with and how to manage flow control
* @param serializer The serializer to serialize Event payloads with
*/
public AxonIQEventStore(EventStoreConfiguration configuration, Serializer serializer) {
this(configuration, serializer, NoOpEventUpcaster.INSTANCE);
}
/**
* Initialize the Event Store using given {@code configuration}, {@code serializer} and {@code upcasterChain}
*
* The Event Store will delay creating the connection until the first activity takes place.
*
* @param configuration The configuration describing the servers to connect with and how to manage flow control
* @param serializer The serializer to serialize Event payloads with
* @param upcasterChain The upcaster to modify received Event representations with
*/
public AxonIQEventStore(EventStoreConfiguration configuration, Serializer serializer, EventUpcaster upcasterChain) {
super(new AxonIQEventStorageEngine(serializer, upcasterChain, configuration, new EventStoreClient(configuration)));
}
@Override
public TrackingEventStream openStream(TrackingToken trackingToken) {
return storageEngine().openStream(trackingToken);
}
@Override
protected AxonIQEventStorageEngine storageEngine() {
return (AxonIQEventStorageEngine) super.storageEngine();
}
private static class AxonIQEventStorageEngine extends AbstractEventStorageEngine {
public static final int ALLOW_SNAPSHOTS_MAGIC_VALUE = -42;
private final String APPEND_EVENT_TRANSACTION = this + "/APPEND_EVENT_TRANSACTION";
private final EventUpcaster upcasterChain;
private final EventStoreConfiguration configuration;
private final EventStoreClient eventStoreClient;
private final GrpcMetaDataConverter converter;
private AxonIQEventStorageEngine(Serializer serializer,
EventUpcaster upcasterChain,
EventStoreConfiguration configuration,
EventStoreClient eventStoreClient) {
super(serializer, upcasterChain, null);
this.upcasterChain = upcasterChain;
this.configuration = configuration;
this.eventStoreClient = eventStoreClient;
this.converter = new GrpcMetaDataConverter(serializer);
}
@Override
protected void appendEvents(List extends EventMessage>> events, Serializer serializer) {
AppendEventTransaction sender;
if (CurrentUnitOfWork.isStarted()) {
sender = CurrentUnitOfWork.get().root().getOrComputeResource(APPEND_EVENT_TRANSACTION, k -> {
AppendEventTransaction appendEventTransaction = eventStoreClient.createAppendEventConnection();
CurrentUnitOfWork.get().root().onRollback(u -> appendEventTransaction.rollback(u.getExecutionResult().getExceptionResult()));
CurrentUnitOfWork.get().root().onCommit(u -> appendEventTransaction.commit());
return appendEventTransaction;
});
} else {
sender = eventStoreClient.createAppendEventConnection();
}
for (EventMessage> eventMessage : events) {
sender.append(map(eventMessage));
}
if (!CurrentUnitOfWork.isStarted()) {
sender.commit();
}
}
public Event map(EventMessage eventMessage) {
Event.Builder builder = Event.newBuilder();
if (eventMessage instanceof GenericDomainEventMessage) {
builder.setAggregateIdentifier(((GenericDomainEventMessage) eventMessage).getAggregateIdentifier())
.setAggregateSequenceNumber(((GenericDomainEventMessage) eventMessage).getSequenceNumber())
.setAggregateType(((GenericDomainEventMessage) eventMessage).getType());
}
SerializedObject serializedPayload = MessageSerializer.serializePayload(eventMessage, getSerializer(), byte[].class);
builder.setMessageIdentifier(eventMessage.getIdentifier())
.setPayload(io.axoniq.eventstore.SerializedObject.newBuilder()
.setType(serializedPayload.getType().getName())
.setRevision(getOrDefault(serializedPayload.getType().getRevision(), ""))
.setData(ByteString.copyFrom(serializedPayload.getData())))
.setTimestamp(eventMessage.getTimestamp().toEpochMilli());
eventMessage.getMetaData().forEach((k, v) -> builder.putMetaData(k, converter.convertToMetaDataValue(v)));
return builder.build();
}
@Override
protected void storeSnapshot(DomainEventMessage> snapshot, Serializer serializer) {
try {
eventStoreClient.appendSnapshot(map(snapshot)).whenComplete((c, e) -> {
if (e != null) {
logger.warn("Error occurred while creating a snapshot", e);
} else if (c != null) {
if (c.getSuccess()) {
logger.info("Snapshot created");
} else {
logger.warn("Snapshot creation failed for unknown reason. Check server logs for details.");
}
}
});
} catch (Throwable e) {
throw AxonErrorMapping.convert(e);
}
}
@Override
protected Stream extends DomainEventData>> readEventData(String aggregateIdentifier, long firstSequenceNumber) {
logger.debug("Reading events for aggregate id {}", aggregateIdentifier);
GetAggregateEventsRequest.Builder request = GetAggregateEventsRequest.newBuilder()
.setAggregateId(aggregateIdentifier);
if (firstSequenceNumber > 0) {
request.setInitialSequence(firstSequenceNumber);
} else if (firstSequenceNumber == ALLOW_SNAPSHOTS_MAGIC_VALUE) {
request.setAllowSnapshots(true);
}
try {
return eventStoreClient.listAggregateEvents(request.build()).map(GrpcBackedDomainEventData::new);
} catch (Exception e) {
throw AxonErrorMapping.convert(e);
}
}
public TrackingEventStream openStream(TrackingToken trackingToken) {
Assert.isTrue(trackingToken == null || trackingToken instanceof GlobalSequenceTrackingToken,
() -> "Invalid tracking token type. Must be GlobalSequenceTrackingToken.");
long nextToken = trackingToken == null ? 0 : ((GlobalSequenceTrackingToken) trackingToken).getGlobalIndex() + 1;
EventBuffer consumer = new EventBuffer(upcasterChain, getSerializer());
logger.info("open stream: {}", nextToken);
FlowControllingStreamObserver observer = new FlowControllingStreamObserver<>(
eventStoreClient::listEvents,
(eventWithToken, getEventsRequestStreamObserver) -> {
logger.debug("Received event with token: {}", eventWithToken.getToken());
consumer.push(eventWithToken);
},
throwable -> {
logger.error("Failed to receive events", throwable);
consumer.fail(new EventStoreException("Error while reading events from the server", throwable));
});
GetEventsRequest request = GetEventsRequest.newBuilder()
.setTrackingToken(nextToken)
.setNumberOfPermits(configuration.getInitialNrOfPermits())
.build();
GetEventsRequest nextRequest = GetEventsRequest.newBuilder().setNumberOfPermits(configuration.getNrOfNewPermits()).build();
observer.start(request, nextRequest, configuration.getInitialNrOfPermits(), configuration.getNrOfNewPermits(), configuration.getNewPermitsThreshold());
consumer.registerCloseListener((eventConsumer) -> observer.stop());
consumer.registerConsumeListener(observer::markConsumed);
return consumer;
}
@Override
public DomainEventStream readEvents(String aggregateIdentifier) {
return readEvents(aggregateIdentifier, ALLOW_SNAPSHOTS_MAGIC_VALUE);
}
@Override
protected Stream extends TrackedEventData>> readEventData(TrackingToken trackingToken, boolean mayBlock) {
throw new UnsupportedOperationException("This method is not optimized for the AxonIQ Event Store and should not be used");
}
@Override
protected Optional extends DomainEventData>> readSnapshotData(String aggregateIdentifier) {
// snapshots are automatically fetched server-side, which is faster
return Optional.empty();
}
}
}