Skip to content

Commit

Permalink
[DEVEX-222] Removed storing CLR type in metadata and moved it to mess…
Browse files Browse the repository at this point in the history
…age type name

Now the FullTypeName is placed in the event type name. Not perfect but here it is.
  • Loading branch information
oskardudycz committed Feb 7, 2025
1 parent 852e367 commit d414d06
Show file tree
Hide file tree
Showing 5 changed files with 30 additions and 150 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -82,78 +82,4 @@ static ReadOnlyMemory<byte> TryInjectTracingMetadata(
return utf8Json;
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static SerializationMetadata ExtractSerializationMetadata(this ReadOnlyMemory<byte> eventMetadata) {
if (eventMetadata.IsEmpty)
return SerializationMetadata.None;

var reader = new Utf8JsonReader(eventMetadata.Span);
try {
if (!JsonDocument.TryParseValue(ref reader, out var doc))
return SerializationMetadata.None;

using (doc) {
if (!doc.RootElement.TryGetProperty(
SerializationMetadata.Constants.MessageTypeAssemblyQualifiedName,
out var messageTypeAssemblyQualifiedName
)
|| !doc.RootElement.TryGetProperty(
SerializationMetadata.Constants.MessageTypeClrTypeName,
out var messageTypeClrTypeName
))
return SerializationMetadata.None;

return new SerializationMetadata(
messageTypeAssemblyQualifiedName.GetString(),
messageTypeClrTypeName.GetString()
);
}
} catch (Exception) {
return SerializationMetadata.None;
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlyMemory<byte> InjectSerializationMetadata(
this ReadOnlyMemory<byte> eventMetadata, SerializationMetadata serializationMetadata
) {
if (serializationMetadata == SerializationMetadata.None || !serializationMetadata.IsValid)
return ReadOnlyMemory<byte>.Empty;

return eventMetadata.IsEmpty
? JsonSerializer.SerializeToUtf8Bytes(serializationMetadata)
: TryInjectSerializationMetadata(eventMetadata, serializationMetadata).ToArray();
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
static ReadOnlyMemory<byte> TryInjectSerializationMetadata(
this ReadOnlyMemory<byte> utf8Json, SerializationMetadata serializationMetadata
) {
try {
using var doc = JsonDocument.Parse(utf8Json);
using var stream = new MemoryStream();
using var writer = new Utf8JsonWriter(stream);

writer.WriteStartObject();

if (doc.RootElement.ValueKind != JsonValueKind.Object)
return utf8Json;

foreach (var prop in doc.RootElement.EnumerateObject())
prop.WriteTo(writer);

writer.WritePropertyName(SerializationMetadata.Constants.MessageTypeAssemblyQualifiedName);
writer.WriteStringValue(serializationMetadata.MessageTypeAssemblyQualifiedName);
writer.WritePropertyName(SerializationMetadata.Constants.MessageTypeClrTypeName);
writer.WriteStringValue(serializationMetadata.MessageTypeClrTypeName);

writer.WriteEndObject();
writer.Flush();

return stream.ToArray();
} catch (Exception) {
return utf8Json;
}
}
}
10 changes: 3 additions & 7 deletions src/Kurrent.Client/Core/Serialization/MessageSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public EventData Serialize(Message message, MessageSerializationContext serializ
var (data, metadata, eventId) = message;

var eventType = _messageTypeNamingStrategy
.ResolveTypeName(message, serializationContext);
.ResolveTypeName(message.Data.GetType(), serializationContext);

var serializedData = schemaRegistry
.GetSerializer(serializationContext.ContentType)
Expand All @@ -76,15 +76,11 @@ public EventData Serialize(Message message, MessageSerializationContext serializ
? _jsonSerializer.Serialize(metadata)
: ReadOnlyMemory<byte>.Empty;

var metadataWithSerialization = serializedMetadata
.InjectSerializationMetadata(SerializationMetadata.From(data.GetType()))
.ToArray();

return new EventData(
eventId ?? Uuid.NewUuid(),
eventType,
serializedData,
metadataWithSerialization,
serializedMetadata,
serializationContext.ContentType.ToMessageContentType()
);
}
Expand All @@ -96,7 +92,7 @@ public bool TryDeserialize(EventRecord messageRecord, [NotNullWhen(true)] out ob
#endif
if (!schemaRegistry
.MessageTypeNamingStrategy
.TryResolveClrType(messageRecord, out var clrType)) {
.TryResolveClrType(messageRecord.EventType, out var clrType)) {
deserialized = null;
return false;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,40 +1,37 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using Kurrent.Client.Tests.Streams.Serialization;
using EventStore.Client.Diagnostics;
using Kurrent.Client.Core.Serialization;

namespace EventStore.Client.Serialization;

public interface IMessageTypeNamingStrategy {
string ResolveTypeName(Message message, MessageSerializationContext serializationContext);
string ResolveTypeName(Type messageType, MessageSerializationContext serializationContext);

#if NET48
bool TryResolveClrType(EventRecord messageRecord, out Type? type);
bool TryResolveClrType(string messageTypeName, out Type? type);
#else
bool TryResolveClrType(EventRecord messageRecord, [NotNullWhen(true)] out Type? type);
bool TryResolveClrType(string messageTypeName, [NotNullWhen(true)] out Type? type);
#endif
}

public class MessageTypeNamingStrategyWrapper(
IMessageTypeRegistry messageTypeRegistry,
IMessageTypeNamingStrategy messageTypeNamingStrategy
) : IMessageTypeNamingStrategy {
public string ResolveTypeName(Message message, MessageSerializationContext serializationContext) {
public string ResolveTypeName(Type messageType, MessageSerializationContext serializationContext) {
return messageTypeRegistry.GetOrAddTypeName(
message.Data.GetType(),
_ => messageTypeNamingStrategy.ResolveTypeName(message, serializationContext)
messageType,
_ => messageTypeNamingStrategy.ResolveTypeName(messageType, serializationContext)
);
}

#if NET48
public bool TryResolveClrType(EventRecord messageRecord, out Type? type) {
public bool TryResolveClrType(string messageTypeName, out Type? type) {
#else
public bool TryResolveClrType(EventRecord messageRecord, [NotNullWhen(true)] out Type? type) {
public bool TryResolveClrType(string messageTypeName, [NotNullWhen(true)] out Type? type) {
#endif
type = messageTypeRegistry.GetOrAddClrType(
messageRecord.EventType,
_ => messageTypeNamingStrategy.TryResolveClrType(messageRecord, out var resolvedType)
messageTypeName,
_ => messageTypeNamingStrategy.TryResolveClrType(messageTypeName, out var resolvedType)
? resolvedType
: null
);
Expand All @@ -43,27 +40,25 @@ public bool TryResolveClrType(EventRecord messageRecord, [NotNullWhen(true)] out
}
}

public class DefaultMessageTypeNamingStrategy
: IMessageTypeNamingStrategy {
public string ResolveTypeName(Message message, MessageSerializationContext serializationContext) =>
$"{serializationContext.CategoryName}-{JsonNamingPolicy.SnakeCaseLower.ConvertName(message.Data.GetType().Name.ToLower())}";
public class DefaultMessageTypeNamingStrategy: IMessageTypeNamingStrategy {
public string ResolveTypeName(Type messageType, MessageSerializationContext serializationContext) =>
$"{serializationContext.CategoryName}-{messageType.FullName}";

#if NET48
public bool TryResolveClrType(EventRecord messageRecord, out Type? type) {
public bool TryResolveClrType(string messageTypeName, out Type? type) {
#else
public bool TryResolveClrType(EventRecord messageRecord, [NotNullWhen(true)] out Type? type) {
public bool TryResolveClrType(string messageTypeName, [NotNullWhen(true)] out Type? type) {
#endif
var serializationMetadata = messageRecord.Metadata.ExtractSerializationMetadata();
var categorySeparatorIndex = messageTypeName.IndexOf('-');

if (!serializationMetadata.IsValid) {
if (categorySeparatorIndex == -1 || categorySeparatorIndex == messageTypeName.Length - 1) {
type = null;
return false;
}

type = Type.GetType(serializationMetadata.MessageTypeAssemblyQualifiedName!)
?? TypeProvider.GetFirstMatchingTypeFromCurrentDomainAssembly(
serializationMetadata.MessageTypeClrTypeName!
);
var clrTypeName = messageTypeName[(categorySeparatorIndex + 1)..];

type = TypeProvider.GetTypeWithAutoLoad(clrTypeName);

return type != null;
}
Expand Down
23 changes: 0 additions & 23 deletions src/Kurrent.Client/Core/Serialization/SerializationMetadata.cs

This file was deleted.

28 changes: 7 additions & 21 deletions src/Kurrent.Client/Core/Serialization/TypeProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,11 @@

namespace Kurrent.Client.Tests.Streams.Serialization;

public static class TypeProvider
{
public static Type? GetTypeFromAnyReferencingAssembly(string typeName)
{
var referencedAssemblies = Assembly.GetEntryAssembly()?
.GetReferencedAssemblies()
.Select(a => a.FullName);

if (referencedAssemblies == null)
return null;

return AppDomain.CurrentDomain.GetAssemblies()
.Where(a => referencedAssemblies.Contains(a.FullName))
.SelectMany(a => a.GetTypes().Where(x => x.AssemblyQualifiedName == typeName || x.Name == typeName))
.FirstOrDefault();
}

public static Type? GetFirstMatchingTypeFromCurrentDomainAssembly(string typeName) =>
AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(a => a.GetTypes().Where(x => x.AssemblyQualifiedName == typeName || x.Name == typeName))
.FirstOrDefault();
static class TypeProvider {
public static Type? GetTypeWithAutoLoad(string typeName) =>
Type.GetType(
typeName,
assemblyResolver: Assembly.Load,
typeResolver: null
);
}

0 comments on commit d414d06

Please sign in to comment.