public PoolingProjector(IPassiveEventStore eventStore, T projection, ILogger <T> logger) { _eventStore = eventStore; _projection = projection; _logger = logger; EventPageSize = 10; PoolingTimer = TimeSpan.FromSeconds(2); }
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(); }); }
/// <summary> /// Creates an adapter that observes an implementation of <see cref="IPassiveEventStore"/> 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 PollingEventStoreAdapter(IPassiveEventStore eventStore, int cacheSize, TimeSpan pollInterval, int maxPageSize, Func <DateTime> getUtcNow) { this.eventStore = eventStore; this.pollInterval = pollInterval; this.maxPageSize = maxPageSize; this.getUtcNow = getUtcNow; if (cacheSize > 0) { transactionCacheByPreviousCheckpoint = new LruCache <long, Transaction>(cacheSize); } }
/// <summary> /// Creates an adapter that observes an implementation of <see cref="IPassiveEventStore"/> 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"> /// Obsolete. Only kept in there to prevent breaking changes. /// </param> /// <param name="logger"> /// An optional method for logging internal diagnostic messages as well as any exceptions that happen. /// </param> /// <remarks> /// Diagnostic information is only logged if this code is compiled with the LIQUIDPROJECTIONS_DIAGNOSTICS compiler symbol. /// </remarks> public PollingEventStoreAdapter(IPassiveEventStore eventStore, int cacheSize, TimeSpan pollInterval, int maxPageSize, Func <DateTime> getUtcNow, LogMessage logger = null) { this.eventStore = eventStore; this.pollInterval = pollInterval; this.maxPageSize = maxPageSize; #if LIQUIDPROJECTIONS_DIAGNOSTICS this.logDebug = logger ?? (_ => { }); #else this.logDebug = _ => {}; #endif this.logError = logger ?? (_ => { }); if (cacheSize > 0) { cachedTransactionsByPrecedingCheckpoint = new LruCache <long, Transaction>(cacheSize); } }