引言

在 Web 变成可编程的 platform 的进程中,有一些应运而生的新的服务的应用场景。我们来看一个具体的例子。Lily 是 Web 2.0 team 一个 Web 开发人员,她想买一些 Web 2.0 开发相关的书来开阔和加深她对 Web 2.0 技术的理解。她希望怎么做呢?她打开她订阅的 eBay畅销书服务(一个 Feed),看到目前最畅销的一组关于 Web 2.0 的书籍,在她浏览这些书的介绍的时候,她还想看看这些书在 Amazon上的书评。等她决定好了买哪些书的时候,她想最好能货比三家,她要知道各个书商提供的 书的价格比较,她选择了一家性价比比较合理的书商后需要付钱购买,比如用 Goolge Check Out 来付账。服务之间的导航关系如图 1 所示。

图 1. 买书所需用的的服务以及关系

今天,lily 如果想做到这一点,要么 IT developer 帮她专门开发一个集成的系统,把这些不同 vendor 提供的 Web 服务集成起来:eBay 提供的 畅销书服务,Amazon 提供的 书评服务,directtextbook.com 提供的 书的价格比较 服务,Google 提供的 Goolge Check Out 服务。但显然这种方法实施性和适应性都较差,因为 lily 的需求不固定,浏览的路径也不固定,IT developer 很难决定需要集成哪些 Web 服务,很难满足像 lily 这样的不固定的需求。另外,Lily 还可以选择使用现有的 Mashups 的产品,如 IBM Mashup Center,但是她需要做比较枯燥乏味而又不是那么容易的 widget 之间的 wiring,而且她还要自己知道这些个相关的 Web 服务。对 lily 而言,如果有这样一个系统,她可以从最开始的 eBay 畅销书服务开始浏览,然后查看 Amazon 的书评,就像现在的 Web 上面的 HTML 页面之间通过 hyperlink 浏览一样,她也可以很容易的通过鼠标的点击、选择、输入一些很少的东西再辅以搜索就可以从一个服务达到另一个服务,平滑、自然、简单、轻松。

这是一个美好的梦想,如何能实现这个梦想?作为第一步,每个企业或者业务系统都必须服务化,实现数据的灵活访问,也就是企业信息系统的解锁,让普通人以 Web 的方式就能轻松的访问。基于此,我们分析 lily 买书这个场景中的几个 Web 服务可知,这些 Web 服务虽然来自不同的系统,但他们之间是有联系的。eBay 提供的畅销书服务的数据里面包含书的 ISBN 信息,而 Amazon 的书评服务和 directtext.com 提供的书的价格比较服务都需要 ISBN 信息才能返回相应的书的书评和价格比较。另外如果现在很多人都用 Goolge Check Out 付账,说明他的 popularity 比较高。当用户想要购买的时候,可以用它。如果我们要满足 lily 的要求而又不需要 IT developer 的参与,我们就需要一种方式描述和建立服务之间的关联,发现和利用这种关联来改善普通人们使用互联网的体验 – 像使用 HTML 为基础的内容 Web 一样来自由地从一个服务浏览到另外一个服务,这正是 REST 架构风格可以解决的问题。

传统的 Web 业务系统的分析

在软件体系架构设计中,分层式结构是最常见,也是最重要的一种结构。Martin Fowler 在《 Patterns of Enterprise Application Architecture 》一书中,将整个架构分为三个主要的层:表示层、领域层和数据源层,如图 2 所示。

表示层 (User Interface Layer) 位于最上层,离用户最近,为用户提供一种交互式操作的界面,用于显示数据和接收用户输入的数据。

业务逻辑层(Business Logic Layer)是系统架构中体现核心价值的部分。它的关注点主要集中在业务规则的制定、业务流程的实现等与业务需求有关的系统设计,也即是说它是与系统所应对的领域(Domain)逻辑有关,很多时候,也将业务逻辑层称为领域层。业务逻辑层在体系架构中的位置很关键,它处于数据访问层与表示层中间,起到了数据交换中承上启下的作用。

数据访问层(Data Access Layer)有时候也称为是持久层,其功能主要是负责数据库的访问,直接操作数据库,针对数据的增、删、改、查。简单的说法就是实现对数据表的 Select,Insert,Update,Delete 的操作。如果要加入 ORM 的元素,那么就会包括对象和数据表之间的 mapping,以及对象实体的持久化。

分层的结构给 Web 应用的开发带来了很多好处,比如开发人员可以只关注整个结构中的其中某一层;可以很容易的用新的实现来替换原有层次的实现;可以降低层与层之间的依赖;有利于标准化;利于各层逻辑的复用。现在也是作为 Web 应用的主流架构提供。

图 2. Web 应用的三层架构

企业信息系统面临的挑战

