【一】 摘要

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. WolframAlpha 的使用

    WolframAlpha 1. 求解复杂方程组 a+b=−4ab+c=2ac=1 直接点开网站,在输入框中输入,a+b=-4;ab+c=2;ac=1;(逗号分割开来),

  2. Cocos2d-X之LUA注意事项

    「使用计时器」: 计时器函数原型:unsigned int scheduleScriptFunc(unsigned int handler, float interval, bool paused) ...

  3. Leetcode 136 Single Number 亦或

    题意就是从一堆数中找出唯一的一个只存在一个的数.除了那个数,其他的数都是两个相同的数. 通过亦或的性质: 1)a^a = 0 0^a = a 2)交换律 a^b = b^ a 3)结合律 (a^b)^ ...

  4. WPF 设置控件阴影后,引发的Y轴位置变化问题

    原文:WPF 设置控件阴影后,引发的Y轴位置变化问题 背景 最近遇到一个动画执行时,文本位置变化的问题.如下图: 如果你仔细看的话,当星星变小时,文本往下降了几个像素. 貌似有点莫名其妙,因为控件之间 ...

  5. OpenGL(九) 三维混色和深度缓存设置

    颜色的混合在现实世界中非常常见,例如隔着有色玻璃观看物体,此时在观察者严重呈现出来物体的颜色就是玻璃的颜色和物体的颜色的混合. OpenGL在RGBA颜色模式下使用函数glenable(GL_BLEN ...

  6. matlab GUI 编程

    matlab 语法的简便,在 GUI 上也不遑多让呀: uigetfile [filename, pathname] = uigetfile('*.m', 'choose a m file') 1. ...

  7. 使用HANDLE_MSG宏简化Win32应用的开发

    http://blog.csdn.net/daiyutage/article/details/17241161 Win32应用中的回调函数WndProc用于接收Windows向应用程序直接发送的消息, ...

  8. Fidder模拟发送请求

    在Fiddler的Composer一栏,可以模拟请求 举例 首先通过浏览器访问页面http://baidu.com/ ,在右侧可以拿到请求情况 在Inspectors一栏可以看到请求和响应结果,复制请 ...

  9. js的一些写法问题

    尽量不要拼接字符,用自定义标签来完成     用winform的形式更佳  

  10. dotnet pack 打包文件版本号引起 "Could not load file or assembly" 问题

    如果不是遇到,真的不会想到,代码世界的问题真是千奇百怪,这次遇到的是 dotnet pack 打包文件版本号引起的问题. 之前进行 nuget 打包都是在 Visual Studio build 时进 ...