Skip to content

Commit

Permalink
Allowed the stats OWIN API to work with any implementation of IProjec…
Browse files Browse the repository at this point in the history
…torState (#106)
  • Loading branch information
dennisdoomen authored Oct 3, 2017
1 parent 146c5b1 commit 2bef488
Show file tree
Hide file tree
Showing 8 changed files with 97 additions and 28 deletions.
4 changes: 2 additions & 2 deletions Src/LiquidProjections.Owin/CustomNancyBootstrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
2 changes: 1 addition & 1 deletion Src/LiquidProjections.Owin/MiddlewareExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down
20 changes: 10 additions & 10 deletions Src/LiquidProjections.Owin/StatisticsModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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}"
});

Expand All @@ -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,
Expand All @@ -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
Expand All @@ -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
{
Expand Down
6 changes: 0 additions & 6 deletions Src/LiquidProjections/LiquidProjections.csproj
Original file line number Diff line number Diff line change
@@ -1,35 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard1.1</TargetFramework>
<PackageId />
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DefineConstants>TRACE;DEBUG;NETSTANDARD1_1;LIBLOG_PORTABLE</DefineConstants>
<DocumentationFile>bin\Debug\netstandard1.1\LiquidProjections.xml</DocumentationFile>
<NoWarn>1701;1702;1705;1591</NoWarn>
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
<TreatSpecificWarningsAsErrors />
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DefineConstants>TRACE;RELEASE;LIBLOG_PORTABLE;NETSTANDARD1_1</DefineConstants>
<DocumentationFile>bin\Release\netstandard1.1\LiquidProjections.xml</DocumentationFile>
<NoWarn>1701;1702;1705;1591</NoWarn>
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
<TreatSpecificWarningsAsErrors />
</PropertyGroup>

<ItemGroup>
<PackageReference Include="LibLog" Version="4.2.6" />
<PackageReference Include="Microsoft.CSharp" Version="4.3.0" />
<PackageReference Include="System.Dynamic.Runtime" Version="4.3.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\LiquidProjections.Abstractions\LiquidProjections.Abstractions.csproj" />
</ItemGroup>

</Project>
47 changes: 47 additions & 0 deletions Src/LiquidProjections/Statistics/IProjectionStats.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;

namespace LiquidProjections.Statistics
{
public interface IProjectionStats : IEnumerable<IProjectorStats>
{
/// <summary>
/// Should be called to track the progress of a projector and use that to calculate an ETA.
/// </summary>
void TrackProgress(string projectorId, long checkpoint);

/// <summary>
/// Can be used to store projector-specific properties that characterize the projector's configuration or state.
/// </summary>
/// <remarks>
/// Each property is identified by a <paramref name="name"/>. This class only keeps the latest value
/// for each property.
/// </remarks>
void StoreProperty(string projectorId, string name, string value);

/// <summary>
/// Can be used to store information that happened that can help diagnose the state or failure of a projector.
/// </summary>
void LogEvent(string projectorId, string body);

/// <summary>
/// Gets the speed in transactions per minute based on a weighted average over the last
/// ten minutes, or <c>null</c> if there is not enough information yet.
/// </summary>
/// <param name="projectorId"></param>
float? GetSpeed(string projectorId);

/// <summary>
/// Calculates the expected time for the projector identified by <paramref name="projectorId"/> to reach a
/// certain <paramref name="targetCheckpoint"/> based on a weighted average over the last
/// ten minutes, or <c>null</c> if there is not enough information yet. Use <see cref="ProjectionStats.TrackProgress"/> to report
/// progress.
/// </summary>
TimeSpan? GetTimeToReach(string projectorId, long targetCheckpoint);

/// <summary>
/// Gets the stats for an individual projector.
/// </summary>
IProjectorStats Get(string projectorId);
}
}
20 changes: 16 additions & 4 deletions Src/LiquidProjections/Statistics/ProjectionStats.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
Expand All @@ -8,7 +9,7 @@ namespace LiquidProjections.Statistics
/// <summary>
/// Provides a thread-safe place to store all kinds of run-time information about the progress of a projector.
/// </summary>
public class ProjectionStats
public class ProjectionStats : IProjectionStats
{
private readonly Func<DateTime> nowUtc;
private readonly ConcurrentDictionary<string, ProjectorStats> stats = new ConcurrentDictionary<string, ProjectorStats>();
Expand Down Expand Up @@ -67,20 +68,31 @@ public void LogEvent(string projectorId, string body)
return this[projectorId].GetTimeToReach(targetCheckpoint);
}

/// <inheritdoc />
public IProjectorStats Get(string projectorId)
{
return this[projectorId];
}

/// <summary>
/// Gets the statistics for a particular projector.
/// </summary>
public ProjectorStats this[string projectorId]
private ProjectorStats this[string projectorId]
{
get
{
return stats.GetOrAdd(projectorId, id => new ProjectorStats(id, nowUtc));
}
}

public IEnumerable<ProjectorStats> GetForAllProjectors()
public IEnumerator<IProjectorStats> GetEnumerator()
{
return stats.Values.GetEnumerator();
}

IEnumerator IEnumerable.GetEnumerator()
{
return stats.ToArray().Select(projectorStatsById => projectorStatsById.Value);
return GetEnumerator();
}
}
}
18 changes: 17 additions & 1 deletion Src/LiquidProjections/Statistics/ProjectorStats.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,29 @@

namespace LiquidProjections.Statistics
{
public interface IProjectorStats
{
string ProjectorId { get; }
TimestampedCheckpoint LastCheckpoint { get; }

/// <summary>
/// Gets a snapshot of the properties stored for this projector at the time of calling.
/// </summary>
IDictionary<string, Property> GetProperties();

/// <summary>
/// Gets a snapshot of the events stored for this projector at the time of calling.
/// </summary>
IReadOnlyList<Event> GetEvents();
}

/// <summary>
/// Contains statistics and information about a particular projector.
/// </summary>
/// <remarks>
/// An instance of this class is safe for use in multi-threaded solutions.
/// </remarks>
public class ProjectorStats
public class ProjectorStats : IProjectorStats
{
private readonly object eventsSyncObject = new object();
private readonly object progressSyncObject = new object();
Expand Down
8 changes: 4 additions & 4 deletions Tests/LiquidProjections.Specs/ProjectionStatsSpecs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 2bef488

Please sign in to comment.