尽管企业现在有很多的数据系统和应用系统,但是还仍然面临以下的严峻挑战:

  1. 现有的应用系统的“更新”速度跟不上“变化”的速度。也就是说,IT 的更新速度跟不上业务需求的变化速度。
  2. 通常业务人员的数据需求是非常宽泛和任意的,IT 系统很难用一个固定的逻辑写出一个固定的系统来满足。现实生活中的需求是多种多样的,一个用固定逻辑写出来的系统只能满足一部分的需求,不够灵活。
  3. 通常业务人员请求的数据可能来自很多其他的领域,比如在公安领域,警察办案除了警察自己的数据库以外,还需要交通部门的数据,中国移动的数据,等这些数据都不是写几个 SQL 语句就能拿到的。这个挑战设计到不同系统之间的集成。怎么样把各个系统的资产变得可重用,还能以一种简单的标准的协议互相通信。
  4. 数据拿到之后对数据还需要做一定的随机的处理,比如合并、过滤、排序等,现有的固定逻辑的业务系统也很难满足。

以更灵活的 Mashup 的视角看业务系统

Mashup 和现有的 Web 应用系统

Mashup 是 Web 2.0 领域里面一个特别火的词,wikipedia 上的解释是“网络聚合应用,由一个或者多个信息源整合起来的网站或者网络应用”。从企业的角度看 Mashup,应该理解成更“灵活的数据的使用和更简单的应用的构建”。图 3 是一个“客户 360 度信息”的 Mashup。可以看出,在这个 Mashup 中包含五个服务,分别是:①以表格形式展示的客户的基本信息;②以曲线形式展示的沃尔玛的股票信息;③以时间线形式展示的沃尔玛的购买行为;④以柱状图形式展示的客户季度收入情况;⑤以 feed 阅读器形式展示的沃尔玛的新闻。可以看出,这个 Mashup 里面包含的服务来自好几个数据源:①客户的基本信息来自企业的 CRM 系统;②股票信息来自 google 财经;③客户的购买行为来自企业的采购系统;④客户季度销售额来自 google 财经;⑤新闻来自 google news。

图 3. 一个“客户 360 度信息”的 Mashup

从上面的例子我们可以看出 Mashup 和现有的 Web 应用系统相比的优势:

  1. 一线的业务人员通过“自服务”的方式创建应用。由此,一线业务人员可以“快速响应、快速决策、通过增强的协作”来应对快速发生的问题和变化。和传统的 IT 系统相比,这种让业务人员通过“自服务”的方式构建应用的能力使得 IT 系统的成本大大降低,并且还增强了系统的灵活性。
  2. 动态组装和配置的应用满足了企业随需应变的需求。
  3. 快速的、动态的创建日常工作中的情景式应用。这些情景式应用一般要求“good enough”就可以,通常都没有 long run 的需求。所以“快速的构建、快速的迭代”的能力对这种应用来说比较重要。
  4. 很容易的把来自不同数据源的信息“混合”在一起形成新的洞察力。通过解锁个人桌面系统和企业信息系统的数据源,发布成可供搜索的、可消费的、可被组装的 Feeds,然后在需要的时候把这些数据“混合”在一起,组成新的数据或者应用。

Mashup 和传统系统集成技术

Mashup 是 Web 2.0 领域里面一个特别火的词,wikipedia 上的解释是“网络聚合应用,有一个或者多个信息源整合起来的网站或者网络应用”。从企业的角度看 Mashup,应该理解成“更灵活的数据的使用和更简单的应用的构建”。那很多人要问了:从这个角度讲,Mashup 和传统的 BPM、BI、EII、ESB 类似的集成技术有啥不一样呢?我们来分别看一看。

  1. Mashup 和业务流程管理系统(BPM)。BPM 是以流程(Process)的系统集成方式,通常需要人的参与。它通常以流程的形式解决长期的、稳定的、企业至关重要的业务。Mashup 不试图去解决这类问题,它主要以灵活组装 Web 服务的形式解决数据的灵活使用、定制化的应用的开发、随机的瞬态的业务目标。通常有浏览器端的可视化工具,使得人们可以自助式的创建这样的灵活的情境式应用。
  2. Mashup 和业务职能系统(BI)。BI 是一种传统的发挥数据价值的一种手段。基于数据仓库(data warehouse)。Mashup 不需要依托于数据仓库,它的基本单元就是 Web 服务,widget,gadget。Web 服务提供数据,widget 和 gadget 提供数据的展示。很多的 BI 服务像 OLAP analysis、reporting、data mining 等都可以以 Web 服务的形式作为 Mashup 的源数据。另外 Mashup 也可以为 BI 的服务提供高质量的前端的展示,像现在 Cognos 和 Mashup 的结合就是一个很好的例子,解决了传统 BI 的数据可用性问题。
  3. Mashup 和企业信息集成系统(EII)。传统的 EII 是信息集成的过程,通过数据抽象(一系列的结构和命名约定),提供企业范围内的数据的统一的接口和视图。EII 的目标是使得企业里面大量的异构的数据能够以统一的同构视图提供出来。和 BI 一样,EII 也可以为 Mashup 提供很重要的数据源。
  4. Mashup 和企业服务总线(ESB)。ESB 是 SOA 里面架构师们特别喜欢采用的手段。ESB 更多的关注应用之间的交互,而 Mashup 是由最终用户创建的,只是针对当前用户的业务场景而生成的一个近似实时的应用。Mashup 可以为 ESB 提供输入,ESB 里面跑的服务也可以被 Mashup 消费。

