public When_a_commit_is_persisted() { Given(() => { UseThe(new TransactionBuilder().WithCheckpoint(123).Build()); var eventStore = A.Fake <IPassiveEventStore>(); A.CallTo(() => eventStore.GetFrom(A <long?> .Ignored)).Returns(new[] { The <Transaction>() }); var adapter = new PollingEventStoreAdapter(eventStore, 11, pollingInterval, 100, () => DateTime.UtcNow); WithSubject(_ => adapter.Subscribe); }); When(() => { Subject(null, new Subscriber { HandleTransactions = (transactions, info) => { transactionHandledSource.SetResult(transactions.First()); return(Task.FromResult(0)); } }, "someId"); }); }
public When_a_commit_is_already_projected() { Given(() => { Transaction projectedCommit = new TransactionBuilder().WithCheckpoint(123).Build(); Transaction unprojectedCommit = new TransactionBuilder().WithCheckpoint(124).Build(); var eventStore = A.Fake <IPassiveEventStore>(); A.CallTo(() => eventStore.GetFrom(A <long?> .Ignored)).Returns(new[] { projectedCommit, unprojectedCommit }); A.CallTo(() => eventStore.GetFrom(123)).Returns(new[] { unprojectedCommit }); var adapter = new PollingEventStoreAdapter(eventStore, 11, 1.Seconds(), 100, () => DateTime.UtcNow); WithSubject(_ => adapter.Subscribe); }); When(() => { Subject(123, new Subscriber { HandleTransactions = (transactions, info) => { transactionHandledSource.SetResult(transactions.First()); return(Task.FromResult(0)); } }, "someId"); }); }
public When_the_persistency_engine_is_temporarily_unavailable() { Given(() => { UseThe(new TransactionBuilder().WithCheckpoint(123).Build()); var eventStore = A.Fake <IPassiveEventStore>(); A.CallTo(() => eventStore.GetFrom(A <long?> .Ignored)).Returns(new[] { The <Transaction>() }); A.CallTo(() => eventStore.GetFrom(A <long?> .Ignored)).Throws(new ApplicationException()).Once(); var adapter = new PollingEventStoreAdapter(eventStore, 11, pollingInterval, 100, () => DateTime.UtcNow); WithSubject(_ => adapter.Subscribe); }); When(() => { Subject(null, new Subscriber { HandleTransactions = (transactions, info) => { actualTransaction = transactions.First(); return(Task.FromResult(0)); } }, "someId"); }); }
public When_requesting_a_subscription_beyond_the_highest_available_checkpoint() { Given(() => { var eventStore = A.Fake <IPassiveEventStore>(); A.CallTo(() => eventStore.GetFrom(999)).Returns(new Transaction[0]); var adapter = new PollingEventStoreAdapter(eventStore, 11, 1.Seconds(), 100, () => DateTime.UtcNow); WithSubject(_ => adapter.Subscribe); }); When(() => { Subject(1000, new Subscriber { HandleTransactions = (transactions, info) => { throw new InvalidOperationException("The adapter should not provide any events."); }, NoSuchCheckpoint = info => { noSuchCheckpointRaised.SetResult(true); return(Task.FromResult(0)); } }, "myIde"); }); }
When_a_subscription_starts_after_zero_checkpoint_and_another_subscription_starts_after_null_checkpoint_while_the_first_subscription_is_loading_the_first_page_from_the_event_store() { Given(() => { IPassiveEventStore eventStore = A.Fake <IPassiveEventStore>(); A.CallTo(() => eventStore.GetFrom(A <long?> .Ignored)).ReturnsLazily(call => { long checkpoint = call.GetArgument <long?>(0) ?? 0; aSubscriptionStartedLoading.Set(); if (!secondSubscriptionCreated.Wait(TimeSpan.FromSeconds(10))) { throw new InvalidOperationException("The second subscription has not been created in 10 seconds."); } // Give the second subscription enough time to access the cache. Thread.Sleep(TimeSpan.FromSeconds(1)); return(checkpoint > 0 ? new Transaction[0] : new[] { new TransactionBuilder().WithCheckpoint(1).Build() }); }); var adapter = new PollingEventStoreAdapter(eventStore, 11, pollingInterval, 100, () => DateTime.UtcNow); WithSubject(_ => adapter.Subscribe); }); When(() => { Subject(0, new Subscriber { HandleTransactions = (transactions, info) => Task.FromResult(0) }, "firstId"); if (!aSubscriptionStartedLoading.Wait(TimeSpan.FromSeconds(10))) { throw new InvalidOperationException("The first subscription has not started loading in 10 seconds."); } Subject(null, new Subscriber { HandleTransactions = (transactions, info) => { secondSubscriptionReceivedTheTransaction.Set(); return(Task.FromResult(0)); } }, "secondId"); secondSubscriptionCreated.Set(); }); }
public When_there_are_no_more_commits() { Given(() => { var eventStore = A.Fake <IPassiveEventStore>(); A.CallTo(() => eventStore.GetFrom(A <long?> .Ignored)).ReturnsLazily <IEnumerable <Transaction>, long?>( checkpoint => { pollingTimeStamps.Add(new PollingCall(checkpoint, DateTime.UtcNow)); if (pollingTimeStamps.Count == 4) { pollingCompleted.SetResult(true); } long checkPoint = checkpoint ?? 0; long offsetToDetectAheadSubscriber = 1; if (checkPoint <= (subscriptionCheckpoint - offsetToDetectAheadSubscriber)) { return(new TransactionBuilder().WithCheckpoint(checkPoint + 1).BuildAsEnumerable()); } else { return(new Transaction[0]); } }); var adapter = new PollingEventStoreAdapter(eventStore, 0, pollingInterval, 100, () => DateTime.UtcNow); WithSubject(_ => adapter.Subscribe); }); When(() => { return(Subject(subscriptionCheckpoint, new Subscriber { HandleTransactions = (transactions, info) => Task.FromResult(0) }, "someId")); }); }
static void Main(string[] args) { var stopWatch = new Stopwatch(); stopWatch.Start(); // var adapter = new PollingEventStoreAdapter(new PassiveEventStore(), 50000, 5.Seconds(), 1000, () => DateTime.UtcNow, messageFunc => Console.WriteLine(messageFunc())); var adapter = new PollingEventStoreAdapter(new PassiveEventStore(), 50000, TimeSpan.FromSeconds(5), 1000, () => DateTime.UtcNow); int maxSubscribers = 50; for (int id = 0; id < maxSubscribers; id++) { int localId = id; adapter.Subscribe(0, new Subscriber { HandleTransactions = (transactions, info) => { Console.WriteLine( $"{stopWatch.Elapsed}: Subscriber {info.Id} received transactions {transactions.First().Checkpoint} to {transactions.Last().Checkpoint} on thead {Thread.CurrentThread.ManagedThreadId}"); Thread.Sleep(new Random().Next(100, 500)); return(Task.FromResult(0)); }, NoSuchCheckpoint = info => Task.FromResult(0) }, id.ToString()); Console.WriteLine($"{stopWatch.Elapsed}: Started subscriber {localId}"); Thread.Sleep(1000); } Console.WriteLine("Press a key to shutdown"); Console.ReadLine(); adapter.Dispose(); }
/// <summary> /// Creates an adapter that observes an implementation of <see cref="IPersistStreams"/> and efficiently handles /// multiple subscribers. /// </summary> /// <param name="eventStore"> /// The persistency implementation that the NEventStore is configured with. /// </param> /// <param name="cacheSize"> /// The size of the LRU cache that will hold transactions already loaded from the event store. The larger the cache, /// the higher the chance multiple subscribers can reuse the same transactions without hitting the underlying event store. /// Set to <c>0</c> to disable the cache alltogether. /// </param> /// <param name="pollInterval"> /// The amount of time to wait before polling again after the event store has not yielded any transactions anymore. /// </param> /// <param name="maxPageSize"> /// The size of the page of transactions the adapter should load from the event store for every query. /// </param> /// <param name="getUtcNow"> /// Provides the current date and time in UTC. /// </param> public NEventStoreAdapter(IPersistStreams eventStore, int cacheSize, TimeSpan pollInterval, int maxPageSize, Func <DateTime> getUtcNow) { pollingAdapter = new PollingEventStoreAdapter(new StreamPersisterAdapter(eventStore), cacheSize, pollInterval, maxPageSize, getUtcNow); }