当前位置: 萬仟网 > IT编程>开发语言>.net > net开发框架never

net开发框架never

2019年06月27日  | 萬仟网IT编程  | 我要评论
【一】 摘要 never是纯c#语言开发的一个框架,同时可在netcore下运行。 该框架github地址:https://github.com/shelldudu/never 同时,配合never_web,never_component,never_application (demo)可对比代码学 ...

【一】 摘要

never是纯c#语言开发的一个框架,同时可在netcore下运行。 该框架github地址:

同时,配合never_web,never_component,never_application (demo)可对比代码学习。

引用其图片说明该构架所涉及到的工具

never      

使用emit技术所实现的核心功能点   

never

其中使用包含了一些开发设计模式,比如message的订阅与发布,熔断机制等。

【二】整体设计

1、以applicationstartup开始,启动服务,注册不同组件,这里是netcore的部分代码

/// <summary>
/// 该方法被configureservices里面的base.configureservicese调用,由于configureservices方法会使用不同的组件方案,所以在其后面启支,是将这些组件方案所注册的ioc规则加入到自己的ioc规则里面去
/// 同时替换了系统iservicecollection自己生成的iserviceprovider对象
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void startup_onstarting(object sender, never.startupeventargs e)
{
    //ddd的command里面使用了恢复(即一些命令出错后被保存后过段时间再执行),当前使用sqlite本地数据库方式
    var commandfile = new fileinfo(appcontext.basedirectory + "\\app_data\\command_demo.db");
    //ddd的event跟上面的一样
    var eventfile = new fileinfo(appcontext.basedirectory + "\\app_data\\event_demo.db");
    //使用nlog组件
    var logfile = new fileinfo(appcontext.basedirectory + "\\app_config\\nlog.config");
    //配置文件的读取
    var configreader = new appconfigreader(this.configuration);
}

 我们先对程序集过滤与开启ioc

//注册程序集过滤,因为整个启动过程会分析程序集里面的type对象,很多dll我们不用分析,只焦点到我们现在注入的2个规则就行,"never" + "b2c",正则只要匹配到该字符就加加载到待分析的dll集合中
e.startup.registerassemblyfilter("b2c".createassemblyfilter()).registerassemblyfilter("never".createassemblyfilter());
//ioc分2种启动方法,主要原因如下:(1)服务启动有先后顺序,不同的系统组件所注册的顺序不同的,但有些组件要求在所有环境下都只有第一或最后启动(2)由于使用环境自动注册这种设计下,一些组件要手动注册会带自己的规则就会被自动注册覆盖
e.startup.useeasyioc(
    (x, y, z) =>
    {
        //先启动该服务注册组件,
    },
    (x, y, z) =>
    {
        //再按自己的个性化注册组件,比如controller在下面useapidependency后会自动注入,但是我想homecontroller注入的时候使用memecahed,这种情况就要手动注入了
        //x.registertype<controllers.homecontroller, controllers.homecontroller>().withparameter<never.caching.icaching>("memcached");
        //注入query与repository实例,为什么不用自动注入?哈哈,因为在framework或netcore等各种不同的环境下大家读取配置文件是不同的,一旦写死在b2c.message.sqldata.query里面读取配置文件,则使用不同的host技术就出现极大问题,
        //比如netcore没有connectionstring这种配置(或者有人说可以手动引用system.configuration,这不是嫌麻烦吗)
        x.registerinstance(new b2c.message.sqldata.query.querydaobuilder(infrastructure.sqldbtype.sqlserver, () => configreader["message_conn"]));
        x.registerinstance(new b2c.message.sqldata.repository.repositorydaobuilder(infrastructure.sqldbtype.sqlserver, () => configreader["message_conn"]));
    });

注册各种组件

