C# 基于微服务开发框架的设计思路(二)之扩展性

设计框架的目标就是简化程序员的开发难度,标准化开发规则和制定业务规则。所以,这里要注意一个问题:框架并不是万能的!这一点很重要,否则,也不需要我们这些程序员了。对于架构师来说,完成之前的要求之后,还要充分考虑“扩展性”。

个人认为,扩展性分两种:一种是技术性扩展,一种是业务性的扩展。从这两点出发思考,就是框架从要“做什么”到“怎么做”的转变。

技术性扩展,也分两种,一就是能够接入新的技术,融入框架中,这个可能比较容易,毕竟一般是由架构师来完成的,自然不会容易出现跳出规则的情况,也就是开发这部分的人,水平应该比较高;二就是能够允许二次扩展开发。框架就如建房子的地基,要允许设计师装修,增加些点缀也好,增加房间也罢,要有足够的空间让程序员自己去发挥,但是这种发挥也是要遵守框架原有的规则。那么这里就需要充分理解开发模式,或者说,简单一点,知道工厂模式就好了,例如,我设计的框架,有一个程序启动时候的加载器,在.net461中放在Global中加载。有一些程序就是加载一次就可以的,例如在框架中定义一个接口IStarter

public interface IStarter

{

        void Start();

        void Stop();

}

然后建立一个启动器的加载器,这里把要加载的启动器放到bin目录中的starter目录中

public static class StarterFactory

{

        public static IStarter CreateStarter(string filename)
        {
            if (cache.ContainsKey(filename))
                return cache[filename];
            try
            {
                var asmName = AssemblyName.GetAssemblyName(filename);
                var asm = Assembly.Load(asmName);

                var type = asm.ExportedTypes.FirstOrDefault(t => typeof(IStarter).IsAssignableFrom(t));
                if (type == null)
                {
                    throw new Exception($"文件中没有找到实现IStart的实体类");
                }
                var s = (IStarter)Activator.CreateInstance(type);
                if(s != null)
                {
                    s.OnStop += S_OnStop;
                    cache[filename] = s;
                }
                return s;
            }
            catch (Exception ex)
            {
                OnError?.Invoke($"启动类加载文件出错:{filename}", ex);
                return null;
            }
        }

        public void LoadStarter()

        {

                var starterDir = "这里是starter目录的具体位置";

                var di = new DirectoryInfo(starterDir);

                var fiArr = di.GetFiles("*.dll");

                        foreach(var fi in fiArr)

                        {

                                try

                                {

                               var starter = CreateStarter(fi.FullName);

                                starter.Start();

                                }

                                catch(Exception ex)

                                {

                                        cyb.Utility.Tool.LogHelper.Error("加载启动器失败"+fi.Name,ex);

                                }

                        }

                }

}

上述只是一个简单的例子,通过接口实现的工厂模式,可以完全脱离的强引用,程序员要扩展这样的应用,只要实现IStarter就可以了。

另外,这样的技术,我在设计框架的时候,IOC也是使用了这个技术,代码就不写了,对关键处作一些说明。

往往设计框架的时候,需要考虑不同的数据库的接入,在sqlSugar这个ORM内置了几种数据库的驱动,其实我不太喜欢这样做,至于具体用哪种ORM,我也不一一说,自己喜欢就好了,都差不多。

我们定义数据访问层的接口为IBaseDAL<T>,数据访问层的基类为BaseDAL<T>:IBaseDAL<T>,

这样开发一个员工数据访问层 IEmployeeDAL:IBaseDAL<Employee>,实体为EmployeeDAL:BaseDAL<Employee>,IEmployeeDAL,这个DAL是使用SQLServer的驱动,把DAL层的库文件放到DAL目录中。

如果用过JAVA的成员都知道[Autoware]这个注入,我感觉挺好用的,就在我的框架也实现了这么个功能。

例如:

public class BLLEmployee

{

        [Autoware] IDALEmployee dal;

}

在构造BLLEmployee的时候,通过寻找DAL目录下的库文件,然后寻找实现IDALEmployee的实例类就直接创建,当然也是用Assembly.Load()方法加载DLL,然后把导出的类一一对比就可以了。

