相信大家对 "前后端分离" 和 "微服务" 这两个词应该是耳熟能详了. 网上也有很多介绍这方面的文章. 我这里提这个是因为接下来我要分享的内容和这个有些关联.
随着前端应用场景的繁荣, 用户体验需求的提高, 原先传统的后端渲染页面返回给前端展示的模式面临挑战. 后端工作除了处理数据逻辑还得适应界面 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 里面建立一个解决方案, 两三个开发人员在同一个解决方案里面开发, 只要协调好还行, 如果再加入人员, 参与的开发人员一旦多了, 就算分工好做各自模块, 但还是会存在一个些冲突, 比如增加文件, 增加引用等等都会引起项目文件或者解决方案文件冲突问题. 而且这种情况代码权限还不好细分控制.
最好的方式是每个开发人员做的事情都在自己的解决方案里面, 只要是公共使用的引用协调好大家使用统一的版本, 其他的自己完全可控, 完全不用担心影响他人, 或者他人的修改影响自己.
大家可以看下这个 https://github.com/dotnet/corefx/tree/master/src , 如图 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 测试各个接口, 正常.
独立插件化开发, 微服务发布部署.
希望你看了之后有点收获, 代码程序下面附件提供
Demo 程序
来源: https://www.cnblogs.com/sylla/p/11198631.html