Skip to content

Class PartitionKeyProviderFactory ​

Namespace: Momentum.Extensions.Messaging.Kafka
Assembly: Momentum.Extensions.Messaging.Kafka.dll

csharp
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

.
csharp
public static Func<TMessage, string>? GetPartitionKeyFunction<TMessage>()

Returns ​

Func<TMessage, string>?

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:

csharp
instance => {partitionKeyProperty1}.ToString() + "-" + {partitionKeyProperty2}.ToString()...

Expression Tree Compilation ​

Single Property Scenario ​

For messages with a single partition key property:

csharp
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:

csharp
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:

csharp
var partitionKeyProperties = messageType.GetPropertiesWithAttribute<PartitionKeyAttribute>();

Order Resolution ​

Properties are ordered based on:

  1. Explicit Order: PartitionKeyAttribute.Order property
  2. Alphabetical: Property name as secondary sort
csharp
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:

csharp
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 ​

csharp
public record OrderCreated(
    [PartitionKey] Guid CustomerId,
    Guid OrderId,
    decimal Amount
);

// Generated: customer => customer.CustomerId.ToString()

Multiple Partition Keys with Ordering ​

csharp
public record ProductUpdated(
    [PartitionKey(Order = 1)] string Category,
    [PartitionKey(Order = 0)] Guid StoreId,
    Guid ProductId
);

// Generated: product => product.StoreId.ToString() + "-" + product.Category.ToString()