零基础ASP.NET Core WebAPI团队协作开发
相信大家对“前后端分离”和“微服务”这两个词应该是耳熟能详了。网上也有很多介绍这方面的文章。我这里提这个是因为接下来我要分享的内容和这个有些关联。
随着前端应用场景的繁荣,用户体验需求的提高,原先传统的后端渲染页面返回给前端展示的模式面临挑战。后端工作除了处理数据逻辑还得适应界面UI的业务,越来越不堪负重。前端的重要性逐渐体现出来,在这种情况下使用前后端分离模式开发的逐渐增多。
前端框架(比如Vue/Angular/React)的发力,大厂的推广使用,前后端分离已经很成熟。包括传统信息化这块以前使用传统WebMVC模式的开发的BS应用有些都逐步转为前后端分离模式。特别是开发人员分工之后专注做好各自的工作,效率更高,做出来的产品也就更好。
一、应用场景
1、浏览器端(Vue/Angular/React)+服务端API
2、桌面客户端(mfc/winform/wpf)+服务端API
3、移动客户端(各种App/App内置浏览器)+服务端API
4、其他终端(大数据展示平台/报表展示平台)+服务端API
客户端越来越强调轻量化,交互体验,不在满足于能用。服务端端只管提供API数据,这样业务逻辑大多在服务端处理,随着需求增加服务端的模块会越来越多。但是有些接口是共用的,有些是根据业务变动的,还有的API新旧版本过渡更新替换等等是服务端api要考虑的事情。设计好API开发框架面灵活应对这么多场景就很有必要了。
原先可能会做一个单体式应用,把所有用到的接口都加进去。但这样粒度很粗,如果某个场景下只使用了部分接口,那也得把这整个应用部署,无法做到按需添加。还有就是可能要修改其中部分方法,需要整体重新编译发布。这都存在可能影响其他模块的风险。
服务端任务量大了,怎么分工?这个时候单体明显已经不适用了。这里就引出了微服务。对微服务可能每个人有不同的理解,但有一点是有共识的,就是把一个大的单体式应用根据功能模块拆分,这样粒度细分之后很多接口就可以共用。之后的修改增加都是可以按需发布部署,局部出现问题不会影响整体。
这个服务的粒度怎么拆分也是需要慎重考虑的问题。除了功能拆分还得考虑人员匹配。
案例场景:一个系统有10个子系统(模块),每个子系统(模块)又有10个功能,每个功能再具体又可能有20个左右的方法代码。
这个案例,最后大概有2000个方法代码。
如果开始安排10人的团队开发,中途因为项目紧急再增加了10个人进来,总共变成了20人,项目组怎么做才能快速适应这种人员变动。
有时候不是人越多越能做好事情,在人员增加情况下除了增加沟通协调成本,实际情况会遇到新加的人参与进来的门槛很高,不能快速着手展开工作,有时候还会出现不知道从何入手的困境。这就是因为拆分的不合理,任何的改动可能会影响到他人。这样虽然为了赶进度加人了,但实际的效果却不是很理想。
二、功能拆分分析
怎么拆分这有两个极端例子
1、粒度最粗,全部在一个解决方案(极端例子,类似单体式应用,适合一人开发)
2、粒度最细,细分到每个方法一个解决方案(极端例子,实际肯定不会这样做)
实际项目中功能拆分就是在这两个极端情况之间找适合的平衡点。具体拆分到多大的粒度,这个就只能是根据具体项目情况具体分析了。但是微服务可能是建议往细的方向拆。
如果是项目型的会发现如果拆的太细,上线一套系统要带N个接口,运维实施都很麻烦;如果是做平台型的产品可能就是有限的几套系统,不会随着项目铺开太多定制化。也就没有了项目型里面的经常部署实施等繁琐的问题。
比如上面的案例,一般都是先根据子系统拆分,具体到每个子系统,有一人或多人开发也有后期临时加入的情况。每个子系统一套接口还是比较合适的。
实际情况项目型比较多,考虑实施运维情况拆分的粒度不会太细,让实施去部署太多业务接口会把他们逼疯。如果有和我类似情况的下面的方案提供了一种解决办法。开发时候可以横向任意扩展,新加入的人员分配任务清晰,不用担心有耦合冲突。发布部署也不会因为粒度太细,增加部署工作量。总结就是插件式开发,微服务部署。
我们都知道vs里面建立一个解决方案,两三个开发人员在同一个解决方案里面开发,只要协调好还行,如果再加入人员,参与的开发人员一旦多了,就算分工好做各自模块,但还是会存在一个些冲突,比如增加文件,增加引用等等都会引起项目文件或者解决方案文件冲突问题。而且这种情况代码权限还不好细分控制。
最好的方式是每个开发人员做的事情都在自己的解决方案里面,只要是公共使用的引用协调好大家使用统一的版本,其他的自己完全可控,完全不用担心影响他人,或者他人的修改影响自己。
大家可以看下这个 github上面dotnet基础类库 ,如图1。
图1
这是dotnet基础库的源码,每个基础类库都是单独一个解决方案维护,随便点一个进去看下,如图2
图2
每个类库都有独立解决方案文件。
微软肯定有更好的方式去管理,但从这里可以看出,独立开发维护的优势。
三、接口项目准备
前面分享过一篇《零基础ASP.NET Core MVC插件式开发》的文章,那边文章其实也就是强调团队开发的时候能做到尽量独立,可以横向扩展,项目灵活变动增加开发人员可以快速参与,开发之后能汇总到一个个的子系统,最后完成整体开发。在这里API的开发也可以使用类似方案,因为API没有视图部分,处理起来比MVC简单。
接下来重点介绍该方案在API开发中的使用。开始这部分内容之前先简单介绍我这边API项目开发总结的两个共性问题:
1、使用swagger显示API文档(nuget 引用 Swashbuckle.AspNetCore)
因为API是没有试图的,为了可视化,以及方便测试,使用swagger作为API的展示界面。具体使用看下面提供的demo代码
2、使用版本控制API版本号(nuget 引用Microsoft.AspNetCore.Mvc.Versioning)
版本控制对API也是同样重要,看BAT大厂提供的API都是有版本控制的,要向他们看齐。实际应用中,程序不可能维持一套最新,有时候新旧版本需要过渡,所以需要有版本来区分。这里使用微软提供的版本控制。具体使用看下面提供的demo代码
这两个使用这里就不细说了,穿插下面主题做些简单介绍,具体看案例demo就可以。
四、接口插件式开发
回到我们的主题,这里重点介绍下一个子系统(模块)任务拆分与人员分工
项目组接下一个项目,一般有个开发组长,着手模块划分并且开发任务分工
组长(公共部分接口+核心功能模块接口)
组员1:细分的模块插件1接口
组员2:细分的模块插件2接口
组员3:细分的模块插件3接口
......
......
对于的解决方案结构,具体命名自己可以根据喜好自己自定义。
Agile.ModuleName.API 如下图3
Agile.ModuleName.Plug1.API 图下图4
Agile.ModuleName.Plug2.API
Agile.ModuleName.Plug3.API
......
......
图3
图4
Demo使用的是vs2019,Asp.net core 2.1
注意:如果一个模块里面接口比较多,一个解决方案里面不适合团队开发。所以对这种接口功能比较多,拆成各个插件方便团队开发,最后发布的时候合并到一起。但是如果这个模块接口不是很多,就没必要过度设计为了插件化而拆开。
每个独立开发的都是vs里面建立的标准ASP.NET Core WebAPI项目,这里主项目和各个插件项目没有从属关系,完全平等API项目开发,最后只是可以汇合到主项目作为一个站点发布交付。各自独立调试运行各自开发功能模块,测试没问题发布汇总到主项目,部署运行,之后哪个接口问题只需要找到对应的模块修改,完全隔离开,不用担心修改会影响其他正常使用的模块。
主项目解决方案结构,如图5
图5
v1,v2里面的是有版本控制的,放在外面的就不需要版本控制。
还有Extensions文件夹里面两个类也是为了版本的显示做处理,具体看Startup.cs里面代码,如图6
图6
搭建好之后,主项目运行,选择v1版本显示如图7
图7
如果选择v2,显示如图8
图8
通过上面两个切换,应该看到不管选择v1还是v2下面不受版本控制的都会显示。
如果把图4的代码注释掉,看下运行效果,如图9
图9
显示就这样,不能根据swagger选择,直观的显示是v1还是v2。这个就是RemoveVersionFromParameter和ReplaceVersionWithExactValueInPath两个类的作用。
这两个类的代码如下
public class RemoveVersionFromParameter : IOperationFilter { public void Apply(Operation operation, OperationFilterContext context) { if (operation.Parameters.Count > 0) { var versionParameter = operation.Parameters.FirstOrDefault(p => p.Name == "version"); operation.Parameters.Remove(versionParameter); } } } public class ReplaceVersionWithExactValueInPath : IDocumentFilter { public void Apply(SwaggerDocument swaggerDoc, DocumentFilterContext context) { swaggerDoc.Paths = swaggerDoc.Paths .ToDictionary( path => path.Key.Replace("v{version}", swaggerDoc.Info.Version), path => path.Value ); } }
到这里主项目API运行正常,接下来看下插件项目API。
插件1项目结构,和主项目类似,如图10
图10
单独运行下这个插件1项目,效果如图11
图11
这里作为插件API项目同样有一点要注意,不要出现和其他插件或者主项目同名的路由(version,controller,action三个完全一样,分工之后各自命名规范估计这种情况也不会出现,主要还是注意避免合并之后路由重名问题)。
这里先把插件1编译的dll放到主项目的运行目录来,如图12
图12
并且在主项目的Startup.cs里面增加这段代码,如图13
图13
运行看下效果,如图14
图14
汇合成功,插件1的API能展示出来,测试也正常,测试就不截图了。
同理,插件1,插件2...等等也是一样处理。开发阶段,各自开发的功能都是可以独立调试运行的。有没有主项目对各自开发的不影响。
五、问题总结
如果插件项目里面引用了一个第三方的程序集,如图15
图15
引用一个测试类库,在Plug1NoVerController的Get方法里面写一个测试代码,如图16
图16
在插件项目单独测试,运行正常。
再把插件1相关文件拷贝到主项目,这时候多了个插件项目自己引用的OtherLib.dll,如图17
图17
正常运行,如图18
图18
测试下刚才插件1里面用到OtherLib类的接口,看效果如图19
图19
汗,居然报错了,提示FileNotFoundException,但是看上面的错误信息截图提示找不到OtherLib.dll文件。OtherLib.dll这个文件明明在这个目录有的。查了相关资料都说是.net core的加载机制变了,但还是没理解透彻,不知道.net core3.0会不会解决这个问题。希望有大神看到可以解惑下这个问题。不过这里我使用一种方式可以解决这个报错,在主程序这里加这段代码。在注册插件项目之前,遍历所有dll,做一次加载就可以了。如图20
图20
需要在注册插件之前,把所有dll文件这样加载一遍,就可以了。
再运行,测试就正常了,如图21
图21
六、发布运行
各个独立开发的插件API,各自独立开发调试正常之后,发布出来。
好了,插件1,插件2...等等各自都开发好了,各自模块调试没问题,最后汇总到主的项目来,基本也就没什么问题了,并且还可以作为一个站点部署。
这里的一个站点只是一个接口服务,不要理解成一个系统就这一个接口服务,虽然可以这样做,但不建议,部署还是各个子系统一个服务,这样数量也不会很多。这里是指在开发阶段对一个子系统(模块)的N个接口做开发方面的分工独立开发调试。
子系统(模块)有N个接口,开发分工如下:
主插件,插件1,插件2,插件3...
全部汇总到主插件的发布目录,或者手动拷贝,最后提供一个完成的子系统接口发布版本,目录如图22
图22
同样命令行运行,或者宿主到iis,这里命令行运行,如图23
图23
浏览器打开,如图24
图24
直接swagger测试各个接口,正常。
独立插件化开发,微服务发布部署。
希望你看了之后有点收获,代码程序下面附件提供