一种.NetCore的ServiceCollection的组件版本控制

一种.NetCore的ServiceCollection的组件版本控制

在服务端开发中,常常遇到一个问题: 某个新版本调整的功能,仅在部分Core的部分版本上生效。传统的做法上,我们用条件分支去控制这些代码,当业务变复杂时,可能导致过多的条件分支。另外,当版本变迁部分代码将会变为冗余代码,如果不及时清理,会导致业务代码臃肿,明明很简单的逻辑,却包含了很多冗余的条件分支,极大的影响代码的可阅读性和可维护性。

这里,我们借助Ioc容器和类的继承,来实现一种版本控制的方法。

类属性标记

首先我们定义一个属性AppVersionAttribute,用于标记指定的Implement类型的输出条件,他的优先级Priority 取决于是否指定了接口类型,以及定义的版本号

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class AppVersionAttribute : Attribute
{
    /// <summary>
    /// 定义代理类型,如果未设置,则对所有代理类型生效,指定类型判定的优先级高于null判定
    /// </summary>
    public Type ServiceType { get; set; } = null;
    /// <summary>
    /// 版本号,版本号高的优先
    /// </summary>
    public int Version { get; set; } = 0;
    /// <summary>
    /// 客户端版本
    /// </summary>
    public int ClientVer { get; set; } = int.MaxValue;
    /// <summary>
    /// 描述
    /// </summary>
    public string Description { get; set; }
    /// <summary>
    /// 优先级,指定接口类型的优先级最高
    /// </summary>
    public int Priority => (ServiceType == null ? 0 : Int16.MaxValue) + Version;
}

类版本控制器

对于标记的类属性,我们在自动注册到容器的过程中,需要收集信息。这里创建一个版本控制器来负责信息注册和输出。这里如果使用AotuFac或者Castle等其他容器,有更好的实现方式

 /// <summary>
   /// 版本控制器
   /// </summary>
   public class VersionManager
   {
       private static VersionManager _instance;
       private static List<VersionInfo> _versions;
       public static VersionManager Instance => _instance;
       static VersionManager()
       {
           _instance = new VersionManager();
           _versions = new List<VersionInfo>();
       }
       /// <summary>
       /// 获取指定代理类型的所有注册信息
       /// </summary>
       /// <param name="serviceType"></param>
       /// <returns></returns>
       public List<VersionInfo> GetImplements(Type serviceType)
       {
           return _versions.Where(t => t.ServiceType.Contains(serviceType)).ToList();
       }
       /// <summary>
       /// 记录注册信息
       /// </summary>
       /// <param name="type"></param>
       /// <param name="services"></param>
       public void RegisterVersionInfo(Type type, IEnumerable<Type> services)
       {
           var lst = type.GetCustomAttributes<AppVersionAttribute>();
           if (lst.Any())
           {
               lst.ToList().ForEach(t =>
               {
                   _versions.Add(new VersionInfo()
                   {
                       AppVersions = t,
                       ImplementType = type,
                       ServiceType = t.ServiceType == null ? services.ToArray() : new Type[] { t.ServiceType },
                   });
               });
           }
           else
           {
               _versions.Add(new VersionInfo()
               {
                   AppVersions = null,
                   ImplementType = type,
                   ServiceType = services.ToArray()
               });
           }
       }
   }

版本选择器

为了方便后续业务扩展,我们定义了一个版本选择器接口IVersionSelector,并创建了一个默认实现NullVersionSelector ;后续控制目标发生改变时,注入一个新的选择器来替代这个选择器就好

/// <summary>
/// 版本选择器
/// </summary>
public interface IVersionSelector
{
    Type Select<T>();
 
    Type Select(Type service);
}
/// <summary>
/// 默认版本选择器
/// </summary>
internal class NullVersionSelector : IVersionSelector
{
    private static NullVersionSelector instance;
 
    public static NullVersionSelector Instance => instance;
 
    static NullVersionSelector()
    {
        instance = new NullVersionSelector();
    }
 