相对于这些传统的企业集成技术,Mashup 是一种扩展和补充。Mashup 提供更灵活的数据使用和展示,主要关注情景式的、瞬态的应用,就像在引言部分 lily 买书的例子一样。传统的这些集成系统也可以以 Web 服务的形式为 Mashup 提供强大的企业数据源。

Mashup 的解决方案

前面两个小节分析了 Mashup 和现有 Web 应用系统以及传统信息集成集成的优缺点,这节主要讲述以 Mashup 技术为基础的解决方案。在业务人员能够创建 Mashup 应用之前,需要把信息和服务发布成为可以 Mashup 的格式,通常而言就是 Feeds 或者 Widgets。

  1. 服务化已有的系统。包括数据层,逻辑层,展示层。数据层和逻辑层以 Feeds 的形式提供出来,展示层以 widgets 的形式展示出来。
  2. 创建 Mashup 的前端可视化工具。该工具提供简单拖拽、配置就能快速的构建 Mashup。
图 4. 基于 Mashup 的解决方案概念图

最佳实践— REST 服务化现有系统

RESTify 数据层

这一节主要讲述识别、创建和发布数据服务的方法。

识别数据服务

识别数据服务是最关键的一步,主要解决针对一个数据系统,应该提供哪些数据服务。根据该系列的第一篇文章“REST Service 的最佳实践 第一部分:重新解析 REST Service”,读者已经知道,RESTful Web 服务的核心是以“资源”为中心,而这里实体 - 关系图中的“实体”和“资源”在语义上有很大的关联性,所以这里提供一个基于 E-R(Entity-Relationship)模型的识别数据服务的方法论。实体 - 关系图是一个在数据库设计时帮助架构师进行思考的重要的概念图,反映出信息系统的实体以及实体和实体间的关系,因此实体 - 关系图一个很好的手段去发现曝露出去的资源。图 5 是一个在线购物网站的 E-R 图,我们将以此为例,讲述识别服务的方法。

图 5. 一个在线购物网站的实体 - 关系图

