Skip to content

Commit f0b7fd6

Browse files
authored
Merge pull request #99 from dennisdoomen/Statistics
Introduce mechanism for tracking statistics over HTTP
2 parents 320afe8 + ad708f0 commit f0b7fd6

33 files changed

+1637
-49
lines changed

LiquidProjections.sln

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
Microsoft Visual Studio Solution File, Format Version 12.00
33
# Visual Studio 15
4-
VisualStudioVersion = 15.0.26430.13
4+
VisualStudioVersion = 15.0.26430.12
55
MinimumVisualStudioVersion = 10.0.40219.1
66
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{27C8B175-6555-4591-87B1-177A2874FEA9}"
77
ProjectSection(SolutionItems) = preProject
@@ -18,11 +18,13 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LiquidProjections.Specs", "
1818
EndProject
1919
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExampleHost", "Samples\ExampleHost\ExampleHost.csproj", "{1B10C576-5212-402E-AC19-8CA1547E3F34}"
2020
EndProject
21-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LiquidProjections", "Src\LiquidProjections\LiquidProjections.csproj", "{7B47454D-0129-43CB-AED5-27AFAFDB5D7C}"
21+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LiquidProjections", "Src\LiquidProjections\LiquidProjections.csproj", "{7B47454D-0129-43CB-AED5-27AFAFDB5D7C}"
2222
EndProject
23-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LiquidProjections.Testing", "Src\LiquidProjections.Testing\LiquidProjections.Testing.csproj", "{B4EA7831-8282-4F3A-A057-2DEB59611A68}"
23+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LiquidProjections.Testing", "Src\LiquidProjections.Testing\LiquidProjections.Testing.csproj", "{B4EA7831-8282-4F3A-A057-2DEB59611A68}"
2424
EndProject
25-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LiquidProjections.Abstractions", "Src\LiquidProjections.Abstractions\LiquidProjections.Abstractions.csproj", "{8E38C862-7DC9-4A7E-A2EE-921FFBC7EE2D}"
25+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LiquidProjections.Abstractions", "Src\LiquidProjections.Abstractions\LiquidProjections.Abstractions.csproj", "{8E38C862-7DC9-4A7E-A2EE-921FFBC7EE2D}"
26+
EndProject
27+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LiquidProjections.Owin", "Src\LiquidProjections.Owin\LiquidProjections.Owin.csproj", "{43C0A9ED-8094-4646-85A4-6EAA03505A20}"
2628
EndProject
2729
Global
2830
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -50,6 +52,10 @@ Global
5052
{8E38C862-7DC9-4A7E-A2EE-921FFBC7EE2D}.Debug|Any CPU.Build.0 = Debug|Any CPU
5153
{8E38C862-7DC9-4A7E-A2EE-921FFBC7EE2D}.Release|Any CPU.ActiveCfg = Release|Any CPU
5254
{8E38C862-7DC9-4A7E-A2EE-921FFBC7EE2D}.Release|Any CPU.Build.0 = Release|Any CPU
55+
{43C0A9ED-8094-4646-85A4-6EAA03505A20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
56+
{43C0A9ED-8094-4646-85A4-6EAA03505A20}.Debug|Any CPU.Build.0 = Debug|Any CPU
57+
{43C0A9ED-8094-4646-85A4-6EAA03505A20}.Release|Any CPU.ActiveCfg = Release|Any CPU
58+
{43C0A9ED-8094-4646-85A4-6EAA03505A20}.Release|Any CPU.Build.0 = Release|Any CPU
5359
EndGlobalSection
5460
GlobalSection(SolutionProperties) = preSolution
5561
HideSolutionNode = FALSE
@@ -60,5 +66,6 @@ Global
6066
{7B47454D-0129-43CB-AED5-27AFAFDB5D7C} = {AE89AB5E-BC68-4AA3-9183-A222AC81691C}
6167
{B4EA7831-8282-4F3A-A057-2DEB59611A68} = {AE89AB5E-BC68-4AA3-9183-A222AC81691C}
6268
{8E38C862-7DC9-4A7E-A2EE-921FFBC7EE2D} = {AE89AB5E-BC68-4AA3-9183-A222AC81691C}
69+
{43C0A9ED-8094-4646-85A4-6EAA03505A20} = {AE89AB5E-BC68-4AA3-9183-A222AC81691C}
6370
EndGlobalSection
6471
EndGlobal

Samples/ExampleHost/App.config

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<configuration>
33
<startup>
4-
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
4+
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
55
</startup>
66
<runtime>
77
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">

Samples/ExampleHost/CountsProjector.cs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,23 @@
22
using System.Collections.Generic;
33
using System.Linq;
44
using LiquidProjections.ExampleHost.Events;
5+
using LiquidProjections.Statistics;
56

67
namespace LiquidProjections.ExampleHost
78
{
89
public class CountsProjector
910
{
1011
private readonly Dispatcher dispatcher;
1112
private readonly InMemoryDatabase store;
13+
private readonly ProjectionStats stats;
1214
private ExampleProjector<DocumentCountProjection> documentCountProjector;
1315
private ExampleProjector<CountryLookup> countryLookupProjector;
1416

15-
public CountsProjector(Dispatcher dispatcher, InMemoryDatabase store)
17+
public CountsProjector(Dispatcher dispatcher, InMemoryDatabase store, ProjectionStats stats)
1618
{
1719
this.dispatcher = dispatcher;
1820
this.store = store;
21+
this.stats = stats;
1922

2023
BuildCountryProjector();
2124
BuildDocumentProjector();
@@ -210,8 +213,11 @@ private void BuildDocumentProjector()
210213
period.Status = "Canceled";
211214
});
212215

213-
documentCountProjector =
214-
new ExampleProjector<DocumentCountProjection>(documentMapBuilder, store, countryLookupProjector);
216+
documentCountProjector =
217+
new ExampleProjector<DocumentCountProjection>(documentMapBuilder, store, stats, countryLookupProjector)
218+
{
219+
Id = "DocumentCount"
220+
};
215221
}
216222

217223
private string GetCountryName(Guid countryCode)
@@ -229,7 +235,10 @@ private void BuildCountryProjector()
229235
.AsCreateOf(anEvent => anEvent.Code)
230236
.Using((country, anEvent) => country.Name = anEvent.Name);
231237

232-
countryLookupProjector = new ExampleProjector<CountryLookup>(countryMapBuilder, store);
238+
countryLookupProjector = new ExampleProjector<CountryLookup>(countryMapBuilder, store, stats)
239+
{
240+
Id = "CountryLookup"
241+
};
233242
}
234243

