微软认知服务应用方式有两大类:
用客户端直接访问认知服务
客户端通过中间服务层访问认知服务
第一种模式很好理解:微软认知服务7x24小时在云端提供服务,开发者在智能手机或者PC上编写客户端应用程序,调用REST API直接访问云端。但是这种模式有一些潜在的问题,如:
客户端代码量大逻辑复杂
客户端需要密集发布并持续维护
客户端与服务器端耦合度高
客户端多次访问服务器
网络安全性低
无论客户端有多少,依赖的认知服务有多少,其实还是下图所示的模式:
目前Bob同学就是使用这种方式,来不断演进他的应用,终于遇到了棘手的问题。
为什么呢?因为客户端一旦发布到用户手里,对发布者来说就比较被动了,需要非常小心地维护升级,每次都要全面测试,测试点多而复杂。即使有应用商店可以帮助发布,但要把所有用户都升级到最新版本,还是需要很长时间的,这意味着你还需要向后兼容。
第二种模式可以用简单的图来表示:
有规模的商业化应用,一般都采用这种模式搭建应用架构,以便得到以下好处:
客户端代码量小逻辑简单
客户端不需要密集发布和维护
客户端与认知服务的耦合度低
客户端单次访问服务器
网络安全性高
拉个表格,一目了然:
直接访问模式 | 中间服务层模式 | |
---|---|---|
客户端代码 | 量大,逻辑复杂 | 量小,逻辑简单 |
发布与维护 | 密集,改一点儿东西都需要重新发布新版本 | 中间层服务能屏蔽大量逻辑,不需要在客户端代码中体现 |
客户端与认知服务的耦合度 | 极高 | 很低 |
客户端与认知服务的通信量 | 频繁,多次 | 单次 |
对认知服务密钥的保护 | 低,用Fiddler就可以“看到”认知服务密钥 | 高,把费德勒叫来也不行 |
服务器端代码 | 无 | 有 |
多种客户端支持 | 复杂 | 简单 |
如果有了中间服务层,客户端的工作就简化到只做与中间服务层通信,提交请求,接收数据,用户交互等等,而复杂的商业逻辑,可以在中间服务层实现。而且在更新业务逻辑的时候,大多数情况下,只需要修改中间服务层的代码,无需更新客户端。
对于多种客户端的支持问题,用微软VS2017提供的跨平台Xamarin架构可以解决,开发者只需要写C#程序,就可以把应用部署在Windows/Android/iOS设备上,一套代码搞定。
如果关注于对认知服务的使用,也可以用另外一种分类方式:
单独使用某个服务
串行使用两个以上的服务
并行使用两个以上的服务
串并行混合使用三个以上的服务
比如上面的最后的场景,实际上是第四种方式:先并行使用了地标识别、名人识别、OCR,然后又串行使用了实体搜索服务。
我们来帮助Bob同学重新设计一下他的应用架构:
上图只是个粗略的架构,中间服务层具体如何实现呢?
环境要求:
强烈建议使用Windows 10 较新的版本(笔者使用的是Version 1803)。使用Windows 7也应该可以,但是笔者没有做过具体测试。
至少8G内存。只有4G的话可能会比较吃力。
CPU主频2.5GHz以上,最好是i7。1.9GHz + i5的配置比较吃力。
可以访问互联网上的微软认知服务
基本步骤:
安装Visual Studio 2017 Community或以上版本,注意要安装服务器开发包,否则找不到第4步的模板。
下载安装Microsoft Visual Studio Tools for AI扩展包,安装完后重启VS2017。
在Server Explorer中的AI Tools->Azure Cognitive Services菜单上,点击鼠标右键,申请两个认知服务:Bing.Search.V7和ComputerVision。关于如何申请服务,请看本系列文章的上一篇。
在VS2017中创建一个 ASP.NET Core Web Application,在里面编写中间服务层的逻辑代码。
利用简单的客户端进行测试。
下面我们展开第4步做详细说明。
在VS2017中创建一个新项目,选择Web->ASP.NET Core Web Application,如下图:
给项目取个名字叫做“CognitiveMiddlewareService”,Location自己随便选,然后点击OK进入下图:
在上图中选择“API”,不要动其他任何选项,点击OK,VS一阵忙碌之后,就会生成下图的解决方案:
这是一个最基本的 ASP.NET Core Web App的框架代码,我们将会在这个基础上增加我们自己的逻辑。在写代码之前,我们先一起搞清楚两个关于 ASP.NET Core框架的基本概念。
ASP.NET Core 支持依赖关系注入 (DI) 软件设计模式,这是一种在类及其依赖关系之间实现控制反转 (国际石油公司)的技术,原文链接在这里。简单的说就是:
定义一个接口
定义一个类实现这个接口
在框架代码Startup.cs中注册这个接口和类
在要用到这个服务的类(使用者)的构造函数中引入该接口,并保存到成员变量中
在使用者中直接使用该成员变量->方法名称
我们在后面的代码中会有进一步的说明。
框架提供了一种机制,可以通过注册IHttpClientFactory用于创建HttpClient实例,这种方式带来以下好处:
提供一个集中位置,用于命名和配置HttpClient实例
通过委托HttpClient中的处理程序来提供中间层服务
管理基础HttpClientMessageHandler实例的池和生存期,避免在手动管理HttpClient生存期时出现常见的DNS问题
添加可配置的记录体验,以处理HttpClientFactory创建的客户端发送的所有请求
以上是原文提供的解释,链接在这里。可能比较难理解,但坊间一直流传着HttpClient不能释放的问题,所以用IHttpClientFactory应该至少可以解决这个问题。
但是在使用它之前,我们需要安装一个NuGet包。在解决方案的名字上点击鼠标右键,在出现的菜单中选择“Manage NuGet Packages...“,在出现的如下窗口中,输入”Microsoft.extensions.http“,然后安装Microsoft.Extensions.Http包:
安装完毕后,需要在Startup.cs文件里增加依赖注入:services.AddHttpClient()。
先在生成好的框架代码的基础上,建立下图所示的文件夹:
认知服务
中间件服务
处理器
Controllers是基础框架带的文件夹,不需要自己创建。
创建这些文件夹的目的,是让我们自己能够缕清逻辑,写代码时注意调用和被调用的关系,用必要的层次来体现软件的抽象。以本案例来说,模块划分与层次抽象应该如下图所示(下图中带箭头的实线表示调用关系):
蓝色的层,也就是CognitiveServices文件夹,包含了两个访问认知服务的基础功能:VisionService和EntitySearchService。
它们返回了最底层的结果:VisionResult和EntityResult。这一层的每个服务,只专注于自己的网络请求与接收结果的任务,不管其它的事情。如果认知服务编程接口有变化,只修改这一层的代码。
黄色的层,也就是MiddlewareService文件夹,是我们自己包装认知服务的逻辑层,在这个层中的代码,每一个服务都是用串行方式访问认知服务的:在用第一个输入(假设是图片)得到第一个认知服务的返回结果后(假设是文字),再把这个返回结果输入到第二个认知服务中去,得到内容更丰富的结果。
它们返回了集成后的结果:LandmarkResult和CelebrityResult,这两个结果的定义已经对认知服务返回的结果进行了进一步的抽象和隔离,其目的是让后面的逻辑代码只针对这一层的抽象进行处理,不必考虑更底层的数据结构。
绿色的层,也就是Processors文件夹,是包装业务逻辑的代码,在本层中做任务分发,用并行方式同时访问两个以上的认知服务,将返回的结果聚合在一起,并根据需要进行排序,最后生成要返回的结果AggregatedResult。