识别数据服务的步骤如下:

  1. 把实体 - 关系图中的“实体”发布成数据服务

    如图 5 中所以,黄色的方框表示的是“实体”。本质上,这些“实体”对应的是系统的“资源”,可以看出,一个在线购物网站,需要提供的资源包括:买家、卖家、供货商、商品、订单、账单、快递单、购物车、买家评价、分类

  2. 把实体 - 关系图中的“关系”发布成数据服务

    实体 - 关系图中定义的“实体”之间的“关系”为“一对一”、“一对多”、“多对多”的关系。把实体 - 关系图中的“关系”发布成数据服务,这里所说的“关系”不是实体之间的数量对应关系,而是语义上数据依赖、相似关系。实体之间的语义关系有几种,如图 6 所示。

    图 6. 实体之间的语义关系图

    下面分别来阐述图 6 所示的关系对应的数据服务。

    相同属性

    “相同属性”指的是“实体 A 和 B 有相同属性”。在“在线购物”的这个场景中,由“相同属性”找出来的数据服务可能包括:具有相同“收获地址”的订单;具有相同“风格”的商品,具有相同属性“打折商品”的商品,等等。一个应用场景是:当用户浏览一件 T 恤的时候,假如“T 恤”这个实体的属性有:风格、面料、尺寸、品牌这四个属性,我们可以识别出来跟实体“T 恤”相同属性的数据服务有两类:一类是相同实体类型的,如相同“风格”的“T 恤”,相同“面料”的“T 恤,相同“尺寸”的“T 恤”,相同“面料”的“T 恤”;还有不同实体的具有“相同属性”的数据服务包括:相同“风格”的“裤子”,相同“面料”的“裤子”,相同“尺寸”的“裤子”,相同“面料”的“裤子”等等。

    从技术来讲,“相同属性”这种识别出来的数据服务其实就是给在 1)中识别出来的实体创建了很多不同的查询条件。通过“属性”,把数据服务关联了起来。通过本系统的第一篇文章,读者已经知道了 REST 的一个核心思想是创建相互联系的服务,而“相同属性”识别数据服务的方法,一方面我们可以得到很多数据服务,另一方面,这些数据服务天然的就相互联系在一块,和 REST 的核心思想一致。

    从用户的角度来讲,“相同属性”是一种导航的线索,通过“相同属性”的关系识别出来的数据服务,使得人们获得一种灵活查询数据的能力。

    操作

    “操作”关系是指“实体 A 对实体 B 做了什么操作”。比如在“在线购物”的这个场景中,“买家”是一种类型的实体,“商品”是另一种类型的实体,“买家 A ‘购买’了商品 B”就是“操作”关系的一种实例。

    通过“操作”关系,可以定义的数据服务包括:买家 A 购买的商品列表,买家 A 浏览的商品列表,买家 A 评价的商品列表等等。

    和通过“相同属性”识别的数据服务类似,通过“操作”关系识别出来的数据服务也天然的符合 REST 的约束。

    实体 A 的实例和实体 B 有相同的关系

    “实体 A 的实例和实体 B 有相同的关系”指的是同种类型的实体的不同实例和第二种类型的实体有相同的关系。还是拿“在线购物”为例,买家 A“购买”了商品 C,买家 B 也“购买”了商品 C,那么可以提供一个数据服务为“同时购买商品 C”的买家列表。细心的读者已经发现,这种关系和“相同属性”的关系有相似之处,不同点在于“相同属性”的关系不依赖于第二种实体,而这种关系依赖于外来的实体,识别数据服务的规则是一样。

    和前两种通过“关系”识别出来的数据服务一样,通过“实体 A 的实例和实体 B 有相同的关系”的关系识别出来的数据服务也天然的符合 REST 的约束。

  3. 把实体 - 关系图中隐含的“实体”发布成数据服务

    通过前两种通过“实体”和“关系”的方法,我们已经得到了很多 RESTful 的数据服务,但这并没有把所有的数据服务都找到,还有一类隐含的数据服务。在“在线购物”的场景中,“买家”(一种实体 - 关系图中的实体)可以对“商品” (一种实体 - 关系图中的实体)进行“评价”(一种实体 - 关系图中的关系),过程中产生了一个隐含的实体“评价内容”。

    也就是说,在实体 - 关系图中的“关系”,除了像 CRUD(创建、查看、更新、删除)这些直接作用在另一个实体的关系以外,还有像“评价”、“打分”等关系,这些关系会产生一些新的实体。我们需要分析实体 - 关系图中的关系,识别出这些隐含的数据服务。

创建和发布数据服务

现在有很多平台可以用来创建数据服务,IBM 就提供了几个选择,例如 IBM WebSphere sMash, Web 2.0 Featurepack for WAS 和 Infosphere Mashuphub 等。利用这些平台,开发人员可以很容易的利用脚本语言或者 Java,或者通过 Mashuphub 的 plugin 来创建这些数据服务。下面以 Mashuphub 为例,来简要的说明创建数据服务的方法。

首先假设“在线购物”的数据库 schema 设计(部分)如清单 1 所示:

清单 1.“在线购物”的数据库 schema 设计(部分)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
create table Buyers(
     id INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY,
     name varchar(64) NOT NULL,
     gender varchar(10),
     salary INTEGER,
     email varchar(50),
     phonenumber varchar(50),
     location varchar(256),  
     starlevel double NOT NULL default 0,  
     PRIMARY KEY (id)
   );
create table Sellers(
     id INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY,
     name varchar(64) NOT NULL,
     phonenumber varchar(50),
     location varchar(256),  
     starlevel double NOT NULL default 0,  
     PRIMARY KEY (id)
   );
create table Delivery(
     id INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY,
     deliverto varchar(256) NOT NULL,
     consignee varchar(50),
     consignee_phonenumber varchar(50),
     delivery_company varchar(256),
     PRIMARY KEY (id)
   );
create table Orders(
     id INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY,
     buyerid INTEGER NOT NULL,
     sellerid INTEGER NOT NULL,
     deliveryid INTEGER NOT NULL,
     generatetimestamp INTEGER default 0,
     confirmtimestamp INTEGER default 0,
     paymenttimestamp INTEGER default 0,
     payment double NOT NULL default 0,       
     PRIMARY KEY (id),
     FOREIGN KEY (buyerid) REFERENCES Buyers(id),
     FOREIGN KEY (sellerid) REFERENCES Sellers(id),
     FOREIGN KEY (deliveryid) REFERENCES Delivery(id),
   );
create table Books(
     id INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY,
     title varchar(128) NOT NULL,
     author varchar(128),
     manufactor varchar(128),
     averageRating  double,
     price double,
     PRIMARY KEY (id)
   );
create table TShirts(
     id INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY,
     name varchar(64) NOT NULL,
     color varchar(64),
     tsize varchar(10),
     brand  varchar(64),
     fabric varchar(64),
     style varchar(64),
     PRIMARY KEY (id)
   );