235244
private static IEnumerable<ValidityPeriod> GetPreviousContiguousValidPeriods(List<ValidityPeriod> allPeriods,

Samples/ExampleHost/ExampleHost.csproj

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
<AppDesignerFolder>Properties</AppDesignerFolder>
1010
<RootNamespace>LiquidProjections.ExampleHost</RootNamespace>
1111
<AssemblyName>LiquidProjections.ExampleHost</AssemblyName>
12-
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
12+
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
1313
<FileAlignment>512</FileAlignment>
1414
<TargetFrameworkProfile />
1515
</PropertyGroup>
@@ -135,11 +135,18 @@
135135
<Project>{8e38c862-7dc9-4a7e-a2ee-921ffbc7ee2d}</Project>
136136
<Name>LiquidProjections.Abstractions</Name>
137137
</ProjectReference>
138+
<ProjectReference Include="..\..\Src\LiquidProjections.Owin\LiquidProjections.Owin.csproj">
139+
<Project>{43c0a9ed-8094-4646-85a4-6eaa03505a20}</Project>
140+
<Name>LiquidProjections.Owin</Name>
141+
</ProjectReference>
138142
<ProjectReference Include="..\..\Src\LiquidProjections\LiquidProjections.csproj">
139143
<Project>{7b47454d-0129-43cb-aed5-27afafdb5d7c}</Project>
140144
<Name>LiquidProjections</Name>
141145
</ProjectReference>
142146
</ItemGroup>
147+
<ItemGroup>
148+
<WCFMetadata Include="Connected Services\" />
149+
</ItemGroup>
143150
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
144151
<PropertyGroup>
145152
<PostBuildEvent>

Samples/ExampleHost/ExampleProjector.cs

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
using System;
2-
using System.Collections.Generic;
1+
using System.Collections.Generic;
32
using System.Linq;
43
using System.Threading.Tasks;
4+
using LiquidProjections.Statistics;
55

66
namespace LiquidProjections.ExampleHost
77
{
@@ -13,14 +13,16 @@ public class ExampleProjector<TProjection> : IExampleProjector
1313
where TProjection : class, IEntity, new()
1414
{
1515
private readonly InMemoryDatabase store;
16-
private readonly Projector innerProjector;
16+
private readonly ProjectionStats stats;
1717

18-
public ExampleProjector(IEventMapBuilder<TProjection, string, ProjectionContext> mapBuilder, InMemoryDatabase store, params IExampleProjector[] childProjectors)
18+
public ExampleProjector(IEventMapBuilder<TProjection, string, ProjectionContext> mapBuilder, InMemoryDatabase store,
19+
ProjectionStats stats, params IExampleProjector[] childProjectors)
1920
{
2021
this.store = store;
22+
this.stats = stats;
2123
var map = BuildMapFrom(mapBuilder);
2224

23-
innerProjector = new Projector(map, childProjectors.Select(p => p.InnerProjector));
25+
InnerProjector = new Projector(map, childProjectors.Select(p => p.InnerProjector));
2426
}
2527

2628
private IEventMap<ProjectionContext> BuildMapFrom(IEventMapBuilder<TProjection, string, ProjectionContext> mapBuilder)
@@ -53,16 +55,20 @@ private IEventMap<ProjectionContext> BuildMapFrom(IEventMapBuilder<TProjection,
5355
return mapBuilder.Build();
5456
}
5557

56-
public Task Handle(IReadOnlyList<Transaction> transactions)
58+
public async Task Handle(IReadOnlyList<Transaction> transactions)
5759
{
58-
return innerProjector.Handle(transactions);
60+
await InnerProjector.Handle(transactions);
61+
62+
stats.TrackProgress(Id, transactions.Last().Checkpoint);
5963
}
6064

61-
public Projector InnerProjector => innerProjector;
65+
public Projector InnerProjector { get; }
66+
67+
public string Id { get; set; }
6268
}
6369

6470
public interface IExampleProjector
6571
{
6672
Projector InnerProjector { get; }
6773
}
68-
}
74+
}

