#1004 Отделить логику маппинга от клиентов + #1000 Добавить множественный маппинг сущностей в TimeStampedClient #31

Merged
on.nemtina merged 11 commits from feature-mapping into master 2025-03-03 09:44:27 +05:00
9 changed files with 97 additions and 54 deletions
Showing only changes of commit 2dca1cc722 - Show all commits

View File

@ -0,0 +1,6 @@
namespace DD.Persistence.Client.Clients.Mapping.Abstractions;
internal interface IMapperStorage
{
TimestampedSetMapper GetMapper<T>(Guid idDiscriminator);
TimestampedSetMapper? GetMapper(Guid idDiscriminator);
}

View File

@ -2,13 +2,23 @@
using DD.Persistence.Client.Clients.Mapping.Abstractions; using DD.Persistence.Client.Clients.Mapping.Abstractions;
using DD.Persistence.Models; using DD.Persistence.Models;
using DD.Persistence.Models.Common; using DD.Persistence.Models.Common;
using DD.Persistence.Models.Configurations;
using System.Text.Json; using System.Text.Json;
namespace DD.Persistence.Client.Clients.Mapping.Clients; namespace DD.Persistence.Client.Clients.Mapping.Clients;
/// <inheritdoc/> /// <inheritdoc/>
public class SetpointMappingClient(ISetpointClient setpointClient, Dictionary<Guid, Type> mappingConfigs) : ISetpointMappingClient public class SetpointMappingClient : ISetpointMappingClient
{ {
private readonly ISetpointClient setpointClient;
private readonly MappingConfig mappingConfigs;
public SetpointMappingClient(ISetpointClient setpointClient, MappingConfig mappingConfigs)
{
this.setpointClient = setpointClient;
this.mappingConfigs = mappingConfigs;
}
/// <inheritdoc/> /// <inheritdoc/>
public async Task<IEnumerable<SetpointValueDto>> GetCurrent(IEnumerable<Guid> setpointKeys, CancellationToken token) public async Task<IEnumerable<SetpointValueDto>> GetCurrent(IEnumerable<Guid> setpointKeys, CancellationToken token)
=> (await setpointClient.GetCurrent(setpointKeys, token)) => (await setpointClient.GetCurrent(setpointKeys, token))

View File

@ -2,21 +2,27 @@
using DD.Persistence.Client.Clients.Mapping.Abstractions; using DD.Persistence.Client.Clients.Mapping.Abstractions;
using DD.Persistence.Models; using DD.Persistence.Models;
using DD.Persistence.Models.Common; using DD.Persistence.Models.Common;
using System.Collections.Concurrent;
namespace DD.Persistence.Client.Clients.Mapping.Clients; namespace DD.Persistence.Client.Clients.Mapping.Clients;
/// <inheritdoc/> /// <inheritdoc/>
public class TimestampedMappingClient(ITimestampedValuesClient client, Dictionary<Guid, Type>? mappingConfigs) : ITimestampedMappingClient internal class TimestampedMappingClient : ITimestampedMappingClient
{ {

Нужно сделать отдельный сервис, который отвечает за кеширование маппингов, и зарегистрировать его в DI.

Нужно сделать отдельный сервис, который отвечает за кеширование маппингов, и зарегистрировать его в DI.
private readonly ConcurrentDictionary<Guid, TimestampedSetMapper> mapperCache = new(); private readonly ITimestampedValuesClient client;
private readonly IMapperStorage mapperStorage;
public TimestampedMappingClient(ITimestampedValuesClient client, IMapperStorage mapperStorage)
{
this.client = client;
this.mapperStorage = mapperStorage;
}
/// <inheritdoc/> /// <inheritdoc/>
public async Task<IEnumerable<T>> GetMapped<T>(Guid discriminatorId, DateTimeOffset? geTimestamp, public async Task<IEnumerable<T>> GetMapped<T>(Guid discriminatorId, DateTimeOffset? geTimestamp,
string? filterTree, IEnumerable<string>? columnNames, int skip, int take, CancellationToken token) string? filterTree, IEnumerable<string>? columnNames, int skip, int take, CancellationToken token)
{ {
var data = await Get([discriminatorId], geTimestamp, filterTree, columnNames, skip, take, token); var data = await Get([discriminatorId], geTimestamp, filterTree, columnNames, skip, take, token);
var mapper = GetMapper<T>(discriminatorId); var mapper = mapperStorage.GetMapper<T>(discriminatorId);
var mappedDtos = data.Select(mapper.DeserializeTimeStampedData).OfType<T>(); var mappedDtos = data.Select(mapper.DeserializeTimeStampedData).OfType<T>();
@ -27,7 +33,7 @@ public class TimestampedMappingClient(ITimestampedValuesClient client, Dictionar
public async Task<IEnumerable<T>> GetLastMapped<T>(Guid idDiscriminator, int take, CancellationToken token) public async Task<IEnumerable<T>> GetLastMapped<T>(Guid idDiscriminator, int take, CancellationToken token)
{ {
var data = await GetLast(idDiscriminator, take, token); var data = await GetLast(idDiscriminator, take, token);
var mapper = GetMapper<T>(idDiscriminator); var mapper = mapperStorage.GetMapper<T>(idDiscriminator);
var mappedDtos = data.Select(mapper.DeserializeTimeStampedData).OfType<T>(); var mappedDtos = data.Select(mapper.DeserializeTimeStampedData).OfType<T>();
@ -43,13 +49,9 @@ public class TimestampedMappingClient(ITimestampedValuesClient client, Dictionar
var result = discriminatorIds var result = discriminatorIds
.ToDictionary(discriminatorId => discriminatorId, discriminatorId => .ToDictionary(discriminatorId => discriminatorId, discriminatorId =>
{ {
if (mappingConfigs is null) var mapper = mapperStorage.GetMapper(discriminatorId);
throw new ArgumentNullException(nameof(mappingConfigs));
if (!mappingConfigs.TryGetValue(discriminatorId, out var type)) ArgumentNullException.ThrowIfNull(mapper);
throw new InvalidCastException();
var mapper = GetMapper(discriminatorId, type);
var mappedDtos = data var mappedDtos = data
.Where(e => e.DiscriminatorId == discriminatorId) .Where(e => e.DiscriminatorId == discriminatorId)
@ -58,19 +60,6 @@ public class TimestampedMappingClient(ITimestampedValuesClient client, Dictionar
return mappedDtos; return mappedDtos;
}); });
Review

Желательно комментарии стереть

Желательно комментарии стереть
//var genericMapperType = typeof(TimestampedSetMapper<>).MakeGenericType(type);
//var ttype = typeof(TimestampedSetMapper<object>);
//object b = 0;
//var mapper = MapperMethodInfo
// .MakeGenericMethod([type])
// .Invoke(this, [discriminatorId]);
//var deserializeMethod = genericMapperType
// .GetMethod(nameof(TimestampedSetMapper<object>.DeserializeTimeStampedData))!;
// ToDo: приводим к Dictionary
return result; return result;
} }
@ -113,13 +102,5 @@ public class TimestampedMappingClient(ITimestampedValuesClient client, Dictionar
client.Dispose(); client.Dispose();
} }
private TimestampedSetMapper GetMapper(Guid idDiscriminator, Type type)
{
return mapperCache.GetOrAdd(idDiscriminator, name => new TimestampedSetMapper(idDiscriminator, type));
}
private TimestampedSetMapper GetMapper<T>(Guid idDiscriminator)
{
return mapperCache.GetOrAdd(idDiscriminator, name => new TimestampedSetMapper(idDiscriminator, typeof(T)));
}
} }

View File

@ -0,0 +1,29 @@
using DD.Persistence.Client.Clients.Mapping.Abstractions;
using DD.Persistence.Models.Configurations;
using System;
using System.Collections.Concurrent;
namespace DD.Persistence.Client.Clients.Mapping;
internal class MapperStorage : IMapperStorage
{
private readonly ConcurrentDictionary<Guid, TimestampedSetMapper> mapperCache = new();
private readonly MappingConfig mappingConfigs;
public MapperStorage(MappingConfig mappingConfigs)
{
this.mappingConfigs = mappingConfigs;
}
public TimestampedSetMapper? GetMapper(Guid idDiscriminator)
{
if(mappingConfigs.TryGetValue(idDiscriminator, out var type))
return mapperCache.GetOrAdd(idDiscriminator, name => new TimestampedSetMapper(idDiscriminator, type));
return null;
}
public TimestampedSetMapper GetMapper<T>(Guid idDiscriminator)
{
return mapperCache.GetOrAdd(idDiscriminator, name => new TimestampedSetMapper(idDiscriminator, typeof(T)));
}
}

View File

@ -1,8 +1,10 @@
using DD.Persistence.Client.Clients; using DD.Persistence.Client.Clients;
using DD.Persistence.Client.Clients.Interfaces; using DD.Persistence.Client.Clients.Interfaces;
using DD.Persistence.Client.Clients.Mapping;
using DD.Persistence.Client.Clients.Mapping.Abstractions; using DD.Persistence.Client.Clients.Mapping.Abstractions;
using DD.Persistence.Client.Clients.Mapping.Clients; using DD.Persistence.Client.Clients.Mapping.Clients;
using DD.Persistence.Models; using DD.Persistence.Models;
using DD.Persistence.Models.Configurations;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
namespace DD.Persistence.Client; namespace DD.Persistence.Client;
@ -17,7 +19,7 @@ public static class DependencyInjection
/// </summary> /// </summary>
/// <param name="services"></param> /// <param name="services"></param>
/// <returns></returns> /// <returns></returns>
public static IServiceCollection AddPersistenceClients(this IServiceCollection services, Dictionary<Guid, Type>? setpointTypeConfigs = null) public static IServiceCollection AddPersistenceClients(this IServiceCollection services)
{ {
services.AddTransient(typeof(IRefitClientFactory<>), typeof(RefitClientFactory<>)); services.AddTransient(typeof(IRefitClientFactory<>), typeof(RefitClientFactory<>));
services.AddTransient<IChangeLogClient, ChangeLogClient>(); services.AddTransient<IChangeLogClient, ChangeLogClient>();
@ -31,18 +33,13 @@ public static class DependencyInjection
} }
Review

Dictionary<Guid, Type>? mappingConfigs - не nullable

Dictionary<Guid, Type>? mappingConfigs - не nullable
public static IServiceCollection AddPersistenceMapping(this IServiceCollection services, Dictionary<Guid, Type>? mappingConfigs) public static IServiceCollection AddPersistenceMapping(this IServiceCollection services, MappingConfig mappingConfigs)
{ {
services.AddTransient<ISetpointMappingClient, SetpointMappingClient>(provider => services.AddSingleton(mappingConfigs);
{ services.AddSingleton<IMapperStorage, MapperStorage>();
var client = provider.GetRequiredService<ISetpointClient>();
return new SetpointMappingClient(client, mappingConfigs); services.AddTransient<ISetpointMappingClient, SetpointMappingClient>();
}); services.AddTransient<ITimestampedMappingClient, TimestampedMappingClient>();
services.AddTransient<ITimestampedMappingClient, TimestampedMappingClient>(provider =>
{
var client = provider.GetRequiredService<ITimestampedValuesClient>();
return new TimestampedMappingClient(client, mappingConfigs);
});
return services; return services;
} }
} }

View File

@ -3,6 +3,7 @@ using DD.Persistence.Client.Clients.Interfaces;
using DD.Persistence.Client.Clients.Interfaces.Refit; using DD.Persistence.Client.Clients.Interfaces.Refit;
using DD.Persistence.Client.Clients.Mapping.Clients; using DD.Persistence.Client.Clients.Mapping.Clients;
using DD.Persistence.Database.Entity; using DD.Persistence.Database.Entity;
using DD.Persistence.Models.Configurations;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using System.Text.Json; using System.Text.Json;
using Xunit; using Xunit;
@ -28,7 +29,7 @@ namespace DD.Persistence.IntegrationTests.Controllers
{ {
var id = Guid.Parse("e0fcad22-1761-476e-a729-a3c59d51ba41"); var id = Guid.Parse("e0fcad22-1761-476e-a729-a3c59d51ba41");
var config = new Dictionary<Guid, Type>(); var config = new MappingConfig();
config[id] = typeof(float); config[id] = typeof(float);
var setpointMapper = new SetpointMappingClient(setpointClient, config); var setpointMapper = new SetpointMappingClient(setpointClient, config);

View File

@ -0,0 +1,4 @@
namespace DD.Persistence.Models.Configurations;
public class MappingConfig : Dictionary<Guid, Type>
{
}

View File

@ -1,6 +1,8 @@
using DD.Persistence.Client.Clients.Interfaces; using DD.Persistence.Client.Clients.Interfaces;
using DD.Persistence.Client.Clients.Mapping;
using DD.Persistence.Client.Clients.Mapping.Clients; using DD.Persistence.Client.Clients.Mapping.Clients;
using DD.Persistence.Models; using DD.Persistence.Models;
using DD.Persistence.Models.Configurations;
using NSubstitute; using NSubstitute;
using System.Text.Json; using System.Text.Json;
@ -15,17 +17,19 @@ public class MappingClientsTest
private readonly ITimestampedValuesClient timestampedValuesClient = Substitute.For<ITimestampedValuesClient>(); private readonly ITimestampedValuesClient timestampedValuesClient = Substitute.For<ITimestampedValuesClient>();
private readonly TimestampedMappingClient timestampedMappingClient; private readonly TimestampedMappingClient timestampedMappingClient;
private readonly Dictionary<Guid, Type> mappingConfigs = new Dictionary<Guid, Type>() private readonly MappingConfig mappingConfigs;
{
{ Guid.NewGuid(), typeof(FirstTestDto) },
{ Guid.NewGuid(), typeof(SecondTestDto) }
};
public MappingClientsTest() public MappingClientsTest()
{ {
timestampedMappingClient = new TimestampedMappingClient(timestampedValuesClient, mappingConfigs); mappingConfigs = GetConfig();
var storage = new MapperStorage(mappingConfigs);
timestampedMappingClient = new TimestampedMappingClient(timestampedValuesClient, storage);
} }
[Fact] [Fact]
public async Task GetMultiMapped() public async Task GetMultiMapped()
{ {
@ -82,4 +86,12 @@ public class MappingClientsTest
expectedId = getResult[1].Values[nameof(SecondTestDto.Id)].ToString(); expectedId = getResult[1].Values[nameof(SecondTestDto.Id)].ToString();
Assert.Equal(expectedId, actualId); Assert.Equal(expectedId, actualId);
} }
private MappingConfig GetConfig()
{
var config = new MappingConfig();
config[Guid.NewGuid()] = typeof(FirstTestDto);
config[Guid.NewGuid()] = typeof(SecondTestDto);
return config;
}
} }

View File

@ -8,6 +8,9 @@
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo"> <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>DD.Persistence.IntegrationTests</_Parameter1> <_Parameter1>DD.Persistence.IntegrationTests</_Parameter1>
</AssemblyAttribute> </AssemblyAttribute>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>DD.Persistence.Test</_Parameter1>
</AssemblyAttribute>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo"> <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>DynamicProxyGenAssembly2</_Parameter1> <_Parameter1>DynamicProxyGenAssembly2</_Parameter1>
</AssemblyAttribute> </AssemblyAttribute>