create table Trousers(
     id INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY,
     name varchar(64) NOT NULL,
     color varchar(64),
     tsize varchar(10),
     brand  varchar(64),
     fabric varchar(64),
     style varchar(64),
     PRIMARY KEY (id)
   );
create table Scarves(
     id INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY,
     name varchar(64) NOT NULL,
     color varchar(64),
     tsize varchar(10),
     brand  varchar(64),
     fabric varchar(64),
     style varchar(64),
     PRIMARY KEY (id)
   );
create table Shoes(
     id INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY,
     name varchar(64) NOT NULL,
     color varchar(64),
     tsize varchar(10),
     brand  varchar(64),
     fabric varchar(64),
     style varchar(64),
     PRIMARY KEY (id)
   );

接下来用 IBM Infosphere Mashuphub 来把 Database 里面的数据服务化。Mashuphub 可以把各种各样的企业数据源发布成数据种子(Feed),详细的介绍请参考 Mashuphub info center。这里简单的介绍下用 Mashuphub 把数据库的数据发布成种子。有以下几步:

  1. 当登录到 Mashuphub 以后,创建新的 feed,选择 feed 的数据源,这里选择 JDBC。如图 7 所示。

    图 7. 选择数据源

  2. 配置 JDBC 数据源,构造 JDBC 连接。如图 8 所示。
    图 8. 配置 JDBC 数据源

  3. 生成 SQL。当第二步成功以后,下一步,Mashuphub 提供了一个 SQL 生成器,它读进来所有数据库表。当用户选择了一个表后,在右边,用户可以选择哪些数据列包含在结果集里。这里我们选择全部。除此以外,还可以配置简单的 orderby 和截断(truncate)。最后,生成的 SQL 在最下面的文本框里面展示出来。当然,如果你已经有写好的 SQL,直接复制粘贴就可以了。如图 9 所示。
    图 9. 用 SQL 生成器生成 SQL

  4. 提供一些描述性的信息,如图 10 所示。
    图 10. 提供一些描述性的信息

  5. 发布成功以后,如图 11 所示。
    图 11. 发布成功以后

RESTify 展示层

表示层主要负责数据的展示,我们称之为 viewer。开发 Viewer 需要具有较多的 Web 开发的技巧,例如 HTML,JavaScript 和 CSS 等。为了重用数据的展示,和数据层一样,我们也需要模块化,REST 服务化。现在流行的可重用的 Web 小组件有很多的规范,像 google gadget,IBM iWidget 等等。下面以 iWidget 规范为例,来展示一个 viewer 的开发过程。下面一个 widget 为例,简要的介绍,结合数据服务,widget 的开发过程。

下面以一个简单的用来显示亚马逊图书搜索的数据服务的 viewer。图书搜索数据服务提供的数据如清单 2 所示:

清单 2. 图书搜索服务的数据样本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<entry xmlns:aws="http://webservices.amazon.com/AWSECommerceService/2005-10-05">
<title> Don't Make Me Think: A Common Sense Approach to WebUsability </title>
<aws:ASIN> 0321344758 </aws:ASIN>
<link rel="alternate" href="http://www.amazon.com/Dont-Make-Think-Usability-ebook/dp
/B000SEGQNS%3FSubscriptionId%3DAKIAJ3RS7ICEOBT6PH4Q%26tag%3Dws%26linkCode%3Dxm2%26cam
p%3D2025%26creative%3D165953%26creativeASIN%3D0321344758"/>
<icon> http://ecx.images-amazon.com/images/I/51GRhbtsUQL._SL160_.jpg </icon>
<logo> http://ecx.images-amazon.com/images/I/51GRhbtsUQL._SL160_.jpg </logo>
<content type="application/xml">
<p:row xmlns:p="http://www.example.com">
<aws:title> Don't Make Me Think: A Common Sense Approach to WebUsability </aws:title>
<aws:ASIN> 0321344758 </aws:ASIN>
<link rel="alternate" href="http://www.amazon.com/Dont-Make-Think-Usability-ebook
/dp/B000SEGQNS%3FSubscriptionId%3DAKIAJ3RS7ICEOBT6PH4Q%26tag%3Dws%26linkCode%3Dxm2%26
camp%3D2025%26creative%3D165953%26creativeASIN%3D0321344758"/>
<author> Steve Krug</author>
<aws:Manufacturer> New Riders Press </aws:Manufacturer>
<aws:lowestNewPrice> $23.30 </aws:lowestNewPrice/>
<aws:averageRating> 4.5 </aws:averageRating>
<p:image> http://ecx.images-mazon.com/images/I/51GRhbtsUQL._SL160_.jpg </p:image>
</p:row>
</content>
</entry>

按照 iWidget 的规范,开发一个 widget 需要提供 widget 的定义文件,如清单 3 所示:

