diff --git a/Src/LiquidProjections.Owin/CustomNancyBootstrapper.cs b/Src/LiquidProjections.Owin/CustomNancyBootstrapper.cs
index b2e1d90..deccd06 100644
--- a/Src/LiquidProjections.Owin/CustomNancyBootstrapper.cs
+++ b/Src/LiquidProjections.Owin/CustomNancyBootstrapper.cs
@@ -20,9 +20,9 @@ namespace LiquidProjections.Owin
{
internal class CustomNancyBootstrapper : DefaultNancyBootstrapper
{
- private readonly ProjectionStats stats;
+ private readonly IProjectionStats stats;
- public CustomNancyBootstrapper(ProjectionStats stats)
+ public CustomNancyBootstrapper(IProjectionStats stats)
{
this.stats = stats;
}
diff --git a/Src/LiquidProjections.Owin/MiddlewareExtensions.cs b/Src/LiquidProjections.Owin/MiddlewareExtensions.cs
index a5aad4f..39446ca 100644
--- a/Src/LiquidProjections.Owin/MiddlewareExtensions.cs
+++ b/Src/LiquidProjections.Owin/MiddlewareExtensions.cs
@@ -6,7 +6,7 @@ namespace LiquidProjections.Owin
{
public static class MiddlewareExtensions
{
- public static IAppBuilder UseLiquidProjections(this IAppBuilder appBuilder, ProjectionStats stats)
+ public static IAppBuilder UseLiquidProjections(this IAppBuilder appBuilder, IProjectionStats stats)
{
appBuilder.Map("/projectionStats", a => a.UseNancy(new NancyOptions
{
diff --git a/Src/LiquidProjections.Owin/StatisticsModule.cs b/Src/LiquidProjections.Owin/StatisticsModule.cs
index d0e41e2..740aa2c 100644
--- a/Src/LiquidProjections.Owin/StatisticsModule.cs
+++ b/Src/LiquidProjections.Owin/StatisticsModule.cs
@@ -16,15 +16,15 @@ namespace LiquidProjections.Owin
{
internal class StatisticsModule : NancyModule
{
- public StatisticsModule(ProjectionStats stats, IResourceLinker resourceLinker)
+ public StatisticsModule(IProjectionStats stats, IResourceLinker resourceLinker)
{
Get("/", args =>
{
- var results = stats.GetForAllProjectors().OrderBy(p => p.ProjectorId).Select(p => new ProjectorSummary
+ var results = stats.OrderBy(p => p.ProjectorId).Select(p => new ProjectorSummary
{
ProjectorId = p.ProjectorId,
- LastCheckpoint = stats[p.ProjectorId].LastCheckpoint.Checkpoint,
- LastCheckpointUpdatedUtc = stats[p.ProjectorId].LastCheckpoint.TimestampUtc,
+ LastCheckpoint = p.LastCheckpoint.Checkpoint,
+ LastCheckpointUpdatedUtc = p.LastCheckpoint.TimestampUtc,
Url = Context.Request.Url + $"/{p.ProjectorId}"
});
@@ -34,13 +34,13 @@ public StatisticsModule(ProjectionStats stats, IResourceLinker resourceLinker)
Get("/{id}", args =>
{
string id = args.Id;
-
+
return new
{
ProjectorId = id,
- LastCheckpoint = stats[id].LastCheckpoint.Checkpoint,
- LastCheckpointUpdatedUtc = stats[id].LastCheckpoint.TimestampUtc,
- Properties = stats[id].GetProperties().Select(p => new ProjectorProperty
+ LastCheckpoint = stats.Get(id).LastCheckpoint.Checkpoint,
+ LastCheckpointUpdatedUtc = stats.Get(id).LastCheckpoint.TimestampUtc,
+ Properties = stats.Get(id).GetProperties().Select(p => new ProjectorProperty
{
Key = p.Key,
Value = p.Value.Value,
@@ -60,7 +60,7 @@ public StatisticsModule(ProjectionStats stats, IResourceLinker resourceLinker)
return new ProjectorEventCollection
{
ProjectorId = id,
- Events = stats[id].GetEvents().Select(@event => new ProjectorEvent
+ Events = stats.Get(id).GetEvents().Select(@event => new ProjectorEvent
{
Body = @event.Body,
TimestampUtc = @event.TimestampUtc
@@ -72,7 +72,7 @@ public StatisticsModule(ProjectionStats stats, IResourceLinker resourceLinker)
{
string id = args.Id;
- TimeSpan? eta = stats[id].GetTimeToReach(args.targetCheckpoint);
+ TimeSpan? eta = stats.GetTimeToReach(id, args.targetCheckpoint);
return new
{
diff --git a/Src/LiquidProjections/LiquidProjections.csproj b/Src/LiquidProjections/LiquidProjections.csproj
index e198fa2..960426d 100644
--- a/Src/LiquidProjections/LiquidProjections.csproj
+++ b/Src/LiquidProjections/LiquidProjections.csproj
@@ -1,11 +1,9 @@
-
netstandard1.1
false
-
TRACE;DEBUG;NETSTANDARD1_1;LIBLOG_PORTABLE
bin\Debug\netstandard1.1\LiquidProjections.xml
@@ -13,7 +11,6 @@
True
-
TRACE;RELEASE;LIBLOG_PORTABLE;NETSTANDARD1_1
bin\Release\netstandard1.1\LiquidProjections.xml
@@ -21,15 +18,12 @@
True
-
-
-
\ No newline at end of file
diff --git a/Src/LiquidProjections/Statistics/IProjectionStats.cs b/Src/LiquidProjections/Statistics/IProjectionStats.cs
new file mode 100644
index 0000000..ad04421
--- /dev/null
+++ b/Src/LiquidProjections/Statistics/IProjectionStats.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Collections.Generic;
+
+namespace LiquidProjections.Statistics
+{
+ public interface IProjectionStats : IEnumerable
+ {
+ ///
+ /// Should be called to track the progress of a projector and use that to calculate an ETA.
+ ///
+ void TrackProgress(string projectorId, long checkpoint);
+
+ ///
+ /// Can be used to store projector-specific properties that characterize the projector's configuration or state.
+ ///
+ ///
+ /// Each property is identified by a . This class only keeps the latest value
+ /// for each property.
+ ///
+ void StoreProperty(string projectorId, string name, string value);
+
+ ///
+ /// Can be used to store information that happened that can help diagnose the state or failure of a projector.
+ ///
+ void LogEvent(string projectorId, string body);
+
+ ///
+ /// Gets the speed in transactions per minute based on a weighted average over the last
+ /// ten minutes, or null if there is not enough information yet.
+ ///
+ ///
+ float? GetSpeed(string projectorId);
+
+ ///
+ /// Calculates the expected time for the projector identified by to reach a
+ /// certain based on a weighted average over the last
+ /// ten minutes, or null if there is not enough information yet. Use to report
+ /// progress.
+ ///
+ TimeSpan? GetTimeToReach(string projectorId, long targetCheckpoint);
+
+ ///
+ /// Gets the stats for an individual projector.
+ ///
+ IProjectorStats Get(string projectorId);
+ }
+}
\ No newline at end of file
diff --git a/Src/LiquidProjections/Statistics/ProjectionStats.cs b/Src/LiquidProjections/Statistics/ProjectionStats.cs
index b6abcd4..968e3cb 100644
--- a/Src/LiquidProjections/Statistics/ProjectionStats.cs
+++ b/Src/LiquidProjections/Statistics/ProjectionStats.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
@@ -8,7 +9,7 @@ namespace LiquidProjections.Statistics
///
/// Provides a thread-safe place to store all kinds of run-time information about the progress of a projector.
///
- public class ProjectionStats
+ public class ProjectionStats : IProjectionStats
{
private readonly Func nowUtc;
private readonly ConcurrentDictionary stats = new ConcurrentDictionary();
@@ -67,10 +68,16 @@ public void LogEvent(string projectorId, string body)
return this[projectorId].GetTimeToReach(targetCheckpoint);
}
+ ///
+ public IProjectorStats Get(string projectorId)
+ {
+ return this[projectorId];
+ }
+
///
/// Gets the statistics for a particular projector.
///
- public ProjectorStats this[string projectorId]
+ private ProjectorStats this[string projectorId]
{
get
{
@@ -78,9 +85,14 @@ public ProjectorStats this[string projectorId]
}
}
- public IEnumerable GetForAllProjectors()
+ public IEnumerator GetEnumerator()
+ {
+ return stats.Values.GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
{
- return stats.ToArray().Select(projectorStatsById => projectorStatsById.Value);
+ return GetEnumerator();
}
}
}
diff --git a/Src/LiquidProjections/Statistics/ProjectorStats.cs b/Src/LiquidProjections/Statistics/ProjectorStats.cs
index 5004d7d..c232dae 100644
--- a/Src/LiquidProjections/Statistics/ProjectorStats.cs
+++ b/Src/LiquidProjections/Statistics/ProjectorStats.cs
@@ -5,13 +5,29 @@
namespace LiquidProjections.Statistics
{
+ public interface IProjectorStats
+ {
+ string ProjectorId { get; }
+ TimestampedCheckpoint LastCheckpoint { get; }
+
+ ///
+ /// Gets a snapshot of the properties stored for this projector at the time of calling.
+ ///
+ IDictionary GetProperties();
+
+ ///
+ /// Gets a snapshot of the events stored for this projector at the time of calling.
+ ///
+ IReadOnlyList GetEvents();
+ }
+
///
/// Contains statistics and information about a particular projector.
///
///
/// An instance of this class is safe for use in multi-threaded solutions.
///
- public class ProjectorStats
+ public class ProjectorStats : IProjectorStats
{
private readonly object eventsSyncObject = new object();
private readonly object progressSyncObject = new object();
diff --git a/Tests/LiquidProjections.Specs/ProjectionStatsSpecs.cs b/Tests/LiquidProjections.Specs/ProjectionStatsSpecs.cs
index d726fa6..1a088b4 100644
--- a/Tests/LiquidProjections.Specs/ProjectionStatsSpecs.cs
+++ b/Tests/LiquidProjections.Specs/ProjectionStatsSpecs.cs
@@ -29,7 +29,7 @@ public void When_checking_in_multiple_times_for_a_projector_it_should_remember_t
//-----------------------------------------------------------------------------------------------------------
// Assert
//-----------------------------------------------------------------------------------------------------------
- var projectorStats = stats.GetForAllProjectors().Should().ContainSingle(s => s.ProjectorId == "myProjector").Subject;
+ var projectorStats = stats.Should().ContainSingle(s => s.ProjectorId == "myProjector").Subject;
projectorStats.LastCheckpoint.Checkpoint.Should().Be(2000);
projectorStats.LastCheckpoint.TimestampUtc.Should().Be(nowUtc);
}
@@ -53,7 +53,7 @@ public void When_multiple_properties_are_registered_under_the_same_name_it_shoul
//-----------------------------------------------------------------------------------------------------------
// Assert
//-----------------------------------------------------------------------------------------------------------
- var projectorStats = stats.GetForAllProjectors().Should().ContainSingle(s => s.ProjectorId == "myProjector").Subject;
+ var projectorStats = stats.Should().ContainSingle(s => s.ProjectorId == "myProjector").Subject;
projectorStats.GetProperties().Should().ContainKey("theName");
projectorStats.GetProperties()["theName"].Should().BeEquivalentTo(new
@@ -83,7 +83,7 @@ public void When_multiple_properties_are_registered_under_different_names_it_sho
//-----------------------------------------------------------------------------------------------------------
// Assert
//-----------------------------------------------------------------------------------------------------------
- var projectorStats = stats.GetForAllProjectors().Should().ContainSingle(s => s.ProjectorId == "myProjector").Subject;
+ var projectorStats = stats.Should().ContainSingle(s => s.ProjectorId == "myProjector").Subject;
projectorStats.GetProperties().Should().ContainKey("aName");
projectorStats.GetProperties()["aName"].Should().BeEquivalentTo(new
@@ -121,7 +121,7 @@ public void When_multiple_events_are_registered_it_should_remember_their_timesta
//-----------------------------------------------------------------------------------------------------------
// Assert
//-----------------------------------------------------------------------------------------------------------
- var projectorStats = stats.GetForAllProjectors().Should().ContainSingle(s => s.ProjectorId == "myProjector").Subject;
+ var projectorStats = stats.Should().ContainSingle(s => s.ProjectorId == "myProjector").Subject;
projectorStats.GetEvents().Should().BeEquivalentTo(new[]
{
new