//使用环境下自动注册组件,
e.startup.useautoinjectingattributeusingioc(new iautoinjectingenvironmentprovider[]
{
    //在message该环境下,所有单例注册组件只有匹配message的才注册,(1)有些组件是线程的,那么不会被描述和注入中,除非再加个线程provider;(2)即使是单例provider,但所运行不是message环境,所以也不会注入
    singletonautoinjectingenvironmentprovider.usingrulecontainerautoinjectingenvironmentprovider("message"),
})
//使用统一配置中心读取配置文件,实用性在后面有讲到
.useconfigclient(new ipendpoint(ipaddress.parse(configreader["config_host"]), configreader.intinappconfig("config_port")), out var configfileclient);
configfileclient.startup(timespan.fromminutes(10), new[] { new configfileclientrequest { filename = "message_api" } }, (c, t) =>
{
    var content = t;
    if (c != null && c.filename == "message_api")
    {
        system.io.file.writealltext(system.io.path.combine(this.environment.contentrootpath, "appsettings.app.json"), content);
    }
}).push("message_api").getawaiter().getresult();
e.startup
    .usecountercache() //使用countcache
    .useconcurrentcache() //使用安全countcache
    .usedatacontractjson() //使用datacontract技术的序列化,实现了ijsonserialize接口
    .useeasyjson(string.empty) //使用easyjson技术的序列化,实现了ijsonserialize接口
    .usenlog(logfile) //使用nlog
    .useappconfig(configreader) //将iconfigreader注入
    .useforcecheckaggregaterootimplihandle() //这几个force都是为了检查ddd开发一些要求,比如是否继承某个类,某些接口
    .useforcecheckcommandappdomainattribute() //检查所有的command是否带了特定attribute
    .useforcecheckcommandevenwithnoparamaterctor() //检查所有的commandhandler所要的构造参数是否被注入中
    .useforcecheckcommandhandlerctor() //检查所有的eventhandler所要的构造参数是否被注入中
    .useforcecheckeventappdomainattribute()//检查所有的event是否带了特定attribute
    .useforcecheckeventhandlerctor() //检查所有的eventhandler所要的构造参数是否被注入中
    .useforcecheckmessagesubscriberctor() //使用消息的订单与发布
    .useinjectingcommandhandlereventhandler(never.ioc.componentlifestyle.singleton) //注入所有的commandhandler,在commandbus执行其对象行为
    .usesqliteeventprovidercommandbus<defaultcommandcontext>(new sqlitefailrecoverystorager(commandfile, eventfile)) //使用cqrs组件,指定sqlite作为恢复组件,
    .useapimodelstatevalidation() //mvc,webapi的模型参数验证
    .useapiactioncustomroute(e.collector as iservicecollection) //自定义路由,相同于在controller可以使用httpget等route技术
    .useapidependency(e.collector as iservicecollection);//注入所有的controller

最后启动过程中检查整个系统是否正常

//配置中心更新配置文件后,系统不一定马上能重新加载
e.startup.startup(timespan.fromseconds(1), (x) =>
{
    //我们在此启动看看所使用组件是否正常启动
    using (var sc = x.servicelocator.beginlifetimescope())
    {
        sc.resolve<icommandbus>();
        sc.resolve<iloggerbuilder>();
        sc.resolve<ijsonserializer>();
        var home = sc.resolve<controllers.messagecontroller>();

        var logger = sc.resolve<iloggerbuilder>().build(typeof(startup));
        logger.info("startup at " + datetime.now.tostring("yyyy-mm-dd hh:mm:ss"));
    }
});
}

2、controller的注入,使用构造函数的方法注入

private readonly iemailcodequery emailcodequery = null;
private readonly imobilecodequery mobilecodequery = null;
private readonly icommandbus commandbus = null;
private readonly iloggerbuilder loggerbuilder = null;
private readonly ijsonserializer jsonserializer = null;
public vcodecontroller(icommandbus commandbus,
    iloggerbuilder loggerbuilder,
    ijsonserializer jsonserializer,
    iemailcodequery emailcodequery,
    imobilecodequery mobilecodequery)
{
    this.commandbus = commandbus;
    this.loggerbuilder = loggerbuilder;
    this.jsonserializer = jsonserializer;
    this.emailcodequery = emailcodequery;
    this.mobilecodequery = mobilecodequery;
}

3、action代码处理

/// <summary>
/// 校验邮箱验证码
/// </summary>
/// <param name="reqs"></param>
/// <returns></returns>
[apiactionremark("a9a900aee8c6", "httppost"), httppost]
public apiresult<string> checkemailvalidatecode(checkemailvalidatecodereqs reqs)
{
    if (!this.tryvalidatemodel(reqs))
    {
        return anonymous.newapiresult(apistatus.fail, string.empty, this.modelerrormessage);
    }

    //实际上不用try + catch了,因为在startup统一日志处理了。
    //发送命令后交给commandhandler去处理领域,commandbus + eventbus
    var handler = this.commandbus.send(new destroyemailcodecommand(newid.generateguid())
    {
        email = reqs.email,
        usagetype = reqs.usagetype,
        vcode = reqs.vcode,
    });

    if (handler == null)
    {
        return anonymous.newapiresult(apistatus.fail, string.empty, "验证失败");
    }

    if (handler.status != commandhandlerstatus.success)
    {
        return anonymous.newapiresult(apistatus.error, string.empty, this.handlermerssage(handler));
    }

    return anonymous.newapiresult(apistatus.success, string.empty);
}