清单 3. 图书搜索服务 viewer 的 widget 定义文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<iw:iwidget name="amazonSearchViewer" xmlns:iw="http://www.ibm.com/xmlns/prod/iWidget"
 iScope="common.iwidget.amazonSearchViewer" allowInstanceContent="true"
  supportedModes="view edit" mode="view" lang="en">
<iw:itemSet id="attributes" >
<iw:item id="feedURL" value="" readOnly="false"/>
<iw:itemDescription name="feedURL" type="url.feed.tabular"
description="Customized widget for displaying book list from Amazon"/>
<iw:item id="title" value="" readOnly="false"/>
<iw:item id="view" value="table" readOnly="false"/>
<iw:item id="titleElement" value="title" />
<iw:itemDescription name="titleElement" type="text" description="book title element"/>
<iw:item id="authorElement" value="author" />
<iw:itemDescription name="authorElement" type="text" description="book author element"/>
<iw:item id="publisherElement" value="Manufacturer" />
<iw:itemDescription name="publisherElement" type="text"
 description="book publisher element"/>
<iw:item id="asinElement" value="asin" />
<iw:itemDescription name="asinElement" type="text" description="book isbn element"/>
<iw:item id="ratingElement" value="AverageRating" />
<iw:itemDescription name="ratingElement" type="text" description="book rating element"/>
<iw:item id="imageElement" value="image" />
<iw:itemDescription name="imageElement" type="text" description="book rating element"/>
<iw:item id="priceElement" value="lowestNewPrice" />
<iw:itemDescription name="priceElement" type="text" description="book price element"/>
</iw:itemSet>
<iw:resource uri="../styles/common.css" />
<iw:resource uri="amazonSearchViewer.js" />
<iw:content mode="view">
<![CDATA[
<div id="_IWID_serviceNode">
<div id="_IWID_loadingNode"
 style="display:none;margin-left:48%;margin-top:40px;height:80px;">
</div>
</div>
]]>
</iw:content>
<iw:content mode="edit">
<![CDATA[
    ]]>
</iw:content>
</iw:iwidget>

itemSet 用来描述 widget 的可配置信息,包括 FeedURL,widget 的标题,还包括和数据服务相关的数据项的描述:titileElement 用来描述图书的标题信息、authorElement 用来描述图书的作者信息等等。

有几种方式来定义 widget,清单 3 给出了其中的一种,用来展示一定类型的数据:图书标题,图书作者,图书标号,图书最新最低价格等等。这种方式编写的 viewer 具有一定的普适性和可重用性,只要数据服务提供了这些信息都可以用这个 viewer 来展示。清单 4 给出了另一种 widget 的定义方式。

清单 4. 图书搜索服务 viewer 的 widget 定义文件 2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<iw:iwidget name="amazonSearchViewer" xmlns:iw="http://www.ibm.com/xmlns/prod/iWidget"
 iScope="common.iwidget.amazonSearchViewer" allowInstanceContent="true"
 supportedModes="view edit" mode="view" lang="en">
<iw:itemSet id="attributes" >
<iw:item id="feedURL" value="" readOnly="false"/>
<iw:itemDescription name="feedURL" type="url.feed.tabular"
description="Customized widget for displaying book list from Amazon"/>
<iw:item id="title" value="" readOnly="false"/>
<iw:item id="view" value="table" readOnly="false"/>
</iw:itemSet>
<iw:resource uri="../styles/common.css" />
<iw:resource uri="amazonSearchViewer.js" />
<iw:content mode="view">
<![CDATA[
<div id="_IWID_serviceNode">
<div id="_IWID_loadingNode"
style="display:none;margin-left:48%;margin-top:40px;height:80px;">
</div>
</div>
]]>
</iw:content>
<iw:content mode="edit">
<![CDATA[
    ]]>
</iw:content>
</iw:iwidget>

清单 4 的 itemSet 只描述了数据源的 FeedURL,而没有更多的关于可配置数据项的描述,这种方式定义的 widget 把对数据的处理隐含在代码里面,局限性比较大,可配置性差,可重用性也差。下面需要写一些 javascript 来处理一个展示的逻辑,如清单 5 所示。负责创建页面元素,并发送 HTTP 请求取回来 feed 的结果。