通过上述两个例子,用工厂模式来实现扩展还是比较简单的。简单无所谓,关键是实用就好了。

为什么要独立封装出不同的DAL来访问不同的数据类型?例如SqlSugar支持MsSQL,PG,MySql等等关系型的数据库,如果换成Mongo呢?好像目前可选的orm还没有哪个很好同时支持关系型数据库和文档型数据库的,其实也不要偷懒,自己写一下也不难,再如果换成ElasticSearch呢?呵呵,谁知道呢,所以,建议不同的数据库类型,采用一组对应的DAL基类实现,但是都要实现接口IBaseDAL,可能不能都能兼容,那么设计和开发过程中,尽量保持一致的操作。

我在开发过程中,能用Dictionary解决的就不使用Switch或者if,记得学习编程的时候,老师也是这么说的,这么多年,也是这么做的,目的只有一个,减少程序的复杂度,增强代码的可阅读性,降低日后代码的维护难度。

业务扩展就有点复杂了,也不能说得太清楚,这里就提到一个概念:过度封装。在做ERP的时候,把常用的业务分为三类,单表,主从表,关联表,关联表其实并不需要再界面上体现,这里简单说说另外两种:

单表:新增(POST)、编辑(PUT)、删除(DELETE)、搜索(Get、GetList、GetPageList)

主从表:新增和编辑(SaveBill)、删除(Delete)、搜索(GetHdr,GetHdrPageList,GetDtl,GetDtlList)

框架建立了一个 public virtual class TokenApiControllerBase,这是一个API的基类,在用户具体开发的时候,肯呢个上述接口不一定够用或者和实际业务需求有一定出入,那么,可以用过继承和重载接口的方法,来定义一个新的API基类,满足业务扩展的需要,这些业务扩展是经过抽象的,不是指特定某一个业务,否则就失去框架的意义了,还是那句话,不能过度封装。

同理,业务层也需要这么样封装和扩展,数据层和业务并没有关联,也不应该有,所以,这部分的扩展,无非就是适应不同的数据库类型。建议业务层也设计业务层接口,例如:IBaseBLL<T>,这样还可以完全抛弃框架现有的BaseBLL<T>基类的束缚,或者在这个基础上做继承,也不会影响控制器与业务层之间的关系定义。

总之,不管是技术性的扩展还是业务性的扩展,都需要熟练掌握面向对象的知识和技巧,简单的方法就是多使用接口,尽量少使用强引用。

这里还未涉及到微服务,其实,文章未来也不会太过具体描述如何建立微服务,现在网上搜搜,有很多微服务的框架可以使用,从学习的角度来讲也足够了,这里已经把单个微服务的系统架构做了一个简单的说明

page-Controller-BLL-DAL-DB这样的层级,同级之间不允许创建调用,上级可以创建多个下级

例如 BLLEmployee 中可以定义IDALEmployee,IDALDepartment等不同的数据访问层。那么业务层之间通讯怎么办?其实也简单,可以定义一个MessageCenter,或者说InternalMessage

在一个业务层中发布 IMPublish<T>(topic,T data)

在接收的方法中打上标注接收就好了

[IMConsume(topic)]

public RValue<T> IMConsume(T data){}

这样就很简单了,只要是匹配到topic的方法,都会被执行,一般横向通讯只会出现在BLL,如果DAL需要横向通讯,通过委托到BLL中转就好了,别想着简单方法简单来,这样会很乱的。同样,这样也能很好落实了“扩展性”的问题,例如一个销售单流程完成一个步骤,要向其他内部业务通知一下,当日后又新增加了一个业务,就不需要去修改发布那端的代码了,一下子就把系统结构简单化,减少了强引用的问题,至于怎么实现内部消息通知,很多技术框架都有设计,抄一个就好了,不过我是自己写的,把这个制作了一个IMStarter,来缓存IMConsume标注过的方法,以备调用需要,当然就消耗一点内存罢了。

最后一句,我认为扩展性的本质就是:解耦。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值