    /// <summary>
    /// 选择最高优先级的实现类
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="implementTypes"></param>
    /// <returns></returns>
    public Type Select<T>()
    {
        return Select(typeof(T));
    }
    /// <summary>
    /// 选择最高优先级的实现类
    /// </summary>
    /// <param name="service"></param>
    /// <returns></returns>
    public Type Select(Type service)
    {
        var implementTypes = VersionManager.Instance.GetImplements(service);
        if (!implementTypes.Any())
        {
            return null;
        }
        var all = new List<TypeInfo>();
        var dict = implementTypes.Where(t => service.IsAssignableFrom(t.ImplementType))
            .GroupBy(t => t.ImplementType)
            .ToDictionary(t => t.Key, t => t.ToList());
        foreach (var key in dict.Keys)
        {
            var lst = dict[key];
            //先判断是否有指定该接口的设定,取专用接口设定中最高优先级的
            var main = lst.Where(t => t.ServiceType?.Count() == 1 && t.ServiceType[0] == service).OrderByDescending(t => GetPriority(t.AppVersions)).FirstOrDefault();
            int priority = -1;
            if (main == null)
            {
                //没有指定接口设定的,取通用设定中的最高优先级
                priority = lst.Where(t => !t.ServiceType.Any() || t.ServiceType.Contains(service)).Max(x => GetPriority(x.AppVersions));
            }
            else
            {
                //存在专用设定的
                priority = GetPriority(main.AppVersions);
            }
            if (priority > -1)
            {
                all.Add(new TypeInfo() { Implement = key, Priority = priority });
            }
        }
        //取所有注册的实现类型中,优先级最高的
        return all.OrderByDescending(t => t.Priority).FirstOrDefault()?.Implement;
    }
    /// <summary>
    /// 根据请求条件判定是否满足
    /// </summary>
    /// <param name="appVersion"></param>
    /// <returns></returns>
    private static bool FilterByCoreVer(AppVersionAttribute appVersion)
    {
        return RequestParam.Current == null  ||  RequestParam.Current.ClientVer >= appVersion.ClientVer;
    }
    /// <summary>
    /// 根据请求条件判定优先级,小于0则是不满足条件
    /// </summary>
    /// <param name="appVersion"></param>
    /// <returns></returns>
    private static int GetPriority(AppVersionAttribute appVersion)
    {
        return appVersion != null ? (FilterByCoreVer(appVersion) ? appVersion.Priority : -1) : 0;
    }
 
 
    class TypeInfo
    {
        public Type Implement { get; set; }
 
        public int Priority { get; set; }
    }
}

使用接口

在需要注入组件的地方,采用VersionItem来替代T,这个类型包含一个T Value的属性,可以获取到受版本选择器选择后的组件。对于延迟加载的情况,我们也提供一个LazyVersionItem来替代。相关代码如下:

/// <summary>
/// 版本控制器
/// </summary>
/// <typeparam name="T"></typeparam>
public class VersionItem<T>
{
    /// <summary>
    /// 对象
    /// </summary>
    public T Value { get; }
    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="serviceProvider"></param>
    /// <param name="versionSelector"></param>
    public VersionItem(IServiceProvider serviceProvider, IVersionSelector versionSelector)
    {
        var type = versionSelector.Select<T>();
        if(type != null && serviceProvider.GetService(type) is T implement)
        {
            Value = implement;
        }
        else
        {
            Value = serviceProvider.GetService<T>();
        }
    }
}

public class LazyVersionItem<T> : Lazy<T>
{
    public LazyVersionItem(IServiceProvider sp) : base(sp.GetRequiredService<VersionItem<T>>().Value)
    {
    }
}

如果需要延迟加载的自动实现,也可以在注册延迟加载的地方使用以下方式来替代


public static IServiceCollection AddLazy(this IServiceCollection services)
{
    //默认延迟加载的实现
    //services.AddTransient(typeof(Lazy<>), typeof(LazyLoader<>));
    //替换延迟加载的实现
    services.AddTransient(typeof(Lazy<>), typeof(LazyVersionItem<>));
    return services;
}

说明

在需要限制版本下发的地方,采用如下方式标记类型:

[AppVersion(ClientVer = 100, Version = 1)]
[AppVersion(ClientVer = 150, Version = 2, ServiceType = typeof(ISampleService))]
public class SampleService : SampleBaseService,ISampleService

在依赖注入的时候,使用LazyVersionItem来获取受版本控制的对象。当请求发生时,就会根据请求条件,指定下发的版本了

扩展

在实际使用中,可以实现一个基于配置的IVersionSelector,根据Configuration和请求条件,去控制下发版本。这样可以实现不修改代码情况下,动态的更改下发版本的配置的效果

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

萌新上路

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值