清单 5. widget 的逻辑代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
dojo.provide("common.iwidget.amazonSearchViewer");
dojo.declare("common.iwidget.amazonSearchViewer", null, {
title:null,
serviceURL:null,
view:null,
onLoad: function(){
this.domID = "_" + this.iContext.widgetId + "_";
var att = this.iContext.getiWidgetAttributes();
   this.title = att.getItemValue("title");
   this.serviceURL = att.getItemValue("feedURL");
   this.view = att.getItemValue("view");
    
   var loading = dojo.byId(this.domID + "loadingNode");
   var innerNode = document.createElement("div");
   loading.appendChild(innerNode);
   new hyperservice.iwidget.ui.Loading({},innerNode);
},
onView: function(){
   var serviceNode = dojo.byId(this.domID + "serviceNode");
   var innerNode = document.createElement("div")
   serviceNode.appendChild(innerNode)
    
      //fetch the feed,
   var self = this;
   var loadCallbackFunc = function(feed){
   new iwidget.ui.FeedListViewer
   ({feed:feed,viewTitle:feed.title,serviceInstance:self.serviceURL,
   selectedItemViewer:"common.ui.AmazonSearchListViewerItem"},innerNode);
   }
    
   var errorCallbackFunc = function(data){
   console.error("failed to fetch a feed with url:"+self.serviceURL);
   console.error(data)
   }
   feedFetcher.fetch(this.serviceURL, loadCallbackFunc,errorCallbackFunc);
   }
   }
});

在清单 5 中创建 FeedListViewer 的时候有一个参数是 selectedItemViewer,用来设置具体的关于数据的展示内容部分,具体的实现代码如清单 6 所示。