Samples/ExampleHost/Program.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
using System.IO;
44
using System.Web.Http;
55
using System.Web.Http.Dispatcher;
6+
using LiquidProjections.Owin;
7+
using LiquidProjections.Statistics;
68
using Microsoft.Owin.Hosting;
79
using Owin;
810
using TinyIoC;
@@ -22,10 +24,16 @@ public static void Main(string[] args)
2224

2325
var dispatcher = new Dispatcher(eventStore.Subscribe);
2426

25-
var bootstrapper = new CountsProjector(dispatcher, projectionsStore);
27+
var stats = new ProjectionStats(() => DateTime.UtcNow);
28+
29+
var bootstrapper = new CountsProjector(dispatcher, projectionsStore, stats);
2630

2731
var startOptions = new StartOptions($"http://localhost:9000");
28-
using (WebApp.Start(startOptions, builder => builder.UseControllers(container)))
32+
using (WebApp.Start(startOptions, builder =>
33+
{
34+
builder.UseControllers(container);
35+
builder.UseLiquidProjections(stats);
36+
}))
2937
{
3038
bootstrapper.Start();
3139

Samples/ExampleHost/Properties/AssemblyInfo.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@
3131
//
3232
// You can specify all the values or you can default the Build and Revision Numbers
3333
// by using the '*' as shown below:
34-
// [assembly: AssemblyVersion("2.0.0.0")]
35-
[assembly: AssemblyVersion("2.0.0.0")]
36-
[assembly: AssemblyFileVersion("2.0.0.0")]
34+
// [assembly: AssemblyVersion("2.1.1.0")]
35+
[assembly: AssemblyVersion("2.1.1.0")]
36+
[assembly: AssemblyFileVersion("2.1.1.0")]
3737

38-
[assembly: AssemblyInformationalVersion("2.0.0+Branch.master.Sha.6e68f840c3303bac97b10c567815a3bfef3184d7")]
38+
[assembly: AssemblyInformationalVersion("2.1.1-Statistics.1+2.Branch.Statistics.Sha.d204fd580c5bb1f85bdcdd4819af6123c7315da8")]

Src/LiquidProjections.Abstractions/AssemblyInfo.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@
77

88
[assembly: ComVisible(false)]
99

10-
[assembly: AssemblyVersion("2.0.0.0")]
11-
[assembly: AssemblyFileVersion("2.0.0.0")]
12-
[assembly: AssemblyInformationalVersion("2.0.0+Branch.master.Sha.6e68f840c3303bac97b10c567815a3bfef3184d7")]
10+
[assembly: AssemblyVersion("2.1.1.0")]
11+
[assembly: AssemblyFileVersion("2.1.1.0")]
12+
[assembly: AssemblyInformationalVersion("2.1.1-Statistics.1+2.Branch.Statistics.Sha.d204fd580c5bb1f85bdcdd4819af6123c7315da8")]

Src/LiquidProjections.Owin/.nuspec

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?xml version="1.0"?>
2+
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
3+
<metadata>
4+
<id>LiquidProjections.Owin</id>
5+
<version>0.0.0.0</version>
6+
<authors>Dennis Doomen</authors>
7+
<owners>Dennis Doomen</owners>
8+
<projectUrl>https://github.com/liquidprojections/LiquidProjections</projectUrl>
9+
<requireLicenseAcceptance>false</requireLicenseAcceptance>
10+
<description>Provides OWIN Middleware to access LiquidProjection over HTTP.</description>
11+
<tags>event-sourcing; projections; ddd; owin; webapi</tags>
12+
<frameworkAssemblies />
13+
<releaseNotes/>
14+
<dependencies>
15+
<group>
16+
<dependency id="LiquidProjections" version="$nugetversion$"/>
17+
<dependency id="Owin" version="1.0.0.0"/>
18+
</group>
19+
</dependencies>
20+
</metadata>
21+
<files>
22+
<file src="..\..\Artifacts\LiquidProjections.Owin.dll" target="lib\net452" />
23+
<file src="..\..\Artifacts\LiquidProjections.Owin.pdb" target="lib\net452" />
24+
<file src="..\..\Artifacts\LiquidProjections.Owin.xml" target="lib\net452" />
25+
</files>
26+
</package>
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
using System;
2+
using System.Collections.Concurrent;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Reflection;
6+
using LiquidProjections.Statistics;
7+
using Nancy;
8+
using Nancy.Bootstrapper;
9+
using Nancy.Configuration;
10+
using Nancy.Extensions;
11+
using Nancy.Linker;
12+
using Nancy.Metadata.Modules;
13+
using Nancy.Routing;
14+
using Nancy.Swagger;
15+
using Nancy.Swagger.Modules;
16+
using Nancy.Swagger.Services;
17+
using Nancy.TinyIoc;
18+
19+
namespace LiquidProjections.Owin
20+
{
21+
internal class CustomNancyBootstrapper : DefaultNancyBootstrapper
22+
{
23+
private readonly ProjectionStats stats;
24+
25+
public CustomNancyBootstrapper(ProjectionStats stats)
26+
{
27+
this.stats = stats;
28+
}
29+
30+
protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines)
31+
{
32+
base.ApplicationStartup(container, pipelines);
33+
34+
SwaggerMetadataProvider.SetInfo(
35+
"LiquidProjections",
36+
Assembly.GetExecutingAssembly().GetName().Version.ToString(),
37+
"Provides statistics about running projectors"
38+
);
39+
}
40+
41+
#if DEBUG
42+
43+
public override void Configure(INancyEnvironment environment)
44+
{
45+
environment.Tracing(enabled: false, displayErrorTraces: true);
46+
base.Configure(environment);
47+
}
48+
#endif
49+
50+
protected override IAssemblyCatalog AssemblyCatalog => new StaticAssemblyCatalog();
51+
52+
protected override ITypeCatalog TypeCatalog => new InternalTypeCatalog(AssemblyCatalog);
53+
54+
protected override void ConfigureApplicationContainer(TinyIoCContainer container)
55+
{
56+
container.Register<IResourceLinker>((x, overloads) =>
57+
new ResourceLinker(x.Resolve<IRouteCacheProvider>(),
58+
x.Resolve<IRouteSegmentExtractor>(), x.Resolve<IUriFilter>()));
59+
60+
container.Register<IRegistrations, Registration>("LinkerRegistrations");
61+
container.Register<IRegistrations, SwaggerRegistrations>("SwaggerRegistrations");
62+
container.Register(stats);
63+
}
64+
65+
protected override IEnumerable<ModuleRegistration> Modules =>
66+
new[]
67+
{
68+
new ModuleRegistration(typeof(SwaggerModule)),
69+
new ModuleRegistration(typeof(StatisticsModule))
70+
};
71+
}
72+
73+
internal class StaticAssemblyCatalog : IAssemblyCatalog
74+
{
75+
public IReadOnlyCollection<Assembly> GetAssemblies()
76+
{
77+
return new[]
78+
{
79+
typeof(MetadataModuleRegistrations).Assembly,
80+
typeof(CustomNancyBootstrapper).Assembly,
81+
typeof(DefaultNancyBootstrapper).Assembly
82+
}.Distinct().ToArray();
83+
}
84+
}
85+
86+
/// <summary>
87+
/// Implementation of the <see cref="T:Nancy.ITypeCatalog" /> interface that will find internal types as well.
88+
/// </summary>
89+
internal class InternalTypeCatalog : ITypeCatalog
90+
{
91+
private readonly IAssemblyCatalog assemblyCatalog;
92+
private readonly ConcurrentDictionary<Type, IReadOnlyCollection<Type>> cache;
93+
94+
/// <summary>
95+
/// Initializes a new instance of the <see cref="T:Nancy.DefaultTypeCatalog" /> class.
96+
/// </summary>
97+
/// <param name="assemblyCatalog">An <see cref="T:Nancy.IAssemblyCatalog" /> instanced, used to get the assemblies that types should be resolved from.</param>
98+
public InternalTypeCatalog(IAssemblyCatalog assemblyCatalog)
99+
{
100+
this.assemblyCatalog = assemblyCatalog;
101+
cache = new ConcurrentDictionary<Type, IReadOnlyCollection<Type>>();
102+
}
103+
104+
/// <summary>
105+
/// Gets all types that are assignable to the provided <paramref name="type" />.
106+
/// </summary>
107+
/// <param name="type">The <see cref="T:System.Type" /> that returned types should be assignable to.</param>
108+
/// <param name="strategy">A <see cref="T:Nancy.TypeResolveStrategy" /> that should be used when retrieving types.</param>
109+
/// <returns>An <see cref="T:System.Collections.Generic.IReadOnlyCollection`1" /> of <see cref="T:System.Type" /> instances.</returns>
110+
public IReadOnlyCollection<Type> GetTypesAssignableTo(Type type, TypeResolveStrategy strategy)
111+
{
112+
return cache.GetOrAdd(type, t => GetTypesAssignableTo(type))
113+
.Where(strategy.Invoke).ToArray();
114+
}
115+
116+
private IReadOnlyCollection<Type> GetTypesAssignableTo(Type type)
117+
{
118+
return assemblyCatalog.GetAssemblies()
119+
.SelectMany(assembly => assembly.SafeGetTypes())
120+
.Where(type.IsAssignableFrom)
121+
.Where(t => !t.GetTypeInfo().IsAbstract).ToArray();
122+
}
123+
}
124+
}

0 commit comments

Comments
 (0)