Class PartitionKeyProviderFactory
Namespace: Momentum.Extensions.Messaging.Kafka
Assembly: Momentum.Extensions.Messaging.Kafka.dll
public static class PartitionKeyProviderFactory
Inheritance
object ← PartitionKeyProviderFactory
Inherited Members
object.GetType(), object.MemberwiseClone(), object.ToString(), object.Equals(object?), object.Equals(object?, object?), object.ReferenceEquals(object?, object?), object.GetHashCode()
Methods
GetPartitionKeyFunction<TMessage>()
Creates a compiled expression that efficiently retrieves the partition key as a string based on the properties marked with the
.public static Func<TMessage, string>? GetPartitionKeyFunction<TMessage>()
Returns
Type Parameters
TMessage
The type of the object containing the partition key properties.
Remarks
Partition Key Provider Factory
Performance-Optimized Key Generation
This method is called on every event published, therefore performance is important, that is why I'm using expression trees instead of reflection.
Generated Code Pattern
The factory generates compiled expressions that efficiently retrieve partition keys:
instance => {partitionKeyProperty1}.ToString() + "-" + {partitionKeyProperty2}.ToString()...
Expression Tree Compilation
Single Property Scenario
For messages with a single partition key property:
var singlePropertyExpression = Expression.Lambda<Func<TMessage, string>>(
Expression.Call(convertToString,
Expression.Convert(Expression.Property(parameter, prop), typeof(object))),
parameter
).Compile();
Multiple Properties Scenario
For messages with multiple partition key properties, values are concatenated with hyphens:
var concatMethod = typeof(string).GetMethod(nameof(string.Concat), [typeof(string[])])!;
var expressionsWithSeparator = new List<Expression>();
for (var i = 0; i < stringValueExpressions.Count; i++)
{
expressionsWithSeparator.Add(stringValueExpressions[i]);
if (i < stringValueExpressions.Count - 1)
{
expressionsWithSeparator.Add(Expression.Constant("-"));
}
}
var combinedExpression = Expression.Call(concatMethod,
Expression.NewArrayInit(typeof(string), expressionsWithSeparator));
Property Discovery and Ordering
Attribute-Based Discovery
Properties are discovered using the PartitionKeyAttribute
:
var partitionKeyProperties = messageType.GetPropertiesWithAttribute<PartitionKeyAttribute>();
Order Resolution
Properties are ordered based on:
- Explicit Order:
PartitionKeyAttribute.Order
property - Alphabetical: Property name as secondary sort
var orderedPartitionKeyProperties = partitionKeyProperties
.OrderBy(p => p.GetCustomAttribute<PartitionKeyAttribute>(primaryConstructor)?.Order ?? 0)
.ThenBy(p => p.Name).ToArray();
Primary Constructor Support
The factory supports records and classes with primary constructors by examining constructor parameters for partition key attributes:
var primaryConstructor = messageType.GetPrimaryConstructor();
var attribute = property.GetCustomAttribute<PartitionKeyAttribute>(primaryConstructor);
Compilation Performance
Expression Tree Benefits
- Compile-time Generation: Expressions compiled once per message type
- Runtime Efficiency: No reflection overhead during message publishing
- Type Safety: Compile-time validation of property access
- Memory Efficiency: Minimal allocation during key generation
Caching Strategy
The generated functions are typically cached by the calling infrastructure, ensuring:
- One-time Compilation: Per message type compilation cost
- Reusable Functions: Cached delegates for repeated use
- Minimal Memory Footprint: Single delegate per message type
Usage Examples
Single Partition Key
public record OrderCreated(
[PartitionKey] Guid CustomerId,
Guid OrderId,
decimal Amount
);
// Generated: customer => customer.CustomerId.ToString()
Multiple Partition Keys with Ordering
public record ProductUpdated(
[PartitionKey(Order = 1)] string Category,
[PartitionKey(Order = 0)] Guid StoreId,
Guid ProductId
);
// Generated: product => product.StoreId.ToString() + "-" + product.Category.ToString()