从零开发TinyWebDB原型系统(1)
一、目的
在以前发布的文章中,我们曾经讲解过如何利用现成的Docker镜像及相关平台搭建专属的TinyWebDB服务器。但在实际的开发实践中,专属系统虽然比公共平台在安全性等方面略有保障,而在功能定制和扩展方面却总显得有些束手束脚,无法满足更进一步的个性化需求。
另一方面,尽管我们在公众号的视频课程中,推出过一些TinyWebDB应用的开发实例,但大多侧重于App Inventor一侧的前端开发部分。在常规App Inventor开发中,后端TinyWebDB服务器常被当作一个缺省配备的封闭黑箱系统,其作用主要是配合前端功能的实现,而非独立的功能实体。我们只能从外部使用其功能和特性,而对其内部结构和机制却无从把握。从App Inventor应用开发的整体看,这无疑是一个非常重要的缺口。
因此,这次我们尝试把视角从前端移到后端,带领大家从零开始开发和构建一个简单的TinyWebDB服务器系统。其目的不仅在于揭示其内部结构和运行机制,也希望借助这一系统的典型性和简单性,使大家能够初步掌握App Inventor网络应用系统开发的一些基本思路和方法。
二、方法
为了便于大家把握重点,避免因技术上的细枝末节造成思想上的困惑和混乱,我们将借用软件工程中常用的原型法来开发我们的目标系统,我们可以把它理解为真正或完整产品的一个粗线条模型或样本。软件行业经常面临的问题是如何准确把握设计需求,规避实施风险,特别是在开发初期,大多数需求可能还只是一个模糊的想法,或者处于经常变动和反复的不确定状态,低成本的快速原型开发以及直观可交互的原型产品,往往有助于验证想法和厘清思路,使需求逐步具体化、条理化和清晰化。同样,对于相关技术的学习,最重要的往往不是具体的技能和技巧,而是对基本概念、思路和方法的理解和掌握。借助原型产品开发,可使我们更好把握其总体脉络和关键特性,为进一步的理解和运用奠定基础。
三、概念
在我们与App Inventor学习者的交流过程中,有时会碰到这样的提问,比如怎么通过TinyWebDB连接MySQL数据库,或者如何通过TinyWebDB实现SQL查询等等,这些其实都牵涉到了对TinyWebDB角色和作用的基本认识问题。
尽管我们把TinyWebDB叫作网络数据库,但实际上,它不是一个真正的数据库,而是一个典型的网络服务平台。尽管这个平台所提供的服务功能是数据存储,但原则上可以有多种数据存储的实现方式,而数据库只是其中的一种,选择其中任何一种方式,都不会也不应该影响到现有服务的接口形式和规格。换句话说,TinyWebDB只是数据库或其他存储载体的使用者,而非数据库或载体本身。如果要使用其他载体形式,只能通过改变TinyWebDB自身的内在机制实现,而不能通过改变其外部接口或应用实现。
为了使大家更好理解上述观点,我们将尝试开发一个简单的应用,利用App Inventor中用于访问网络服务的Web客户端组件和用于实现数据存储的TinyWebDB(网络数据库)组件,以功能替代的方式进行交叉数据验证。就是先由Web客户端组件写数据,再由TinyWebDB组件读取其写入的数据,验证其正确性,然后再调换角色,进行反向的数据验证。如果能成功完成读写操作,并且确保所读写数据的正确性,那么就证明Web客户端组件所访问的服务在功能上和TinyWebDB服务器是等价的,或者说,根本上它们就是一回事,即TinyWebDB组件就是一个定制版的Web客户端组件,TinyWebDB服务器就是一个典型的Web应用服务平台。
四、接口
在实现具体的验证功能之前,有必要先了解清楚封装在TinyWebDB组件内部的数据操作指令及相关规格,也就是我们通常所说的应用编程接口或API,主要包括包括指令名称、数据格式以及调用方式等。
TinyWebDB所提供的功能主要包括保存数据和查询数据两类,对应的接口指令分别为storeavalue和getvalue,相关数据结构也非常简单,只有tag(标记)和value(数值)两项。为了帮助理解,我们可以把接口看作是App Inventor中常见的有返回过程或函数,也不妨暂时把它们叫作“网络函数”。
和我们在本地应用中定义的函数有所不同的是,“网络函数”是被定义在远端的网络服务器上的,因此在调用这样的函数时,我们必须指明定义它们的主机地址,即要在指令前加上服务器的访问地址和域名。这有点类似于,当我们在电脑中从一个目录去调用另外一个目录里的可执行文件时,必须在文件名前加上完整的路径。
另外,由于最初的网络平台是为文件服务的,所以在参数和数据的表示上具有特定的格式要求,不能沿用我们在函数上所使用的方式。比如,当以GET方式调用storeavalue和getvalue指令时,我们会将相关的参数名和参数值以拼字串的方式带入。
在上面的对照图中,除了指令和参数形式方面的对应和差异外,我们不难发现,调用接口指令时还需额外指明调用方法。其原因在于,网络服务器将所调用的所有功能和数据都视同为类似于文件这样的信息资源,因此,必须额外设定对这些资源的操作或处理方法,比如常见的GET和POST等。
而从应用开发的角度看,不同的调用方法往往适用于不同的功能和服务类型。比如GET方法因数据容量受限,所以只能用于普通的网页访问或简单的数据操作,而如要应对类型复杂、数量较大的数据操作需求,就要采用POST这类适用性更强的方法。
但最终采用哪种调用方式和方法,不取决于调用服务的一方,而取决于定义和提供服务的一方。或者,服务的使用方和提供方之间,必须就通信类型、接口形式以及指令和数据表示方式达成一致,这样才能确保应用功能的正常实现。这就像两个人对话,如果事先不约定好通信的方式,一个人写书信的同时,另一个却在电话旁守候,那么肯定无法实现有效的沟通交流。在网络通信和应用领域,我们通常把这种双方都必须遵守的一整套技术规则和形式,叫做通信协议。
就TinyWebDB客户端和服务器端之间的通信而言,除必须遵守前面已经提到的指令和数据方面的要求外,还须将指令调用或服务请求的方式设定为POST,将请求数据格式(Content-Type)设为application/x-www-form-urlencoded,并确保将指令相关的参数和数据以指定的格式进行封装。否则服务端会因无法正常识别,造成处理错误,实际来自于App Invnentor初学者的大多数问题反馈都与此有关。
最后,单单约定客户端向服务器端发送指令的方式还不够,还必须约定好服务器端向客户端返回处理结果的具体方式。和本地应用函数有些不同的是,对“网络函数”的调用不会立即返回结果,甚至不知道什么时候能返回结果,因此,只能以事件的方式来获取其调用结果。
TinyWebDB接口服务的请求结果一律采用App Inventor列表字串形式表示。其中,针对storeavalue的具体形式为(STORED tag值 value值),针对getvalue的则为(VALUE tag值 value值)。如果处理失败,则相应的值为空。
五、实现
典型的App Inventor网络应用一般由用户界面、通信接口和业务逻辑等三部分组成。其中,用户界面部分主要面向应用的使用者,为其提供操作和处理信息的可视化人机界面;通信接口则负责应用与外部服务资源之间的通信连接,将经处理的用户信息传递给后台服务系统,并接收系统返回的处理结果;而业务逻辑单元则更像是整个应用的调度指挥中心,一方面负责侦听和响应用户在界面上的操作行为,捕获用户产生的数据信息,另一方面,将捕获到的数据按一定的规则和方法进行加工处理,然后以指令方式交由通信接口对外发送,并将返回结果经再次加工处理后呈现给用户。
具体到我们将要实现的应用,由于我们开发的目的是为了验证TinyWebDB和Web应用服务之间的等价性,因此很明显,我们的应用必须包含两个对应的通信接口组件,即一个Web客户端组件和一个网络数据库(TinyWebDB)组件,这样才能建立与后端服务平台间的联系。
而对于用户界面部分,由于它实际是应用功能的外展形式,所以我们有必要在进行具体的设计前,先对应用的主体功能进行分析,以确定其设计依据。
假设验证的具体流程和方法是,先将测试数据提交给后端服务器保存,在确保收到其保存成功的状态反馈后,再向后端提交查询测试数据的请求,最后将收到的查询结果显示出来供测试人员比对。那么,为实现这一业务流程,应用至少必须提供四项基本功能,即保存测试数据、显示保存状态、查询测试数据以及显示查询数据等,我们可以先虚设四个主体过程来表示其具体的规格。
从对功能规格的分析中不难看出,在用户界面部分,我们至少需要三个用于输入数据的组件和两个用于显示结果的组件。为了简化设计以及布局方便,我们可以在测试过程中,将tag的值固定化,这样就可以减少两个输入组件。具体的功能由App Inventor的用户界面和布局类组件实现,其中包括一个文本输入框组件、一个对话框组件以及一个标签组件,分别用于输入测试数据、显示保存状态和显示查询结果,另外还有两个对应的按钮组件,用于通知和触发业务逻辑单元进行相关处理操作。
至此,我们已经实现了应用同外部环境之间的物理连接,接下来我们要通过业务逻辑单元的设计,为这一物理连接通道开启数据流通功能。
首先,我们需要为应用功能的实现建立基础数据模型,以实现用户界面、通信接口和业务逻辑等各应用单元之间的数据共享并确保其一致性。通过前面的需求分析,我们可以确定,整个应用所关注的核心对象为测试数据,其中包括数据标记tag和数据值value,我们将其分别用变量“标记”和“数值”来表示。
接着,我们需要就数据的使用目标,设计相应的数据处理过程,使数据的形式更好满足和匹配功能实现的要求。具体说,就是将数据转化为适合通信传输的形式,为此我们需要先将协议中涉及到的相关约定设定为可供各个过程共用的常量(因App Inventor中没有常量类型,所以用变量暂代)。
然后,分别建立“生成服务指令”、“生成数据格式”、“封装保存数据”、“封装查询条件”以及“解析返回结果”等五个关键的数据处理过程。
数据处理过程设计完成后,我们就可以着手完善一开始提到的四个主体过程的功能实现了。为了便于比较以及确保调用方式上的一致性,我们这里将Web客户端组件的相关功能封装为与TinyWeb组件兼容的形式。
另外,为了测试时调换组件顺序方便,我们对主体过程的参数也做了一些扩充,具体实现如下图所示。
最后,把这些功能和前后端的事件连接起来,实现应用内部和外部环境之间的逻辑连接。比如,当验证Web客户端的数据提交功能时,实现代码如下:
实际测试结果为:
当验证其数据查询功能时,实现代码如下:
实际测试结果为:
以上代码实际还有许多可以优化和扩展的地方,比如增加一个下拉选择框切换不同的测试方案等,但出于简化和突出重点的需要,我们都一并省略了。
从测试结果的对比情况看,除一些无关紧要的细微差异外,测试中保存和查询的数据基本是一致的,这也基本证明了我们最初的猜想,即TinyWebDB服务器的确是标准的Web应用服务平台。以此为基础,我们就可以在后续的文章中,按照常规Web应用服务的实现方式,逐步展开TinyWebDB后端系统的开发和构建过程了。