Skip to content

Commit 7ecd316

Browse files
Add ProjectionNameResolver and update all components to use it
Co-authored-by: JeanMarcMbouma <16613177+JeanMarcMbouma@users.noreply.github.com>
1 parent 08d101b commit 7ecd316

File tree

4 files changed

+64
-8
lines changed

4 files changed

+64
-8
lines changed

src/BbQ.Events/Engine/DefaultProjectionEngine.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -377,16 +377,20 @@ private static ProjectionOptions GetProjectionOptions(Type concreteType)
377377
// Fall back to reading from attribute
378378
var attribute = concreteType.GetCustomAttribute<ProjectionAttribute>();
379379

380-
return new ProjectionOptions
380+
var options = new ProjectionOptions
381381
{
382-
ProjectionName = concreteType.Name,
383382
MaxDegreeOfParallelism = attribute?.MaxDegreeOfParallelism ?? 1,
384383
CheckpointBatchSize = attribute?.CheckpointBatchSize ?? 100,
385384
StartupMode = attribute?.StartupMode ?? ProjectionStartupMode.Resume,
386385
ChannelCapacity = attribute?.ChannelCapacity ?? 1000,
387386
BackpressureStrategy = attribute?.BackpressureStrategy ?? BackpressureStrategy.Block,
388387
// ErrorHandling is already initialized by property initializer to default values
389388
};
389+
390+
// Use ProjectionNameResolver for consistent name resolution
391+
options.ProjectionName = ProjectionNameResolver.Resolve(concreteType, registeredOptions);
392+
393+
return options;
390394
}
391395

392396
/// <summary>

src/BbQ.Events/Engine/DefaultProjectionRebuilder.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,9 @@ public IEnumerable<string> GetRegisteredProjections()
134134
var registration = ProjectionHandlerRegistry.GetHandlerRegistration(eventType, handlerType);
135135
if (registration != null)
136136
{
137-
projectionNames.Add(registration.ConcreteType.Name);
137+
var projectionOptions = ProjectionHandlerRegistry.GetProjectionOptions(registration.ConcreteType.Name);
138+
var resolvedName = ProjectionNameResolver.Resolve(registration.ConcreteType, projectionOptions);
139+
projectionNames.Add(resolvedName);
138140
}
139141
}
140142
}

src/BbQ.Events/Engine/DefaultReplayService.cs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,9 @@ public async Task ReplayAsync(
102102
var registration = ProjectionHandlerRegistry.GetHandlerRegistration(eventType, handlerType);
103103
if (registration != null)
104104
{
105-
registeredProjections.Add(registration.ConcreteType.Name);
105+
var projectionOptions = ProjectionHandlerRegistry.GetProjectionOptions(registration.ConcreteType.Name);
106+
var resolvedName = ProjectionNameResolver.Resolve(registration.ConcreteType, projectionOptions);
107+
registeredProjections.Add(resolvedName);
106108
}
107109
}
108110
}
@@ -241,13 +243,18 @@ private async Task StreamEventsAndProcessAsync(
241243
foreach (var handlerType in handlers)
242244
{
243245
var registration = ProjectionHandlerRegistry.GetHandlerRegistration(eventType, handlerType);
244-
if (registration != null && registration.ConcreteType.Name == projectionName)
246+
if (registration != null)
245247
{
246-
if (!projectionHandlers.ContainsKey(eventType))
248+
var projectionOptions = ProjectionHandlerRegistry.GetProjectionOptions(registration.ConcreteType.Name);
249+
var resolvedName = ProjectionNameResolver.Resolve(registration.ConcreteType, projectionOptions);
250+
if (resolvedName == projectionName)
247251
{
248-
projectionHandlers[eventType] = new List<Type>();
252+
if (!projectionHandlers.ContainsKey(eventType))
253+
{
254+
projectionHandlers[eventType] = new List<Type>();
255+
}
256+
projectionHandlers[eventType].Add(handlerType);
249257
}
250-
projectionHandlers[eventType].Add(handlerType);
251258
}
252259
}
253260
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
namespace BbQ.Events.Engine;
2+
3+
/// <summary>
4+
/// Provides consistent projection name resolution across all projection components.
5+
/// </summary>
6+
/// <remarks>
7+
/// This resolver ensures that projection names are resolved using the same logic everywhere:
8+
/// - DefaultProjectionEngine (runtime processing)
9+
/// - DefaultReplayService (replay operations)
10+
/// - DefaultProjectionRebuilder (rebuild operations)
11+
///
12+
/// Resolution logic:
13+
/// 1. If ProjectionOptions.ProjectionName is set and not empty → use it
14+
/// 2. Otherwise → use the projection type name
15+
///
16+
/// This consistency prevents mismatched projection identifiers between runtime processing
17+
/// and replay/rebuild operations.
18+
/// </remarks>
19+
public static class ProjectionNameResolver
20+
{
21+
/// <summary>
22+
/// Resolves the projection name for a given projection type and options.
23+
/// </summary>
24+
/// <param name="projectionType">The concrete type of the projection handler.</param>
25+
/// <param name="options">Optional projection options that may contain an explicit projection name.</param>
26+
/// <returns>The resolved projection name.</returns>
27+
public static string Resolve(Type projectionType, ProjectionOptions? options)
28+
{
29+
if (projectionType == null)
30+
{
31+
throw new ArgumentNullException(nameof(projectionType));
32+
}
33+
34+
// If ProjectionOptions.ProjectionName is explicitly set, use it
35+
if (!string.IsNullOrWhiteSpace(options?.ProjectionName))
36+
{
37+
return options.ProjectionName;
38+
}
39+
40+
// Otherwise, fall back to the projection type name
41+
return projectionType.Name;
42+
}
43+
}

0 commit comments

Comments
 (0)