All Downloads are FREE. Search and download functionalities are using the official Maven repository.

eventstore.akka.EsConnection.scala Maven / Gradle / Ivy

package eventstore
package akka

import java.io.Closeable
import scala.concurrent.{ExecutionContext, Future}
import _root_.akka.NotUsed
import _root_.akka.actor._
import _root_.akka.pattern.ask
import _root_.akka.stream.scaladsl._
import _root_.akka.util.Timeout
import eventstore.akka.tcp.ConnectionActor
import eventstore.akka.streams.{AllStreamsSourceStage, StreamSourceStage}

/**
 * Maintains a full duplex connection to the EventStore
 * 

* All operations are handled in a full async manner. * Many threads can use an [[EsConnection]] at the same time or a single thread can make many asynchronous requests. * To get the most performance out of the connection it is generally recommended to use it in this way. */ class EsConnection( connection: ActorRef, factory: ActorRefFactory, settings: Settings = Settings.Default ) { implicit val timeout: Timeout = Timeout(settings.operationTimeout) def apply[OUT <: Out, IN <: In](out: OUT, credentials: Option[UserCredentials] = None)( implicit outIn: ClassTags[OUT, IN] ): Future[IN] = { val future = connection ? credentials.fold[OutLike](out)(WithCredentials(out, _)) future.mapTo[IN](outIn.in) } /** * Starts a transaction in the event store on a given stream asynchronously *

* [[EsTransaction]] allows the calling of multiple writes with multiple * round trips over long periods of time between the caller and the event store. This method * is only available through the TCP interface and no equivalent exists for the RESTful interface. * * @param data Stream id and other params to star transaction * @param credentials The optional user credentials to perform operation with * @return A [[Future]] containing an actual transaction */ def startTransaction(data: TransactionStart, credentials: Option[UserCredentials] = None): Future[EsTransaction] = { val props = TransactionActor.props(connection, TransactionActor.Start(data), credentials = credentials) val actor = factory.actorOf(props) EsTransaction.start(actor) } /** * Continues transaction by provided transaction ID. *

* [[EsTransaction]] allows the calling of multiple writes with multiple * round trips over long periods of time between the caller and the event store. This method * is only available through the TCP interface and no equivalent exists for the RESTful interface. * * @param transactionId The transaction ID that needs to be continued. * @param credentials The optional user credentials to perform operation with * @return [[EsTransaction]] for given transaction id */ def continueTransaction(transactionId: Long, credentials: Option[UserCredentials] = None): EsTransaction = { val props = TransactionActor.props(connection, TransactionActor.Continue(transactionId), credentials = credentials) val actor = factory.actorOf(props) EsTransaction.continue(transactionId, actor) } /** * Subscribes to a single event stream. New events * written to the stream while the subscription is active will be * pushed to the client. * * @param streamId The stream to subscribe to * @param observer A [[SubscriptionObserver]] to handle a new event received over the subscription * @param resolveLinkTos Whether to resolve LinkTo events automatically * @param credentials The optional user credentials to perform operation with * @return A [[Closeable]] representing the subscription which can be closed. */ def subscribeToStream( streamId: EventStream.Id, observer: SubscriptionObserver[Event], resolveLinkTos: Boolean = settings.resolveLinkTos, credentials: Option[UserCredentials] = None ): Closeable = { subscribeToStream(streamId, observer, Some(EventNumber.Last), resolveLinkTos, credentials) } /** * Subscribes to a single event stream. Existing events from * lastCheckpoint onwards are read from the stream * and presented to the user of [[SubscriptionObserver]] * as if they had been pushed. *

* Once the end of the stream is read the subscription is * transparently (to the user) switched to push new events as * they are written. *

* If events have already been received and resubscription from the same point * is desired, use the event number of the last event processed which * appeared on the subscription. * * @param streamId The stream to subscribe to * @param observer A [[SubscriptionObserver]] to handle a new event received over the subscription * @param fromNumberExclusive The event number from which to start, or `None` to read all events. * @param resolveLinkTos Whether to resolve LinkTo events automatically * @param credentials The optional user credentials to perform operation with * @return A [[Closeable]] representing the subscription which can be closed. */ def subscribeToStreamFrom( streamId: EventStream.Id, observer: SubscriptionObserver[Event], fromNumberExclusive: Option[EventNumber.Exact] = None, resolveLinkTos: Boolean = settings.resolveLinkTos, credentials: Option[UserCredentials] = None ): Closeable = { subscribeToStream(streamId, observer, fromNumberExclusive, resolveLinkTos, credentials) } private def subscribeToStream( streamId: EventStream.Id, observer: SubscriptionObserver[Event], fromNumberExclusive: Option[EventNumber], resolveLinkTos: Boolean, credentials: Option[UserCredentials] ): Closeable = { val client = factory.actorOf(SubscriptionObserverActor.props(observer)) val props = StreamSubscriptionActor.props( connection = connection, client = client, streamId = streamId, fromNumberExclusive = fromNumberExclusive, credentials = credentials, settings = settings.copy(resolveLinkTos = resolveLinkTos) ) factory.actorOf(props) ActorCloseable(client) } /** * Subscribes to all events in the Event Store. New events written to the stream * while the subscription is active will be pushed to the client. * * @param observer A [[SubscriptionObserver]] to handle a new event received over the subscription * @param resolveLinkTos Whether to resolve LinkTo events automatically * @param credentials The optional user credentials to perform operation with * @return A [[Closeable]] representing the subscription which can be closed. */ def subscribeToAll( observer: SubscriptionObserver[IndexedEvent], resolveLinkTos: Boolean = settings.resolveLinkTos, credentials: Option[UserCredentials] = None ): Closeable = { subscribeToAll(observer, Some(Position.Last), resolveLinkTos, credentials) } /** * Subscribes to a all events. Existing events from position * onwards are read from the Event Store and presented to the user of * [[SubscriptionObserver]] as if they had been pushed. *

* Once the end of the stream is read the subscription is * transparently (to the user) switched to push new events as * they are written. *

* If events have already been received and resubscription from the same point * is desired, use the position representing the last event processed which * appeared on the subscription. * * @param observer A [[SubscriptionObserver]] to handle a new event received over the subscription * @param fromPositionExclusive The position from which to start, or `None` to read all events * @param resolveLinkTos Whether to resolve LinkTo events automatically * @param credentials The optional user credentials to perform operation with * @return A [[Closeable]] representing the subscription which can be closed. */ def subscribeToAllFrom( observer: SubscriptionObserver[IndexedEvent], fromPositionExclusive: Option[Position.Exact] = None, resolveLinkTos: Boolean = settings.resolveLinkTos, credentials: Option[UserCredentials] = None ): Closeable = { subscribeToAll(observer, fromPositionExclusive, resolveLinkTos, credentials) } private def subscribeToAll( observer: SubscriptionObserver[IndexedEvent], fromPositionExclusive: Option[Position], resolveLinkTos: Boolean, credentials: Option[UserCredentials] ) = { val client = factory actorOf SubscriptionObserverActor.props(observer) val props = SubscriptionActor.props( connection = connection, client = client, fromPositionExclusive = fromPositionExclusive, credentials, settings = settings.copy(resolveLinkTos = resolveLinkTos) ) factory.actorOf(props) ActorCloseable(client) } def setStreamMetadata( streamId: EventStream.Id, metadata: Content, expectedMetastreamVersion: ExpectedVersion = ExpectedVersion.Any, credentials: Option[UserCredentials] = None )(implicit ec: ExecutionContext): Future[Option[WriteResult]] = { val writeEvents = WriteEvents.StreamMetadata(streamId.metadata, metadata, expectedMetastreamVersion) apply(writeEvents, credentials).map(WriteResult.opt) } // TODO think about replacing content with something similar to what is in the .Net client def getStreamMetadata(streamId: EventStream.Id, credentials: Option[UserCredentials] = None)(implicit ec: ExecutionContext): Future[Content] = { apply(ReadEvent.StreamMetadata(streamId.metadata), credentials).map { case ReadEventCompleted(Event.StreamMetadata(data)) => data case ReadEventCompleted(EventRecord.Deleted) => Content.Empty case ReadEventCompleted(event) => throw NonMetadataEventException(event) }.recover { case _: StreamNotFoundException => Content.Empty case _: StreamDeletedException => Content.Empty } } /** * Creates a [[Source]] you can use to subscribe to a single event stream. Existing events from * event number onwards are read from the stream and presented to the user of * [[Source]] as if they had been pushed. *

* Once the end of the stream is read the [[Source]] transparently (to the user) * switches to push new events as they are written. *

* If events have already been received and resubscription from the same point * is desired, use the event number of the last event processed. * * @param streamId The stream to subscribe to * @param fromEventNumberExclusive The event number from which to start, or [[None]] to read all events. * @param resolveLinkTos Whether to resolve LinkTo events automatically * @param credentials The optional user credentials to perform operation with * @param infinite Whether to subscribe to the future events upon reading all current * @param readBatchSize Number of events to be retrieved by client as single message * @return A [[_root_.akka.stream.scaladsl.Source]] representing the stream */ def streamSource( streamId: EventStream.Id, fromEventNumberExclusive: Option[EventNumber] = None, resolveLinkTos: Boolean = settings.resolveLinkTos, credentials: Option[UserCredentials] = None, infinite: Boolean = true, readBatchSize: Int = settings.readBatchSize ): Source[Event, NotUsed] = Source.fromGraph( new StreamSourceStage( connection = connection, streamId = streamId, fromNumberExclusive = fromEventNumberExclusive, credentials = credentials, settings = settings.copy(resolveLinkTos = resolveLinkTos, readBatchSize = readBatchSize), infinite = infinite ) ) /** * Creates a [[Source]] you can use to subscribes to all events. Existing events from position * onwards are read from the Event Store and presented to the user of * [[Source]] as if they had been pushed. *

* Once the end of the stream is read the [[Source]] transparently (to the user) * switches to push new events as they are written. *

* If events have already been received and resubscription from the same point * is desired, use the position representing the last event processed. * * @param resolveLinkTos Whether to resolve LinkTo events automatically * @param fromPositionExclusive The position from which to start, or [[None]] to read all events * @param credentials The optional user credentials to perform operation with * @param infinite Whether to subscribe to the future events upon reading all current * @param readBatchSize Number of events to be retrieved by client as single message * @return A [[_root_.akka.stream.scaladsl.Source]] representing all streams */ def allStreamsSource( resolveLinkTos: Boolean = settings.resolveLinkTos, fromPositionExclusive: Option[Position] = None, credentials: Option[UserCredentials] = None, infinite: Boolean = true, readBatchSize: Int = settings.readBatchSize ): Source[IndexedEvent, NotUsed] = Source.fromGraph( new AllStreamsSourceStage( connection = connection, fromPositionExclusive = fromPositionExclusive, credentials = credentials, settings = settings.copy(resolveLinkTos = resolveLinkTos, readBatchSize = readBatchSize), infinite = infinite ) ) } object EsConnection { def apply(system: ActorSystem, settings: Settings = Settings.Default): EsConnection = { val props = ConnectionActor.props(settings) new EsConnection(system.actorOf(props), system,settings) } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy