【一】 摘要

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

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

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

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

其中使用包含了一些开发设计模式,比如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(), 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(), (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 = ,
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>)

net开发框架never的更多相关文章

  1. Enterprise Solution 3.1 企业应用开发框架 .NET ERP/CRM/MIS 开发框架,C/S架构,SQL Server + ORM(LLBL Gen Pro) + Infragistics WinForms

    行业:基于数据库的制造行业管理软件,包含ERP.MRP.CRM.MIS.MES等企业管理软件 数据库平台:SQL Server 2005或以上 系统架构:C/S 开发技术 序号 领域 技术 1 数据库 ...

  2. 从零开始编写自己的C#框架(27)——什么是开发框架

    前言 做为一个程序员,在开发的过程中会发现,有框架同无框架,做起事来是完全不同的概念,关系到开发的效率.程序的健壮.性能.团队协作.后续功能维护.扩展......等方方面面的事情.很多朋友在学习搭建自 ...

  3. CRL快速开发框架系列教程十三(嵌套查询)

    本系列目录 CRL快速开发框架系列教程一(Code First数据表不需再关心) CRL快速开发框架系列教程二(基于Lambda表达式查询) CRL快速开发框架系列教程三(更新数据) CRL快速开发框 ...

  4. CRL快速开发框架系列教程十二(MongoDB支持)

    本系列目录 CRL快速开发框架系列教程一(Code First数据表不需再关心) CRL快速开发框架系列教程二(基于Lambda表达式查询) CRL快速开发框架系列教程三(更新数据) CRL快速开发框 ...

  5. CRL快速开发框架系列教程十一(大数据分库分表解决方案)

    本系列目录 CRL快速开发框架系列教程一(Code First数据表不需再关心) CRL快速开发框架系列教程二(基于Lambda表达式查询) CRL快速开发框架系列教程三(更新数据) CRL快速开发框 ...

  6. CRL快速开发框架系列教程十(导出对象结构)

    本系列目录 CRL快速开发框架系列教程一(Code First数据表不需再关心) CRL快速开发框架系列教程二(基于Lambda表达式查询) CRL快速开发框架系列教程三(更新数据) CRL快速开发框 ...

  7. CRL快速开发框架系列教程九(导入/导出数据)

    本系列目录 CRL快速开发框架系列教程一(Code First数据表不需再关心) CRL快速开发框架系列教程二(基于Lambda表达式查询) CRL快速开发框架系列教程三(更新数据) CRL快速开发框 ...

  8. CRL快速开发框架系列教程七(使用事务)

    本系列目录 CRL快速开发框架系列教程一(Code First数据表不需再关心) CRL快速开发框架系列教程二(基于Lambda表达式查询) CRL快速开发框架系列教程三(更新数据) CRL快速开发框 ...

  9. CRL快速开发框架系列教程六(分布式缓存解决方案)

    本系列目录 CRL快速开发框架系列教程一(Code First数据表不需再关心) CRL快速开发框架系列教程二(基于Lambda表达式查询) CRL快速开发框架系列教程三(更新数据) CRL快速开发框 ...

  10. CRL快速开发框架系列教程五(使用缓存)

    本系列目录 CRL快速开发框架系列教程一(Code First数据表不需再关心) CRL快速开发框架系列教程二(基于Lambda表达式查询) CRL快速开发框架系列教程三(更新数据) CRL快速开发框 ...

随机推荐

  1. seajs构建web申请书

    随着开发项目的不断扩大,查找代码依赖关系复杂化,维护比较沉闷.记seajs有这种效果方面.果断尝鲜.解决两个问题:1)命名冲突 2)文件相关性 因为所在BG使用TAF服务,基于C++开发一套WSP w ...

  2. GetEntryAssembly、GetExecutingAssembly和GetCallingAssembly的区别

    GetEntryAssembly获取的是当前应用程序第一个启动的程序,一般就是xxx.exe文件. GetExecutingAssembly获取的是当前执行的方法所在的程序文件,可能是.exe,也可能 ...

  3. silverlight,WPF动画终极攻略之番外 3D切换导航篇(Blend 4开发)

    原文:silverlight,WPF动画终极攻略之番外 3D切换导航篇(Blend 4开发) 这篇介绍的是3D导航,点击图标,页面360°翻转的效果!有什么不足的欢迎大家指出来. 1.新建一个user ...

  4. 基于Linux C的socketEthereal程序和Package分析 (一个)

     执行测试平台:CentOS 6.5发行版,内核版本号3.11 1. Linux抓包源程序 在OSI七层模型中.网卡工作在物理层和数据链路层的MAC子层. 进行网络通信时.源主机通过socket( ...

  5. Painting and Drawing[MSDN/Windows GDI]

    https://msdn.microsoft.com/en-us/library/dd162759(v=vs.85).aspx Painting and Drawing This overview d ...

  6. AngularJS ng-if使用

    示例中,根据ng-if指令显示不同任务状态,以及判断任务是否可以操作 <div ng-app="NgifDemoApp" ng-controller="NgifDe ...

  7. Expander

    实现折叠列表的效果 <Expander Header="水果列表"> <StackPanel> <RadioButton Content=" ...

  8. 关于QSocket的释放的一个需要注意的情况(必须先断开连接)

    最近在用QtNetwork编写服务器程序进行TCP/IP通信,大体过程如下: 1. 创建一个QTcpServer实例,监听目标IP和端口: 2. 一旦监听到有连接,获取和客户端之间的socket: 3 ...

  9. VMNET 工作站

    nattunnel 快速连接内网电脑 内网穿透.内网映射,支持微信小程序本地开发 支持WEB.远程桌面.多种TCP协议 官方主页:http://www.vmnet.cc 用途 一个可以快速连接局域网中 ...

  10. Linux杂谈: 树形显示多级目录--tree

    最近写博客的时候偶尔会需要将文件目录结构直观地列出来,例如python的包结构. 于是在网上搜了搜,发现了一个Linux下还不错的工具--tree tree 可以很直观地显示多级目录结构. 1. 安装 ...