清单 6. 关于数据的展示部分的代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
dojo.provide("common.ui.AmazonSearchListViewerItem")
dojo.declare("common.ui.AmazonSearchListViewerItem", [dijit._Widget, dijit._Templated],{
imgSrc:null,
entry:null,
serviceInstance:null,
//dojoattachpoint
imgNode:null,
itemDetailNode:null,
titleNode:null,
authorNode:null,
manufacturerNode:null,
//priceMessageNode:null,
priceNode:null,
ASINnode:null,
ratingNode:null,
averageRatingNode:null,
averageRatingNode:null,
constructor:function(){
this.entry = null;
},
postCreate:function(){
this.createLiveText();
},
createLiveText:function()
{
if(!this.entry){
return;
}
var foundPriceElement = false;
this.imgNode.src = this.imgNode;
var contextRow = {}
dojo.forEach(this.entry.dataitemRows,function(dataitem){ //using xpath
                                                      // as dataitem identifier
contextRow[dataitem.xpath] = dataitem.value;
})
dojo.forEach(this.entry.dataitemRows,dojo.hitch(this,function(dataitem){
if(dataitem.name.indexOf("image")!=-1){
this.imgNode.src = dataitem.value
}else if(dataitem.name.toLowerCase()=="title"){
this.titleNode.innerHTML = "<span>"+dataitem.value+"</span>"
}else if(dataitem.name.toLowerCase()=="author"){
this.authorNode.innerHTML = "<span>"+dataitem.value+"</span>"
}else if(dataitem.name.toLowerCase()=="manufacturer"){
this.manufacturerNode.innerHTML = "<span>"+dataitem.value+"</span>"
}else if(dataitem.name.toLowerCase() == "lowestnewprice"){
foundPriceElement = true;
if(dataitem.value!=""){
    this.priceNode.innerHTML = "<span>"+dataitem.value+"</span>"
    this.priceNode.style.display = "inline"
}else{
this.priceNode.style.display = "none"
}
}else if(dataitem.name.toLowerCase() == "asin"){
this.ASINnode.innerHTML = "<span>"+dataitem.value+"</span>"
}else if(dataitem.name.toLowerCase() == "averagerating"){
var ratio=parseFloat(dataitem.value);
if(Math.floor(ratio)==Math.ceil(ratio))
{
dojo.addClass(this.ratingNode,"stars");
}
var count=0-(5-Math.floor(ratio))*18;
var style=count+"px";
dojo.style(this.ratingNode,{
backgroundPosition:style
});
this.averageRatingNode.innerHTML="<span>("+dataitem.value+")</span>";
     if(!foundPriceElement){
//this.priceMessageNode.style.display = "none"
this.priceNode.style.display = "none"
}
}))
}
});
.stars{
Background:transparent url(sample/imapges/star.png) no-repeat scroll 0 0;
}

具体的 viewer 的展示效果如图 12 所示。

按照此种方法,开发人员可以快速的开发用来展示各种各样类型数据的 widget。

结束语

Web 在转化为一个可编程的平台,越来越多的 Web Service 被发布出来,它们表现为 Feeds,REST APIs 和 Widgets。据 www.ProgrammableWeb.com 网站的统计,该网站已经拥有 1000 多个 Web API 而且以每天新增 2 个的速度在增加。来自 Google 的消息,Google gadget 现在已经有 45170  多个。

本文通过深入的分析 Web 架构的业务系统所面临的挑战,即不够灵活、可复用性差、业务人员难于参与到应用的构建等等,然后以 Mashup 的角度重新审视已有的业务系统,带来一种全新的使用数据、业务逻辑、和展示层的思路,使得这些企业积累起来的资产能够被更好的使用。然后,本文以“在线购物”应用为例介绍了以实体 - 关系模型为基础的 REST 服务化已有的业务系统,包括识别、创建和发布“数据服务”、识别和创建“展示服务”。通过这些分析和方法的介绍,希望给需要提高系统的可重用性、灵活性、响应更快的开发人员提供一定的帮助。

REST service 化一个数据系统(REST Service 的最佳实践,第 2 部分)相关推荐

  1. 《大数据系统构建:可扩展实时数据系统构建原理与最佳实践》一1.5 大数据系统应有的属性...

    本节书摘来自华章出版社<大数据系统构建:可扩展实时数据系统构建原理与最佳实践>一书中的第1章,第1.1节,南森·马茨(Nathan Marz) [美] 詹姆斯·沃伦(JamesWarren ...

  2. .NET DateTime,一个关于最佳实践和时间旅行的故事

    在这里,我们研究了夏令时(DST)对本地DateTime值的影响,使用Utc DateTime值的最佳实践,使最佳实践更难遵循的原因以及应该如何指定AssumeLocal(或AssumeUnivers ...

  3. 【imessage苹果家庭推】群发过程一个“The Feedback Service”的服务

    推荐内容IMESSGAE相关 作者推荐内容 iMessage苹果推软件 *** 点击即可查看作者要求内容信息 作者推荐内容 1.家庭推内容 *** 点击即可查看作者要求内容信息 作者推荐内容 2.相册 ...

  4. CIM、WBEM要Web Service化么?

    CIM.WBEM要Web Service化么? 其实我的需求很简单,就是要让PHP能够和C/C++通讯. RPC?是的,当然要有.但是我们也期望有一个好的设计,就比如Java RMI那样的,可以将我们 ...

  5. 每天一个脚本解析day1==》《service xxxxx status》之service脚本解析

    vim    /sbin/service #!/bin/sh . /etc/init.d/functions #读取环境变量. VERSION="$(basename $0) ver. 0. ...

  6. InfoJet Service,一个InfoPath Web表单产品

    InfoJet Service 是一个面向开发人员的用于将InfoPath表单发布到Web的.NET类库.整合了InfoJet Service的.NET Web应用程序可以支持用户在Internet ...

  7. 在一个app中启动另外一个app的service

    例如:appA要启动appB中的service 步骤一: appB中service的声明,如图 <service android:name="com.appb.BService&quo ...

  8. 【Android】一个APP检测另一个APP的Service被杀死时自动重启服务

    例如:appA要检测启动appB中的service ##1.修改B中Service启动时的FLAG @Overridepublic int onStartCommand(Intent intent, ...

  9. 把 SOAP 服务转化为 REST 服务(REST Service 的最佳实践,第 3 部分)

    from: https://www.ibm.com/developerworks/cn/webservices/1102_mace_restservicePart3/1102_mace_restser ...

最新文章

  1. 基于windows 2012部署oracle 12.1.0.2 rac历险记
  2. 深度学习NCHW和NHWC数据格式(由三维数据转换成一维数据的遍历方式)
  3. 三种CSS样式的引用方式
  4. linux syslog 笔记
  5. 《四世同堂》金句摘抄(六)
  6. 转使用Moq让单元测试变得更简单
  7. mysql 横向扩展 中间件_mysql-proxy数据库中间件架构 | 架构师之路
  8. 为什么用redis做缓存而不是mybatis自带的缓存_如何用Java设计一个本地缓存,涨姿势了...
  9. 职业生涯中的选择时机非常重要,各种条件还没成熟时的时候,因为诱惑而贸然行事,只会得到适得其反的结果...
  10. svn对项目权限进行管理
  11. 地理国情监测云平台简介
  12. 仿链家地图找房的简单实现
  13. ADI 485芯片型号
  14. js_ctype linux,linux – 解释export LANG,LC_CTYPE,LC_ALL的效果
  15. 工业相机和镜头主要参数解释
  16. 10月24日,咱们自己的节日来了!
  17. Elastic 7.9 版本发布,提供免费的 Workplace Search 和终端安全功能
  18. springboot幼儿园幼儿基本信息管理系统设计与实现毕业设计源码201126
  19. PHASEN: A Phase-and-Harmonics-Aware Speech Enhancement Network
  20. 批处理:批量修改文件名称

热门文章

  1. php register_shutdown_function
  2. ai ci ba logon use infomation
  3. linux系统下对磁盘的,学会在Linux下对硬盘分区
  4. centos rpm安装mysql5.5_CentOS 5.5下RPM方式安装MySQL 5.5 详解
  5. Golang 判断key是否在map中
  6. 浅谈 PHP 与手机 APP 开发(API 接口开发)
  7. Informatica如何利用数据3.0助力业务
  8. js 常用倒计时功能:
  9. 对页面制定区域进行打印,以及打印不显示页脚URL的方法
  10. Redis附加功能之键过期功能