
io.fluxcapacitor.javaclient.FluxCapacitor Maven / Gradle / Ivy
Show all versions of java-client Show documentation
/*
* Copyright (c) Flux Capacitor IP B.V. or its affiliates. All Rights Reserved.
*
* 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.fluxcapacitor.javaclient;
import io.fluxcapacitor.common.Guarantee;
import io.fluxcapacitor.common.MessageType;
import io.fluxcapacitor.common.Registration;
import io.fluxcapacitor.common.ThrowingConsumer;
import io.fluxcapacitor.common.ThrowingFunction;
import io.fluxcapacitor.common.api.Metadata;
import io.fluxcapacitor.common.api.search.SearchQuery;
import io.fluxcapacitor.common.application.PropertySource;
import io.fluxcapacitor.common.caching.Cache;
import io.fluxcapacitor.javaclient.common.IdentityProvider;
import io.fluxcapacitor.javaclient.common.Message;
import io.fluxcapacitor.javaclient.common.UuidFactory;
import io.fluxcapacitor.javaclient.common.serialization.DeserializingMessage;
import io.fluxcapacitor.javaclient.common.serialization.FilterContent;
import io.fluxcapacitor.javaclient.common.serialization.Serializer;
import io.fluxcapacitor.javaclient.configuration.DefaultFluxCapacitor;
import io.fluxcapacitor.javaclient.configuration.client.Client;
import io.fluxcapacitor.javaclient.configuration.spring.FluxCapacitorSpringConfig;
import io.fluxcapacitor.javaclient.modeling.Aggregate;
import io.fluxcapacitor.javaclient.modeling.Entity;
import io.fluxcapacitor.javaclient.modeling.EntityId;
import io.fluxcapacitor.javaclient.modeling.Id;
import io.fluxcapacitor.javaclient.persisting.eventsourcing.EventStore;
import io.fluxcapacitor.javaclient.persisting.eventsourcing.SnapshotStore;
import io.fluxcapacitor.javaclient.persisting.keyvalue.KeyValueStore;
import io.fluxcapacitor.javaclient.persisting.repository.AggregateRepository;
import io.fluxcapacitor.javaclient.persisting.search.DocumentStore;
import io.fluxcapacitor.javaclient.persisting.search.Search;
import io.fluxcapacitor.javaclient.persisting.search.Searchable;
import io.fluxcapacitor.javaclient.publishing.CommandGateway;
import io.fluxcapacitor.javaclient.publishing.ErrorGateway;
import io.fluxcapacitor.javaclient.publishing.EventGateway;
import io.fluxcapacitor.javaclient.publishing.MetricsGateway;
import io.fluxcapacitor.javaclient.publishing.QueryGateway;
import io.fluxcapacitor.javaclient.publishing.ResultGateway;
import io.fluxcapacitor.javaclient.publishing.WebRequestGateway;
import io.fluxcapacitor.javaclient.publishing.correlation.CorrelationDataProvider;
import io.fluxcapacitor.javaclient.publishing.correlation.DefaultCorrelationDataProvider;
import io.fluxcapacitor.javaclient.scheduling.Scheduler;
import io.fluxcapacitor.javaclient.tracking.Tracker;
import io.fluxcapacitor.javaclient.tracking.Tracking;
import io.fluxcapacitor.javaclient.tracking.handling.HandleCommand;
import io.fluxcapacitor.javaclient.tracking.handling.LocalHandler;
import io.fluxcapacitor.javaclient.tracking.handling.Request;
import io.fluxcapacitor.javaclient.tracking.handling.authentication.User;
import io.fluxcapacitor.javaclient.tracking.handling.authentication.UserProvider;
import lombok.SneakyThrows;
import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.stream.Stream;
import static io.fluxcapacitor.common.MessageType.EVENT;
import static io.fluxcapacitor.common.MessageType.NOTIFICATION;
import static java.util.Arrays.stream;
/**
* High-level client for Flux Capacitor. If you are using anything other than this to interact with the service at
* runtime you're probably doing it wrong.
*
* To start handling messages build an instance of this API and invoke {@link #registerHandlers}.
*
* Once you are handling messages you can simply use the static methods provided (e.g. to publish messages etc). In
* those cases it is not necessary to inject an instance of this API. This minimizes the need for dependencies in your
* functional classes and maximally cashes in on location transparency.
*
* To build an instance of this client check out {@link DefaultFluxCapacitor}.
*/
public interface FluxCapacitor extends AutoCloseable {
/**
* Flux Capacitor instance set by the current application. Used as a fallback when no threadlocal instance was set.
* This is added as a convenience for applications that never have more than one than FluxCapacitor instance which
* will be the case for nearly all applications. On application startup simply fill this application instance.
*/
AtomicReference applicationInstance = new AtomicReference<>();
/**
* Flux Capacitor instance bound to the current thread. Normally there's only one FluxCapacitor client per
* application. Before messages are passed to message handlers the FluxCapacitor client binds itself to this field.
* By doing so message handlers can interact with Flux Capacitor without injecting any dependencies.
*/
ThreadLocal instance = new ThreadLocal<>();
/**
* Returns the Flux Capacitor instance bound to the current thread or else set by the current application. Throws an
* exception if no instance was registered.
*/
static FluxCapacitor get() {
return Optional.ofNullable(instance.get())
.orElseGet(() -> Optional.ofNullable(applicationInstance.get())
.orElseThrow(() -> new IllegalStateException("FluxCapacitor instance not set")));
}
/**
* Returns the FluxCapacitor client bound to the current thread or else set by the current application as Optional.
* Returns an empty Optional if no instance was registered.
*/
static Optional getOptionally() {
FluxCapacitor result = instance.get();
return result == null ? Optional.ofNullable(applicationInstance.get()) : Optional.of(result);
}
/**
* Gets the clock of the current FluxCapacitor instance (obtained via {@link #getOptionally()}). If there is no
* current instance the system's UTC clock is returned.
*/
static Clock currentClock() {
return getOptionally().map(FluxCapacitor::clock).orElseGet(Clock::systemUTC);
}
/**
* Gets the time according to the current FluxCapacitor clock (obtained via {@link #currentClock()}). If there is no
* current FluxCapacitor instance the system's UTC time is returned.
*/
static Instant currentTime() {
return currentClock().instant();
}
/**
* Gets the {@link IdentityProvider} of the current FluxCapacitor to generate a unique identifier. If there is no
* current FluxCapacitor instance a new UUID is generated.
*/
static String generateId() {
return currentIdentityProvider().nextFunctionalId();
}
/**
* Fetches the {@link IdentityProvider} of the current FluxCapacitor. If there is no current FluxCapacitor instance
* a new UUID factory is generated.
*/
static IdentityProvider currentIdentityProvider() {
return getOptionally().map(FluxCapacitor::identityProvider).orElseGet(UuidFactory::new);
}
/**
* Gets the current correlation data, which by default depends on the current {@link Client}, {@link Tracker} and
* {@link DeserializingMessage}
*/
static Map currentCorrelationData() {
return getOptionally().map(FluxCapacitor::correlationDataProvider).orElse(
DefaultCorrelationDataProvider.INSTANCE).getCorrelationData();
}
/**
* Publishes the given application event. The event may be an instance of a {@link Message} in which case it will be
* published as is. Otherwise the event is published using the passed value as payload without additional metadata.
*
* Note that the published event will not be available for event sourcing as it is does not belong to any
* aggregate.
*
* @see #aggregateRepository() if you're interested in publishing events that belong to an aggregate.
*/
static void publishEvent(Object event) {
get().eventGateway().publish(event);
}
/**
* Publishes an event with given payload and metadata.
*
* @see #publishEvent(Object) for more info
*/
static void publishEvent(Object payload, Metadata metadata) {
get().eventGateway().publish(payload, metadata);
}
/**
* Publishes given application events. The events may be instances of {@link Message} in which case they will be
* published as is. Otherwise, the events are published using the passed value as payload without additional
* metadata.
*
* Note that the published events will not be available for event sourcing as they do not belong to any
* aggregate.
*
* @see #aggregateRepository() if you're interested in publishing events that belong to an aggregate.
*/
static void publishEvents(Object... events) {
get().eventGateway().publish(events);
}
/**
* Sends the given command and doesn't wait for a result. The command may be an instance of a {@link Message} in
* which case it will be sent as is. Otherwise the command is published using the passed value as payload without
* additional metadata.
*
* @see #sendCommand(Object) to send a command and inspect its result asynchronously
*/
static void sendAndForgetCommand(Object command) {
get().commandGateway().sendAndForget(command);
}
/**
* Sends the given commands and doesn't wait for results. Commands may be an instance of a {@link Message} in which
* case it will be sent as is. Otherwise, the commands are published using the passed value as payload without
* additional metadata.
*
* @see #sendCommands(Object...) to send commands and inspect their results asynchronously
*/
static void sendAndForgetCommands(Object... commands) {
get().commandGateway().sendAndForget(commands);
}
/**
* Sends a command with given payload and metadata and don't wait for a result.
*
* @see #sendCommand(Object, Metadata) to send a command and inspect its result
*/
static void sendAndForgetCommand(Object payload, Metadata metadata) {
get().commandGateway().sendAndForget(payload, metadata);
}
/**
* Sends a command with given payload and metadata and don't wait for a result. With a guarantee the method will
* wait for the command itself to be sent or stored.
*
* @see #sendCommand(Object, Metadata) to send a command and inspect its result
*/
static void sendAndForgetCommand(Object payload, Metadata metadata, Guarantee guarantee) {
get().commandGateway().sendAndForget(payload, metadata, guarantee);
}
/**
* Sends the given command and returns a future that will be completed with the command's result. The command may be
* an instance of a {@link Message} in which case it will be sent as is. Otherwise the command is published using
* the passed value as payload without additional metadata.
*/
static CompletableFuture sendCommand(Object command) {
return get().commandGateway().send(command);
}
/**
* Sends the given command and returns a future that will be completed with the command's result. The command may be
* an instance of a {@link Message} in which case it will be sent as is. Otherwise the command is published using
* the passed value as payload without additional metadata.
*
* The return type is determined by the given command.
*/
static CompletableFuture sendCommand(Request command) {
return get().commandGateway().send(command);
}
/**
* Sends the given commands and returns a list of futures that will be completed with the commands' results. The
* commands may be instances of a {@link Message} in which case they will be sent as is. Otherwise, the commands are
* published using the passed values as payload without additional metadata.
*/
static List> sendCommands(Object... commands) {
return get().commandGateway().send(commands);
}
/**
* Sends a command with given payload and metadata and returns a future that will be completed with the command's
* result.
*/
static CompletableFuture sendCommand(Object payload, Metadata metadata) {
return get().commandGateway().send(payload, metadata);
}
/**
* Sends a command with given payload and metadata and returns a future that will be completed with the command's
* result.
*
* The return type is determined by the given command.
*/
static CompletableFuture sendCommand(Request payload, Metadata metadata) {
return get().commandGateway().send(payload, metadata);
}
/**
* Sends the given command and returns the command's result. The command may be an instance of a {@link Message} in
* which case it will be sent as is. Otherwise, the command is published using the passed value as payload without
* additional metadata.
*/
static R sendCommandAndWait(Object command) {
return get().commandGateway().sendAndWait(command);
}
/**
* Sends the given command and returns the command's result. The command may be an instance of a {@link Message} in
* which case it will be sent as is. Otherwise, the command is published using the passed value as payload without
* additional metadata.
*
* The return type is determined by the given command.
*/
static R sendCommandAndWait(Request command) {
return get().commandGateway().sendAndWait(command);
}
/**
* Sends a command with given payload and metadata and returns a future that will be completed with the command's
* result.
*/
static R sendCommandAndWait(Object payload, Metadata metadata) {
return get().commandGateway().sendAndWait(payload, metadata);
}
/**
* Sends a command with given payload and metadata and returns a future that will be completed with the command's
* result.
*
* The return type is determined by the given command.
*/
static R sendCommandAndWait(Request payload, Metadata metadata) {
return get().commandGateway().sendAndWait(payload, metadata);
}
/**
* Sends the given query and returns a future that will be completed with the query's result. The query may be an
* instance of a {@link Message} in which case it will be sent as is. Otherwise, the query is published using the
* passed value as payload without additional metadata.
*/
static CompletableFuture query(Object query) {
return get().queryGateway().send(query);
}
/**
* Sends the given query and returns a future that will be completed with the query's result. The query may be an
* instance of a {@link Message} in which case it will be sent as is. Otherwise, the query is published using the
* passed value as payload without additional metadata.
*
* The return type is determined by the given query.
*/
static CompletableFuture query(Request query) {
return get().queryGateway().send(query);
}
/**
* Sends a query with given payload and metadata and returns a future that will be completed with the query's
* result.
*/
static CompletableFuture query(Object payload, Metadata metadata) {
return get().queryGateway().send(payload, metadata);
}
/**
* Sends a query with given payload and metadata and returns a future that will be completed with the query's
* result.
*
* The return type is determined by the given query.
*/
static CompletableFuture query(Request payload, Metadata metadata) {
return get().queryGateway().send(payload, metadata);
}
/**
* Sends the given query and returns the query's result. The query may be an instance of a {@link Message} in which
* case it will be sent as is. Otherwise, the query is published using the passed value as payload without
* additional metadata.
*/
static R queryAndWait(Object query) {
return get().queryGateway().sendAndWait(query);
}
/**
* Sends the given query and returns the query's result. The query may be an instance of a {@link Message} in which
* case it will be sent as is. Otherwise, the query is published using the passed value as payload without
* additional metadata.
*
* The return type is determined by the given query.
*/
static R queryAndWait(Request query) {
return get().queryGateway().sendAndWait(query);
}
/**
* Sends a query with given payload and metadata and returns the query's result.
*/
static R queryAndWait(Object payload, Metadata metadata) {
return get().queryGateway().sendAndWait(payload, metadata);
}
/**
* Sends a query with given payload and metadata and returns the query's result.
*
* The return type is determined by the given query.
*/
static R queryAndWait(Request payload, Metadata metadata) {
return get().queryGateway().sendAndWait(payload, metadata);
}
/**
* Starts a new periodic schedule, returning the schedule's id. The {@code schedule} parameter may be an instance of
* a {@link Message} or the schedule payload. If the payload is not annotated with
* {@link io.fluxcapacitor.javaclient.scheduling.Periodic} an {@link IllegalArgumentException} is thrown.
*
* @see io.fluxcapacitor.javaclient.scheduling.Periodic
*/
static String schedulePeriodic(Object schedule) {
return get().scheduler().schedulePeriodic(schedule);
}
/**
* Starts a new periodic schedule using given schedule id. The {@code schedule} parameter may be an instance of a
* {@link Message} or the schedule payload. If the payload is not annotated with
* {@link io.fluxcapacitor.javaclient.scheduling.Periodic} an {@link IllegalArgumentException} is thrown.
*
* @see io.fluxcapacitor.javaclient.scheduling.Periodic
*/
static void schedulePeriodic(Object schedule, String scheduleId) {
get().scheduler().schedulePeriodic(schedule, scheduleId);
}
/**
* Schedules a message for the given timestamp, returning the schedule's id. The {@code schedule} parameter may be
* an instance of a {@link Message} in which case it will be scheduled as is. Otherwise, the schedule is published
* using the passed value as payload without additional metadata.
*/
static String schedule(Object schedule, Instant deadline) {
return get().scheduler().schedule(schedule, deadline);
}
/**
* Schedules a message with given {@code scheduleId} for the given timestamp. The {@code schedule} parameter may be
* an instance of a {@link Message} in which case it will be scheduled as is. Otherwise, the schedule is published
* using the passed value as payload without additional metadata.
*/
static void schedule(Object schedule, String scheduleId, Instant deadline) {
get().scheduler().schedule(schedule, scheduleId, deadline);
}
/**
* Schedules a message after the given delay, returning the schedule's id. The {@code schedule} parameter may be an
* instance of a {@link Message} in which case it will be scheduled as is. Otherwise, the schedule is published
* using the passed value as payload without additional metadata.
*/
static String schedule(Object schedule, Duration delay) {
return get().scheduler().schedule(schedule, delay);
}
/**
* Schedules a message with given {@code scheduleId} after given delay. The {@code schedule} parameter may be an
* instance of a {@link Message} in which case it will be scheduled as is. Otherwise, the schedule is published
* using the passed value as payload without additional metadata.
*/
static void schedule(Object schedule, String scheduleId, Duration delay) {
get().scheduler().schedule(schedule, scheduleId, delay);
}
/**
* Schedules a command for the given timestamp, returning the command schedule's id. The {@code command} parameter
* may be an instance of a {@link Message} in which case it will be scheduled as is. Otherwise, the command is
* scheduled using the passed value as payload without additional metadata.
*/
static String scheduleCommand(Object command, Instant deadline) {
return get().scheduler().scheduleCommand(command, deadline);
}
/**
* Schedules a command with given {@code scheduleId} for the given timestamp. The {@code command} parameter may be
* an instance of a {@link Message} in which case it will be scheduled as is. Otherwise, the command is published
* using the passed value as payload without additional metadata.
*/
static void scheduleCommand(Object command, String scheduleId, Instant deadline) {
get().scheduler().scheduleCommand(command, scheduleId, deadline);
}
/**
* Schedules a command after given delay, returning the command schedule's id. The {@code command} parameter may be
* an instance of a {@link Message} in which case it will be scheduled as is. Otherwise, the command is scheduled
* using the passed value as payload without additional metadata.
*/
static String scheduleCommand(Object command, Duration delay) {
return get().scheduler().scheduleCommand(command, delay);
}
/**
* Schedules a command with given {@code scheduleId} after given delay. The {@code command} parameter may be an
* instance of a {@link Message} in which case it will be scheduled as is. Otherwise, the command is published using
* the passed value as payload without additional metadata.
*/
static void scheduleCommand(Object command, String scheduleId, Duration delay) {
get().scheduler().scheduleCommand(command, scheduleId, delay);
}
/**
* Cancels the schedule with given {@code scheduleId}.
*/
static void cancelSchedule(String scheduleId) {
get().scheduler().cancelSchedule(scheduleId);
}
/**
* Publishes a metrics event. The parameter may be an instance of a {@link Message} in which case it will be sent as
* is. Otherwise the metrics event is published using the passed value as payload without additional metadata.
*
* Metrics events can be published in any form to log custom performance metrics about an application.
*/
static void publishMetrics(Object metrics) {
get().metricsGateway().publish(metrics);
}
/**
* Publishes a metrics event with given payload and metadata. Metrics events can be published in any form to log
* custom performance metrics about an application.
*/
static void publishMetrics(Object payload, Metadata metadata) {
get().metricsGateway().publish(payload, metadata, Guarantee.NONE);
}
/**
* Loads the aggregate root of type {@code } with given aggregateId.
*
* If the aggregate is loaded while handling an event of the aggregate, the returned Aggregate will automatically be
* played back to the event currently being handled. Otherwise, the most recent state of the aggregate is loaded.
*
* @see Aggregate for more info on how to define an event sourced aggregate root
*/
static Entity loadAggregate(Id aggregateId) {
return playbackToHandledEvent(get().aggregateRepository().load(aggregateId));
}
/**
* Loads the aggregate root of type {@code } with given aggregateId.
*
* If the aggregate is loaded while handling an event of the aggregate, the returned Aggregate will automatically be
* played back to the event currently being handled. Otherwise, the most recent state of the aggregate is loaded.
*
* @see Aggregate for more info on how to define an event sourced aggregate root
*/
static Entity loadAggregate(Object aggregateId, Class aggregateType) {
return playbackToHandledEvent(get().aggregateRepository().load(aggregateId, aggregateType));
}
/**
* Loads the aggregate root of type {@code } that currently contains the entity with given entityId. If no such
* aggregate exists an empty aggregate root is returned with given {@code defaultType} as its type.
*
* This method can also be used if the entity is the aggregate root (aggregateId is equal to entityId). If the
* entity is associated with more than one aggregate the behavior of this method is unpredictable, though the
* default behavior is that any one of the associated aggregates is returned.
*
* If the aggregate is loaded while handling an event of the aggregate, the returned Aggregate will automatically be
* played back to the event currently being handled. Otherwise, the most recent state of the aggregate is loaded.
*
* @see Aggregate for more info on how to define an event sourced aggregate root
*/
static Entity loadAggregateFor(Object entityId, Class> defaultType) {
return playbackToHandledEvent(get().aggregateRepository().loadFor(entityId, defaultType));
}
/**
* Loads the aggregate root that currently contains the entity with given entityId. If no such aggregate exists an
* empty aggregate root is returned of type {@code Object}. In that case be aware that applying events to create the
* aggregate may yield an undesired result; to prevent this use {@link #loadAggregateFor(Object, Class)}.
*
* This method can also be used if the entity is the aggregate root (aggregateId is equal to entityId). If the
* entity is associated with more than one aggregate the behavior of this method is unpredictable, though the
* default behavior is that any one of the associated aggregates is returned.
*
* If the aggregate is loaded while handling an event of the aggregate, the returned Aggregate will automatically be
* played back to the event currently being handled. Otherwise, the most recent state of the aggregate is loaded.
*
* @see Aggregate for more info on how to define an event sourced aggregate root
*/
static Entity loadAggregateFor(Object entityId) {
return loadAggregateFor(entityId, Object.class);
}
/**
* Loads the entity with given id. If the entity is not associated with any aggregate yet, a new aggregate root is
* loaded with the entityId as aggregate identifier. In case multiple entities are associated with the given
* entityId the most recent entity is returned.
*
* If the entity is loaded while handling an event its aggregate, the returned entity will automatically be played
* back to the event currently being handled. Otherwise, the most recent state of the entity is loaded.
*/
@SuppressWarnings("unchecked")
static Entity loadEntity(Object entityId) {
return (Entity) loadAggregateFor(entityId).getEntity(entityId)
.orElseGet(() -> loadAggregate(entityId.toString(), Object.class));
}
/**
* Loads the entity with given id. If the entity is not associated with any aggregate yet, a new aggregate root is
* loaded with the entityId as aggregate identifier. In case multiple entities are associated with the given
* entityId the most recent entity is returned.
*
* If the entity is loaded while handling an event its aggregate, the returned entity will automatically be played
* back to the event currently being handled. Otherwise, the most recent state of the entity is loaded.
*/
static Entity loadEntity(Id entityId) {
return loadAggregateFor(entityId).getEntity(entityId).orElseGet(() -> loadAggregate(entityId));
}
/**
* Loads the current entity value for given entity id. Entity may be the aggregate root or any ancestral entity. If
* no such entity exists an empty optional is returned.
*
* If the entity is loaded while handling an event its aggregate, the returned entity will automatically be played
* back to the event currently being handled. Otherwise, the most recent state of the entity is loaded.
*/
@SuppressWarnings("unchecked")
static Optional loadEntityValue(Object entityId) {
return loadAggregateFor(entityId).getEntity(entityId).map(e -> (T) e.get());
}
/**
* Loads the current entity value for given entity id. Entity may be the aggregate root or any ancestral entity. If
* no such entity exists an empty optional is returned.
*
* If the entity is loaded while handling an event its aggregate, the returned entity will automatically be played
* back to the event currently being handled. Otherwise, the most recent state of the entity is loaded.
*/
@SuppressWarnings("unchecked")
static Optional loadEntityValue(Id entityId) {
return loadAggregateFor(entityId).getEntity(entityId).map(e -> (T) e.get());
}
/**
* Returns an Entity containing given value. The returned entity won't exhibit any side effects when they are
* updated, i.e. they won't be synced to any repository or give rise to any events. Other than, that they are fully
* functional.
*/
static Entity asEntity(T value) {
return get().aggregateRepository().asEntity(value);
}
private static Entity playbackToHandledEvent(Entity entity) {
DeserializingMessage message = DeserializingMessage.getCurrent();
if (!Entity.isApplying()
&& message != null && (message.getMessageType() == EVENT || message.getMessageType() == NOTIFICATION)
&& entity.rootAnnotation().eventSourced()
&& entity.id().toString().equals(Entity.getAggregateId(message))
&& Entity.hasSequenceNumber(message)) {
return entity.playBackToEvent(message.getMessageId());
}
return entity;
}
/**
* Index given object for search.
*
* If the object is annotated with {@link Searchable @Searchable} the collection name and any timestamp or end path
* defined there will be used.
*
* If the object has a property annotated with {@link EntityId}, it will be used as the id of the document.
* Otherwise, a random id will be assigned to the document.
*
* This method returns once the object is stored.
*
* @see DocumentStore for more advanced uses.
* @see Searchable for ways to define collection name etc
*/
static CompletableFuture index(Object object) {
return get().documentStore().index(object);
}
/**
* Index given object for search.
*
* If the object has a property annotated with {@link EntityId}, it will be used as the id of the document.
* Otherwise, a random id will be assigned to the document.
*
* This method returns once the object is stored.
*
* @see DocumentStore for more advanced uses.
*/
static CompletableFuture index(Object object, Object collection) {
return get().documentStore().index(object, collection);
}
/**
* Index given object for search. This method returns once the object is stored.
*
* @see DocumentStore for more advanced uses.
*/
static CompletableFuture index(Object object, Object id, Object collection) {
return get().documentStore().index(object, id, collection);
}
/**
* Index given object for search. This method returns once the object is stored.
*
* @see DocumentStore for more advanced uses.
*/
static CompletableFuture index(Object object, Object id, Object collection, Instant timestamp) {
return get().documentStore().index(object, id, collection, timestamp);
}
/**
* Index given object for search. This method returns once the object is stored.
*
* @see DocumentStore for more advanced uses.
*/
static CompletableFuture index(Object object, Object id, Object collection, Instant begin, Instant end) {
return get().documentStore().index(object, id, collection, begin, end);
}
/**
* Index given objects for search. Use {@code idFunction} to provide the document's required id. Use
* {@code timestampFunction} and {@code endFunction} to provide the object's timestamp. If none are supplied the
* document will not be timestamped.
*
* This method returns once all objects are stored.
*
* @see DocumentStore for more advanced uses.
*/
static CompletableFuture index(Collection extends T> objects, Object collection,
Function super T, String> idFunction,
Function super T, Instant> timestampFunction,
Function super T, Instant> endFunction) {
return get().documentStore().index(objects, collection, idFunction, timestampFunction, endFunction);
}
/**
* Search the given collection for documents. Usually collection is the String name of the collection. However, it
* is also possible to call it with a {@link Collection} containing one or multiple collection names.
*
* If collection is of type {@link Class} it is expected that the class is annotated with {@link Searchable}. It
* will then use the collection configured there.
*
* For all other inputs, the collection name will be obtained by calling {@link Object#toString()} on the input.
*
* Example usage: FluxCapacitor.search("myCollection").query("foo !bar").fetch(100);
*/
static Search search(Object collection) {
return get().documentStore().search(collection);
}
/**
* Search the given collections for documents.
*
* If collection is of type {@link Class} it is expected that the class is annotated with * {@link Searchable}. It
* will then use the collection configured there. For all other inputs, the collection name will be obtained by
* calling {@link Object#toString()} on the input.
*
* Example usage: FluxCapacitor.search("myCollection", "myOtherCollection).query("foo !bar").fetch(100);
*/
static Search search(Object collection, Object... additionalCollections) {
return get().documentStore()
.search(Stream.concat(Stream.of(collection), stream(additionalCollections)).toList());
}
/**
* Search documents using given reusable query builder.
*
* Example usage: FluxCapacitor.search(SearchQuery.builder().search("myCollection").query("foo !bar")).fetch(100);
*/
static Search search(SearchQuery.Builder queryBuilder) {
return get().documentStore().search(queryBuilder);
}
/**
* Gets the document with given id in given collection, returning the value in the type that it was stored.
*/
static Optional getDocument(Object id, Object collection) {
return get().documentStore().fetchDocument(id, collection);
}
/**
* Gets the document with given id in given collection, converting the matching document to a value with given
* type.
*/
static Optional getDocument(Object id, Object collection, Class type) {
return get().documentStore().fetchDocument(id, collection, type);
}
/**
* Deletes the document with given id in given collection if it exists.
*/
static CompletableFuture deleteDocument(Object id, Object collection) {
return get().documentStore().deleteDocument(id, collection);
}
/**
* Deletes a search collection if it exists.
*/
static CompletableFuture deleteCollection(Object collection) {
return get().documentStore().deleteCollection(collection);
}
/**
* Modify given value before it's passed to the given viewer. See {@link FilterContent} for info on how to filter
* the value.
*/
static T filterContent(T value, User user) {
return get().serializer().filterContent(value, user);
}
/**
* Registers given handlers and initiates message tracking (i.e. listening for messages).
*
* The given handlers will be inspected for annotated handler methods (e.g. methods annotated with
* {@link HandleCommand}). Depending on this inspection message tracking will commence for any handled message
* types. To stop listening at any time invoke {@link Registration#cancel()} on the returned object.
*
* Note that an exception may be thrown if tracking for a given message type is already in progress.
*
* If any of the handlers is a local handler or contains local handler methods, i.e. if type or method is annotated
* with {@link LocalHandler}, the target object will (also) be registered as local handler. Local handlers will
* handle messages in the publishing thread. If a published message can be handled locally it will not be published
* to the Flux Capacitor service. Local handling of messages may come in handy in several situations: e.g. when the
* message is expressly meant to be handled only by the current application or if the message needs to be handled as
* quickly as possible. However, in most cases it will not be necessary to register local handlers.
*
* Note that it will generally not be necessary to invoke this method manually if you use Spring to configure your
* application.
*
* @see FluxCapacitorSpringConfig for more info on how to configure your application using Spring
* @see LocalHandler for more info on local handlers.
*/
default Registration registerHandlers(Object... handlers) {
return registerHandlers(Arrays.asList(handlers));
}
/**
* Registers given handlers and initiates message tracking.
*
* @see #registerHandlers(Object...) for more info
*/
default Registration registerHandlers(List> handlers) {
return apply(f -> {
Registration local = handlers.stream().flatMap(h -> Stream
.of(commandGateway().registerHandler(h), queryGateway().registerHandler(h),
eventGateway().registerHandler(h), eventStore().registerHandler(h),
errorGateway().registerHandler(h), webRequestGateway().registerHandler(h)))
.reduce(Registration::merge).orElse(Registration.noOp());
Registration tracking = stream(MessageType.values()).map(t -> tracking(t).start(this, handlers))
.reduce(Registration::merge).orElse(Registration.noOp());
return tracking.merge(local);
});
}
/**
* Have Flux Capacitor use the given Clock when generating timestamps, e.g. when creating a {@link Message}.
*/
void withClock(Clock clock);
/**
* Returns a client to assist with event sourcing.
*/
AggregateRepository aggregateRepository();
/**
* Returns the store for aggregate events.
*/
EventStore eventStore();
/**
* Returns the store for aggregate snapshots.
*/
SnapshotStore snapshotStore();
/**
* Returns the message scheduling client.
*/
Scheduler scheduler();
/**
* Returns the gateway for command messages.
*/
CommandGateway commandGateway();
/**
* Returns the gateway for query messages.
*/
QueryGateway queryGateway();
/**
* Returns the message gateway for application events. Use {@link #aggregateRepository()} to publish events
* belonging to an aggregate.
*/
EventGateway eventGateway();
/**
* Returns the gateway for result messages sent by handlers of commands and queries.
*/
ResultGateway resultGateway();
/**
* Returns the gateway for any error messages published while handling a command or query.
*/
ErrorGateway errorGateway();
/**
* Returns the gateway for metrics events. Metrics events can be published in any form to log custom performance
* metrics about an application.
*/
MetricsGateway metricsGateway();
/**
* Returns the gateway for sending web requests.
*/
WebRequestGateway webRequestGateway();
/**
* Returns a client to assist with the tracking of a given message type.
*/
Tracking tracking(MessageType messageType);
/**
* Returns a client for the key value service offered by Flux Capacitor.
*/
KeyValueStore keyValueStore();
/**
* Returns a client for the document search service offered by Flux Capacitor.
*/
DocumentStore documentStore();
/**
* Returns the UserProvider used by Flux Capacitor to authenticate users. May be {@code null} if user authentication
* is disabled.
*/
UserProvider userProvider();
/**
* Returns the cache used by the client to cache aggregates etc.
*/
Cache cache();
/**
* Returns the provider of correlation data for published messages.
*/
CorrelationDataProvider correlationDataProvider();
/**
* Returns the default serializer
*/
Serializer serializer();
/**
* Returns the clock used by Flux Capacitor to generate timestamps.
*/
Clock clock();
/**
* Returns the factory used by Flux Capacitor to generate identifiers.
*/
IdentityProvider identityProvider();
/**
* Returns the {@link PropertySource} configured for this FluxCapacitor instance.
*/
PropertySource propertySource();
/**
* Returns the low level client used by this FluxCapacitor instance to interface with the Flux Capacitor service. Of
* course the returned client may also be a stand-in for the actual service.
*/
Client client();
/**
* Applies the given function with this Flux Capacitor set as current threadlocal instance.
*/
@SneakyThrows
default R apply(ThrowingFunction function) {
FluxCapacitor current = FluxCapacitor.instance.get();
try {
FluxCapacitor.instance.set(this);
return function.apply(this);
} finally {
FluxCapacitor.instance.set(current);
}
}
/**
* Executes the given task with this Flux Capacitor set as current threadlocal instance.
*/
@SneakyThrows
default void execute(ThrowingConsumer task) {
FluxCapacitor current = FluxCapacitor.instance.get();
try {
FluxCapacitor.instance.set(this);
task.accept(this);
} finally {
FluxCapacitor.instance.set(current);
}
}
/**
* Register a task to run before this Flux Capacitor instance is closed.
*/
Registration beforeShutdown(Runnable task);
@Override
void close();
}