【三】组成部分

  1. applicationstartup 整个系统的初始化中心点,可以是web环境,也可以是service环境。
  2. emit 避免使用反射带来的损耗,并且对opcode的使用封装变成方法的调用,可容易理解与使用,是后面所有技术的支撑点。
  3. ioc 简单实现三种生命周期,单例 + 作用域 + 短暂,注入指定参数,可以生成代理注入拦截器。
  4. aop 加入上下文日志跟踪(如loggerattribte自动写日志);mock对象等。
  5. cqrs 实现了一套commandbus + eventbus设计,commandbus执行命令后,若聚合对象有事件,则通过eventbus发布到订阅者;中间使用sqlite来保存订阅失败的队列,用于后期的恢复发布订阅。
  6. sqlclient 配置极其简单,使用也很容易的一个sql执行方法,使用xml文件配置管理sql语句,可执行事务,对xml内容进行缩进使得好看;也可以直接写sql语句。使用typehander,用于处理阻抗失败的情况。
  7. mapper 直接映射对象,效率比emitmapper差一点。
  8. message 消息的发布与订阅,可以在内存,mq方式发布到不同的机器。
  9. socket 使用socketasynceventargs实现的一套高性能方案,读取与发送分开队列,可以设置心跳。
  10. remoting 在socket的基础上实现一套通讯。
  11. configuration 配置中心,对文件(夹)进行监控,修改文件会触发所有应用程序的配置更新;设置了共享级+应用级配置文件,不用的应用级配置文件可以直接link共享级的配置,共享级的配置可以读取文件,也可以到数据库查询。
  12. deployment 对webapi里面的service直接生成代理类,封装了web请求的参数,路由等信息,还可以使用熔断机制,在客户端发现服务不可用的时候自动返回友好结果。
  13. workflow 实现了一套工作流内容,每一步骤都可以独立为插件或一个类,并且可组合不同步骤,包含等待,重试,中断等不同状态。
  14. memcached 一个memcached客户端,文本协议+二进制协议,还有gzip压缩,binary序列化;定义的接口可以很方便使用protobuf等技术的自由扩展。
  15. jsonserializerjson 序列化,可动态配置不用类型的输出结构,通过emit后缓存提高性能,还能支持用户自定义序列接口。

【四】快速开发

我们打开startup文件global文件来看看,整个构架的初始化都在global或startup里面实现的,环境搭建比较简单,可以直接开发业务而不关心组件实现方式。

摘要里面一些代码展示:

1、接口与实现使用ioc管理,加上灵活的aop,可统一日志管理的管理

[logger]
public class emailcodecommandhandler : icommandhandler<createemailcodecommand>, icommandhandler<destroyemailcodecommand> { }

2、对远程方法的调用,封装成本地调用方式

//实际上这里是web远程方法,使用代理生成类,带熔断,
var api = this.validatecodeservice.createmobilevalidatecode(new message.contract.request.createmobilevalidatecodereqs()
{
    mobile = model.username,
    clientip = this.getappip(),
    platform = this.getappplatform(),
    length = 4,
    usagetype = message.contract.enumtypes.usagetype.注册,
});

3、友好的参数验证,用户自己加验证参数规则。

/// <summary>
/// 用户model
/// </summary>
[serializable, validator(typeof(requestvalidator))]
public class userviewmodel
{
    #region prop

    /// <summary>
    /// 用户名
    /// </summary>
    [displayname("用户名")]
    public string username { get; set; }

    #endregion prop

    #region validator

    private class requestvalidator : validator<userviewmodel>
    {
        public override ienumerable<keyvaluepair<expression<func<userviewmodel, object>>, string>> rulefor(userviewmodel target)
        {
            if (target.username.isnullorwhitespace())
                yield return new keyvaluepair<expression<func<userviewmodel, object>>, string>(m => m.username, "手机号码为空");
        }
    }

    #endregion validator
}

4、可靠的性能:json的序列化与反序列化,在反序列化timespan下(字符串:"00:10:00"), 2700x + 32g内存1000万次测试,jsonnet 使用12.6秒(gc=3.7万),easyser使用2.6秒(gc=3.7k),jil使用0.8秒(gc=1.2k)

5、简单的配置:系统初始化过程风格统一,还有组件eqsysql只需要xml文件 + 链接字符串,就可以实现orm管理(如queryforobject<t>,queryforenumerable<t>)

如您对本文有疑问或者有任何想说的,请点击进行留言回复,万千网友为您解惑!

相关文章:

验证码:
Copyright © 2017-2021  萬仟网 保留所有权利. 粤ICP备17035492号-1
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com