标签
saas, soa, 全部
开发人员:SaaS

使用 Oracle Application Express 实际演示 SaaS

作者:Steve Bobrowski

通过构建您自己的小型 SaaS 应用程序了解“软件即服务”体系结构的基本概念。

2007 年 8 月发布

无论您相信创造论、智能设计论还是进化论,进化都是一个不可否认的过程,几乎涉及到生活中的方方面面。在自然界,各物种通过进化在不断变化的环境中生存。而在商界,管理良好的公司通过发展业务运营来提高竞争力和收益。

目前,商业领域中一个最普遍的趋势是,向提供和使用“软件即服务”(或 SaaS)的方向发展。本文解释了 SaaS 的一些基本概念、优势和实现细节,提供了构建演示应用程序的教程,以便开发人员和用户可以更好地理解和利用这项新兴技术。

为何选择 SaaS?

从广义上讲,SaaS 的概念并不复杂,即:客户通过互联网访问作为托管应用程序的软件。那么,这样简单的概念为何大受欢迎?考虑一个简单的示例,比较一家中型或大型企业在实现标准客户关系管理 (CRM) 应用程序时可能采用的两种不同方法:传统的、内建应用程序所有权模型与按需 SaaS(或应用程序订户模型)。

传统的应用程序所有权模型通常需要公司进行以下操作:

  • 购买软件和针对操作系统、数据库以及 CRM 应用程序的支持许可证
  • 购买支持 CRM 应用程序的硬件(一个或多个服务器和存储设备)
  • 聘用由一个或多个管理员和顾问组成的团队来安装、配置和维护 CRM 环境

从客户的角度看,拥有传统 CRM 应用程序第一年的总拥有成本可能高达数十万美元,更不用说每年的后续维护成本了。从 CRM 应用程序开发人员的角度出发,潜在的客户群仅限于能够负担与拥有和管理其应用程序副本相关的高昂价格的公司。

让我们将上述方法与简便的 SaaS 方法进行比较。如果使用 SaaS 方法,公司只需订购 CRM 软件服务,而无需考虑需要进行访问的用户数量。公司不必购买任何特殊的硬件或软件,也不需要聘用员工来安装、配置、修补、监视并维护操作系统、软件和数据。而公司的用户只需使用廉价的 PC 将应用程序页面加载到标准 Web 浏览器中,然后进行自己的工作。这样,向用户提供 CRM 应用程序的公司的总成本将大幅度下降。例如,salesforce.com 的一个常用按需 CRM 解决方案 (Salesforce) 的销售价格仅为每年每用户 140 美元。从客户的角度出发,该底线十分引人注目(除非公司拥有数千位用户),订购一个 CRM 应用程序远比为应用程序的完整拥有权支付费用更加经济高效。

应用程序提供商也会在向 SaaS 方法转变的过程中受益匪浅。如果应用程序设计良好且可在成百上千位用户订购服务时轻松扩展,那么低成本的商用硬件和开放源代码的操作系统可简化 SaaS 应用程序的有利托管。通过将低成本传递给客户,软件提供商现在可以从新市场(例如,原来无法负担应用程序所有权模型的小型企业)获得巨大的销售潜力。

不选择 SaaS 的原因

SaaS 批评者会指出,尽管 SaaS 可能是提供小型应用程序(这类应用程序不维护关键数据)的良好模型,由于以下一些重要的警告,SaaS 很难在行业应用程序方面获得成功:

  • 用户必须信任第三方来管理和保护他们的业务数据,确保对这些数据的安全访问。
  • 用户不必拥有由独立软件提供商的服务管理的数据。
  • 如果 SaaS 提供商破产,用户没有求助对象,并可能会因此丢失所有数据。

注意,这些反对过早接受 SaaS 的正确论点都聚焦在两个主要概念上:信任和控制(或者说缺乏信任和控制)。但随着时间流逝和 SaaS 逐渐成熟,不断发展的标准商业准则和解决方案一定能够解决所有这些问题。因此,我们有理由期望,对使用 SaaS 的担忧将降至人们或公司随意将其所有资金都存入银行或其他金融机构时的紧张程度。

SaaS 就在我们身边

CRM 应用程序仅仅是目前作为服务提供的、面向用户的业务应用程序的一个示例。在软件行业的每个领域都能找到无数 SaaS 示例:

  • eBay 可能是最出名的 SaaS 示例,它目前在为全世界数百万商家和顾客提供在线拍卖和购物服务。它通过对销售交易收取费用来赚取利润。
  • Yahoo!、Google 和 Microsoft 分别提供免费的在线电子邮件应用:Yahoo!Mail、Gmail 和 Hotmail。另外,Google 目前还提供了一个 SaaS“办公套件”的初期产品:Google Docs 及 Spreadsheets。这些公司通过销售整合到其服务中的广告来获得收入。
  • 功能完善的在线办公套件包括 zoho.com 和 thinkfree.com。
  • 员工招聘网站 monster.com、dice.com 和 hotjobs.com 的收入来自广告和用户费两个方面。
  • 只需使用传统的办公室内软件部署成本的一小部分,医务所或牙科诊所就可以使用任意一种基于订阅的托管软件解决方案来管理其业务运营。
  • 一家小型企业及其会计人员可以使用 Intuit 的 QuickBooks Online 管理公司的资金。每年的订阅费用仅为 40 美元,可为三位用户及一位会计提供访问权限。

适应还是保守

随着越来越多的企业了解并意识到将标准业务应用程序的运营外包给 SaaS 提供商可以节省资金,来自传统应用程序销售的利润无疑会受到打击。如果您是一位应用程序开发人员,一定无法承担忽视 SaaS 的出现所产生的后果。但是,构建、托管和维护 SaaS 应用程序需要您的思维习惯从“应用程序开发人员”转变为“应用程序提供商”。您不但需要了解如何开发实现 SaaS 的高可伸缩性应用程序,还需要应对并克服应用程序托管和维护带来的挑战。

我在本文中用于演示 SaaS 设计的软件开发环境是 Oracle Application Express。为了能从本文中最大程度地受益,我建议您在 http://apex.oracle.com 注册一个免费的 Oracle Application Express 工作区,然后进行本文中的操作来获得一些实践经验。

注:如果您没有时间或耐心亲自实施本文中的步骤,可以在 www.dbdomain.com/saasdemo.htm 查看已完成的应用程序的实况演示。

元数据:动态安全性和定制的关键

大多数人都会认同,成功的 SaaS 应用程序必须具备可伸缩性、多承租商(即,可通过一个安全、可定制的数据库模式满足多个“承租商”的需求)和易于配置等特性。

您即将构建的小型 SaaS 应用程序经一次部署即可供任意数量的承租商使用,而各承租商的运营基本上是隔离的,不会意识到他或她正在与其他人共享应用程序,并且也不会出现一位承租商的数据被泄露给其他承租商的危险。

一个可以满足既定目标的应用程序不会是传统意义上的、静态编译的应用程序;它在本质上必须具备动态特性。可以在运行时变化的参数化应用程序由元数据驱动。广泛接受的元数据定义是“关于数据的数据”。换句话说,外观和功能可以变化的多态应用程序使用元数据来描述应用程序在其执行期内任意给定时刻的运行时属性。

Oracle Application Express 是应用程序开发环境的完美示例,它使用元数据来获得应用程序在运行时的各方面信息。 Oracle Database Application Express 用户手册中介绍的 Oracle Application Express 如下所示:

Application Express 引擎从存储在数据库表中的数据实时呈现应用程序。在创建或扩展应用程序时,Oracle Application Express 将创建或更改存储在数据库表中的元数据。然后,在运行该应用程序时,Application Express 引擎会读取这些元数据并显示该应用程序。

实践操作

让我们通过构建一个最简单的 SaaS CRM 应用程序,将您刚刚了解的 SaaS 基本知识投入实践,并亲自体验您从上文中获知的一些挑战。

此应用程序演示了我所提及的 静态通用列数据模型,该模型用于支持承租商可配置的 SaaS 应用程序。您还可以考虑另外一种称为 三层数据模型的实现方法(参见边栏)。

此演示应用程序中使用的模式在图 1 中显示为 UML 类图。该模式的设计非常简单,这样您可以将精力集中在学习 SaaS 开发的概念,而不是了解复杂模式设计的复杂程度。



图 1. CRM 类图

该模式中的第一个表为 TENANTS 表,它存储了虚拟 CRM 应用程序订户(承租商)的最基本信息:主键 (TENANT_ID) 和公司名 (COMPANY)。TENANTS 表中的数据与表 1 的数据类似。

 

TENANT_ID COMPANY

1

Mike’s Tires

2

Joe’s Tires

Table 1. TENANTS 表存储 CRM 服务订户(承租商)的基本信息。

需要了解的下一个表是虚拟 CRM 应用程序的主要应用程序表,即 CUSTOMERS 表。乍一看来,该表的列名和数据类型显得有些奇怪。CUSTOMERS 表具有一个典型的主键 (CUSTOMER_ID)、一个引用 TENANTS 表主键的外键 (TENANT_ID) 以及八个非典型通用 VARCHAR2 列,列名分别为 COL_2、COL_3……COL_9。究竟为何我建议大家使用这种表格设计而非具有一组标准列(如 ADDRESS、CITY、STATE 等等)的 CUSTOMERS 表?为了实现我们 SaaS 应用程序设计的两个目标 — 使应用程序的数据模型灵活适应所有承租商需求变化,以及使用单一的模式应对所有承租商 — 您不能使用通常以静态编译应用程序构建的标准化“通用”表格设计,而必须使用具有表的应用程序模式。这些表允许每位承租商根据需要动态定制和扩展数据模型,并且可以在应用程序运行时随承租商的需求进行有效地变化。下面的表 2 将有助于您对上述内容的理解。

CUSTOMER_ID TENANT_ID COL_2 COL_3 COL_4 COL_5 COL_6 COL_7
1 1 Bobrowski Steve 123 Maple Street Springfield MA 02001
2 1 Kyte Tom 456 Elm Street Reston VA 01566
3 2 Ellison Larry lellison@aol.com - - -
4 2 Gates Bill bgates@hotmail.com - - -

表 2. CUSTOMERS 表存储关于每位承租商的客户的信息。

注意,第一位承租商拥有两位客户:我和 Tom Kyte;同时,第二位承租商也有两位客户,分别是名为 Ellison 和 Gates 的著名企业家。对于每个承租商的每位客户,COL_2 和 COL_3 分别用于存储客户的姓和名,但这是两个承租商所拥有的唯一共同点。第一位承租商使用 COL_4、COL_5、COL_6 和 COL_7 存储每位客户的实际地址信息;而第二位承租商使用 COL_4 存储每位客户的电子邮件地址,且根本不使用 COL_5、COL_6 和 COL_7。(此图使用 "-" 表示空值。)因此,我们此处显示的是一个单独的表格,可以存储这个非常简单的演示应用程序的每位承租商的不同客户属性。

您可能会问的下一个问题是:“该应用程序如何为每个承租商确定 CUSTOMERS 表中各列(COL_2、COL_3……COL_9)的用途?”答案是创建一个表,在其中存储关于含有真实数据的应用程序表的元数据:在此演示应用程序中,元数据表为 TENANT_COLUMNS。TENANT_COLUMNS 表有四列(参见表 3):

  • TENANT_ID 列是引用 TENANTS 表主键的外键。
  • TABLE_NAME 列存储 CRM 应用程序模式中一个特殊表的名称,如 CUSTOMERS。
  • COLUMN_ID 列存储与相应表的通用列名中的数字标识符匹配的整数。
  • COLUMN_NAME 列存储指示相应列的名称的字符串。
TENANT_ID TABLE_NAME COLUMN_ID COLUMN_NAME
1 CUSTOMERS 2 Last Name
1 CUSTOMERS 3 First Name
1 CUSTOMERS 4 Address
1 CUSTOMERS 5 City
1 CUSTOMERS 6 State
1 CUSTOMERS 7 Zip Code
2 CUSTOMERS 2 Last Name
2 CUSTOMERS 3 First Name
2 CUSTOMERS 4 Email

表 3. TENANT_COLUMNS 表包含应用程序可在运行时用于变化应用程序表的元数据。

注意,上表中第一位承租商的数据显示,CUSTOMERS 表中的 COL_2 存储每位客户的 "Last Name",COL_3 存储每位客户的 "First Name",COL_4 存储每位客户的 "Address",COL_5 存储每位客户的 "City",COL_6 存储每位客户的 "State",COL_7 存储每位客户的 "Zip Code"。同时,第二位承租商的数据显示,CUSTOMERS 表中的 COL_2 存储每位客户的 "Last Name",COL_3 存储每位客户的 "First Name"。但与第一位承租商不同,COL_4 存储每位客户的 "Email" 地址。我故意将上文中各列的标签置于引号中,以帮助您意识到这些标签是应用程序在运行时查询和使用的抽象元数据。如果一位承租商决定向表中添加新的元数据或更改某列的标签,应用程序将在运行时自动调整,无需对代码库进行任何修改。在每位用户注册使用虚拟 CRM 应用程序时,承租商的管理员可以使用设置向导,该向导有助于简化承租商对应用程序用途的定制。

CRM 应用程序模式中的最后一个表是 USERS 表。这个简单的表仅存储有关承租商用户(执行该应用程序进行工作的人)的最基本信息:主键 (USER_ID)、用户登录名 (USERNAME) 和外键 (TENANT_ID)。USERS 表中的数据与下方表 4 的数据类似。

USER_ID USERNAME TENANT_ID
1 MIKE 1
2 JOE 2

表 4. USERS 表存储每位承租商的注册用户信息。

要点:每位用户的 USERNAME 必须全部为大写字母,以便支持示例应用程序使用的用户认证模式。稍后在您构建应用程序时,我们会详细介绍这一点。

创建 CRM 应用程序模式

现在,您已经了解 CRM 应用程序模式,可以使用 Oracle Application Express 构建应用程序的表和支持数据结构了。在 apex.oracle.com 上获得一个工作空间后,可以使用给定的工作空间名称和凭证创建一个 Oracle Application Express 会话。Oracle Application Express 主页显示三个主图标,通过这三个主图标,您可以使用工具开始构建应用程序。

对于您将创建的四个表中的三个,还需要创建一个序列(即,主键生成器)和一个相关的触发器,以使用序列号填充表的主键。利用 Oracle Application Express 的向导驱动的界面,可以轻松创建表、相关的完整性约束、序列和触发器。

要在一次操作中创建一个 TENANTS 表和所有相关的对象,单击 SQL Worksheet -> Object Browser -> Create -> Table 启动 Create Table 向导。

  1. 在向导的 Columns 页面上,指定表名称为 TENANTS 并指定两列:TENANT_ID 为 NUMBER,COMPANY 为非空的 VARCHAR2(4000)。如果您不熟悉 Oracle 数据库,没关系,VARCHAR2 是 Oracle 数据库的可变长度列数据类型,最大长度为 4000(默认单位为字节)。单击 Next 继续。
  2. 在向导的 Primary Key 页面上,选择 Populated from a new sequence,接受新序列和主键约束的默认名称,然后从 Primary Key 列表中选择 TENANT_ID 列。然后,单击 Next
  3. 利用向导的后两页,您可以声明表的外键(引用)、校验和唯一约束;您创建的 TENANTS 表没有这些类型的约束。单击 Next,然后单击 Finish 转至向导的 Confirm 页。在该页上,如果您单击 SQL,应该能看到与以下内容类似的 SQL 语句,在您单击 Create 时,向导将执行这些语句。
    CREATE table "TENANTS" (
        "TENANT_ID"  NUMBER,
        "COMPANY"    VARCHAR2(4000) NOT NULL,
        constraint  "TENANTS_PK"
         primary key ("TENANT_ID")
    )
    /
    
    CREATE sequence "TENANTS_SEQ" 
    /
    
    CREATE trigger "BI_TENANTS"  
      before insert on "TENANTS"              
      for each row 
    begin  
        select "TENANTS_SEQ".nextval
          into :NEW.TENANT_ID
          from dual;
    end;
    /
    
创建表之后,Oracle Application Express 的 Object Browser 页面会在页面左侧的列表中显示新对象。接下来是 CUSTOMERS 表。单击 Create -> Table,并完成以下步骤:
  1. 在向导的 Columns 页面上,指定表名称为 CUSTOMERS。然后,指定 CUSTOMER_ID 和 TENANT_ID 列为 NUMBER,并且不允许 TENANT_ID 列包含空值。最后,指定 8 个通用的 VARCHAR2(4000) 列,即 COL_2...COL_9(您需要单击 Add Column 多次,以便为这些列获取空间),然后单击 Next 继续。
  2. 在向导的 Primary Key 页面上,选择 Populated from a new sequence,接受新序列和主键约束的默认名称,然后从 Primary Key 列表中选择 CUSTOMER_ID 列。然后,单击 Next
  3. 在向导的 Foreign Keys 页面上,将 TENANT_ID 列添加到 Key Column(s) 列表,在 References Table 域中指定 TENANTS 表,然后将 TENANT_ID 列添加到 Referenced Column(s) 列表。不要忘记单击 Add 添加声明的外键。
  4. 要跳过向导的其余页面,单击 Confirm。然后,单击 SQL 并确保 SQL 类似以下内容。
    CREATE table "CUSTOMERS" (
        "CUSTOMER_ID" NUMBER,
        "TENANT_ID"   NUMBER NOT NULL,
        "COL_2"       VARCHAR2(4000),
        "COL_3"       VARCHAR2(4000),
        "COL_4"       VARCHAR2(4000),
        "COL_5"       VARCHAR2(4000),
        "COL_6"       VARCHAR2(4000),
        "COL_7"       VARCHAR2(4000),
        "COL_8"       VARCHAR2(4000),
        "COL_9"       VARCHAR2(4000),
        constraint  "CUSTOMERS_PK"
         primary key ("CUSTOMER_ID")
    )
    /
    
    CREATE sequence "CUSTOMERS_SEQ" 
    /
    
    CREATE trigger "BI_CUSTOMERS"  
      before insert on "CUSTOMERS"              
      for each row 
    begin  
      if :NEW."CUSTOMER_ID" is null then
        select "CUSTOMERS_SEQ".nextval
        into :NEW."CUSTOMER_ID" from dual;
      end if;
    end;
    /   
    
    ALTER TABLE "CUSTOMERS" ADD CONSTRAINT "CUSTOMERS_FK" 
    FOREIGN KEY ("TENANT_ID")
    REFERENCES "TENANTS" ("TENANT_ID")
    /
    
要创建 USERS 表,单击 Create -> Table 并完成以下步骤:
  1. 在向导的 Columns 页面上,指定表名称为 USERS。接下来,指定 USER_ID 列为 NUMBER,指定 USERNAME 列为 VARCHAR2(10) AND not null,指定 TENANT_ID 列为 NUMBER and not null。然后,单击 Next 继续。
  2. 在向导的 Primary Key 页面上,选择 Populated from a new sequence,接受新序列和主键约束的默认名称,然后从 Primary Key 列表中选择 USER_ID 列。然后,单击 Next
  3. 在向导的 Foreign Keys 页面上,将 TENANT_ID 列添加到 Key Column(s) 列表,在 References Table 域中指定 TENANTS 表,然后将 TENANT_ID 列添加到 Referenced Column(s) 列表。单击 Add 添加声明的外键。
  4. 要跳过向导的其余页面,单击 Confirm。然后,单击 SQL 并确保 SQL 类似以下内容。
    CREATE table "USERS" (
        "USER_ID"    NUMBER,
        "USERNAME"   VARCHAR2(30),
        "TENANT_ID"  NUMBER,
        constraint  "USERS_PK"
         primary key ("USER_ID")
    )
    /
    
    CREATE sequence "USERS_SEQ" 
    /
    
    CREATE trigger "BI_USERS"  
      before insert on "USERS"              
      for each row 
    begin  
      if :NEW."USER_ID" is null then
        select "USERS_SEQ".nextval
        into :NEW."USER_ID" from dual;
      end if;
    end;
    /   
    
    ALTER TABLE "USERS" ADD CONSTRAINT "USERS_FK" 
    FOREIGN KEY ("TENANT_ID")
    REFERENCES "TENANTS" ("TENANT_ID")
    
    /
    
要创建 TENANT_COLUMNS 表,您应该使用 SQL 命令而非 Create Table 向导手动创建该表。为什么?该向导有一些缺陷,对该特定的表尤为明显:
  • 使用该向导,您不能创建包含两个以上列的主键约束。TENANT_COLUMNS 表中的所有列共同构成其主键。
  • 使用该向导,您只能创建标准的、按堆栈组织的表,但是该查询表最好是一个按索引组织的表 (IOT)。IOT 是 Oracle 数据库创建为索引的表,换句话说,该表和主键索引没有独立的数据结构。当应用程序主要按查询表的主键对查询表进行查询时,IOT 可以显著提高性能。性能应该是任何应用程序设计的主要目标,对于能够在支持数千个用户的情况下进行伸缩的 SaaS 应用程序来说,尤为如此。Oracle 数据库有许多与性能相关的特性,例如 IOT 可以提高应用程序的效率。
要手动创建 TENANT_COLUMNS 表,单击 SQL Workshop -> SQL Commands 显示 Oracle Application Express 的 SQL Commands 页面。然后,输入并运行 CREATE TABLE 命令:
CREATE table "TENANT_COLUMNS" (
    "TENANT_ID"   NUMBER,
    "TABLE_NAME"  VARCHAR2(30),
    "COLUMN_ID"   NUMBER,
    "COLUMN_NAME" VARCHAR2(30),
     CONSTRAINT "TENANT_COLUMNS_PK"
      PRIMARY KEY (
       "TENANT_ID", "TABLE_NAME",
       "COLUMN_ID", "COLUMN_NAME"),
     CONSTRAINT "TENANT_COLUMNS_FK" 
      FOREIGN KEY ("TENANT_ID")
      REFERENCES "TENANTS" ("TENANT_ID"))
    ORGANIZATION INDEX;
要帮助实现性能的最大化并提高应用程序的可伸缩性,请确保每个表都有一个主键,并为所有外键创建索引。默认情况下,Oracle 数据库会为所有主键约束创建索引,但并不为外键创建。Oracle Application Express 在工具的 Utilities 页面上有一个非常有用的实用程序集,可以帮助您识别此类问题并提高应用程序模式的质量。例如,单击 Home -> Utilities -> Object Reports -> Tables -> Unindexed Foreign Keys 将显示所有没有基本索引的外键。

您可以使用 Oracle Application Express Object Browser 的 Create Index 向导轻松创建必要的外键索引。要想操作更方便,也可以使用 SQL Commands 页执行两个 CREATE INDEX 命令来完成该任务:

CREATE INDEX  "CUSTOMERS_IDX1" ON  "CUSTOMERS" ("TENANT_ID");

CREATE INDEX  "USERS_IDX1" ON  "USERS" ("TENANT_ID");
简单 CRM 应用程序模式的最后一个组件是一个程序包,即一个由过程、函数、全局变量和其他结构组成的应用程序编程接口 (API)。利用 Oracle 数据库,您可以使用 Oracle 的 SQL 专用编程语言扩展(称为 PL/SQL)编写程序包代码。您可以使用 Object Browser 的 Create Package 向导,或者使用 SQL Commands 页面执行 CREATE PACKAGECREATE PACKAGE BODY 命令,来创建 CRM 的程序包:
-- The CRM_PKG specification declares the package's API.
CREATE OR REPLACE PACKAGE  "CRM_PKG" AS
 g_tenant_id NUMBER;
 FUNCTION get_tenant_id (p_username IN VARCHAR2)
  RETURN NUMBER;
 FUNCTION get_report_columns (
  p_row_source IN VARCHAR2,
  p_tenant_id IN NUMBER DEFAULT g_tenant_id)
  RETURN VARCHAR2;
 FUNCTION get_form_columns (
  p_row_source IN VARCHAR2,
  p_tenant_id IN NUMBER DEFAULT g_tenant_id)
  RETURN VARCHAR2;
END;
/

-- The CRM_PKG body defines all package constructs.
CREATE OR REPLACE PACKAGE BODY  "CRM_PKG" AS

  FUNCTION get_tenant_id(p_username IN VARCHAR2)
  RETURN NUMBER IS
  BEGIN
    SELECT tenant_id
    INTO g_tenant_id
    FROM users
    WHERE username = p_username;
    RETURN g_tenant_id;
  END;

  FUNCTION get_report_columns(
   p_row_source IN VARCHAR2,
   p_tenant_id IN NUMBER DEFAULT g_tenant_id)
  RETURN VARCHAR2 IS

  l_columns VARCHAR(1000) := 'Edit:';

  BEGIN
    FOR rec IN
      (SELECT column_id, column_name
       FROM tenant_columns
       WHERE tenant_id = p_tenant_id
       AND TABLE_NAME = p_row_source
       ORDER BY column_id)
    LOOP
      IF(LENGTH(l_columns) > 0) THEN
        l_columns := l_columns || ':';
      END IF;
      l_columns := l_columns || rec.column_name;
    END LOOP;
    RETURN l_columns;
  END;

  FUNCTION get_form_columns(
   p_row_source IN VARCHAR2,
   p_tenant_id IN NUMBER DEFAULT g_tenant_id)
  RETURN VARCHAR2 IS

  l_columns VARCHAR(1000) := 'ID';

  BEGIN
    FOR rec IN
      (SELECT column_id, column_name
       FROM tenant_columns
       WHERE tenant_id = p_tenant_id
       AND TABLE_NAME = p_row_source
       ORDER BY column_id)
    LOOP
      IF(LENGTH(l_columns) > 0) THEN
        l_columns := l_columns || ':';
      END IF;
      l_columns := l_columns || rec.column_name;
    END LOOP;
    RETURN l_columns;
  END;

  -- initialization block
  BEGIN
    SELECT tenant_id
    INTO g_tenant_id
    FROM users
    WHERE UPPER(username) =
     htmldb_custom_auth.get_username;
  END;
/
如果您具有 3GL 编程语言的编程经验,CRM_PKG 的声明应该相当容易理解。文章写到这里,我不想解释 API 的每个方面,但要介绍一些有助于理解程序包的基本知识。在本文的后面部分,当您使用 Oracle Application Express 构建 CRM 应用程序时,我将在合适的地方进一步解释每个程序包组成部分的使用。
  • CRM_PKG 程序包规范声明了一个公用全局变量 G_TENANT_ID。程序包主体具有一个初始化块,它根据 USERS 表的查询返回的值使用承租商 ID 初始化 G_TENANT_ID。Oracle 数据库为使用程序包的每个会话维护特定于会话的程序包状态,包括全局变量的内容。例如,如果您连接到用户名为 MIKE 的应用程序,Oracle 将查询 USERS 表并使用 USERS 初始化 G_TENANT_ID 程序包变量。
  • 如果提供 USERS 表中的一个用户名,公用程序包函数 GET_TENANT_ID 将返回一个承租商 ID。例如,如果您使用 JOE 用户名连接到应用程序并调用 CRM_PKG.GET_TENANT_ID 函数,Oracle 将查询 USERS 表并返回 TENANT_ID 2。
  • 如果提供一个在 TENANT_COLUMNS 表中注册的行源名(例如,表或视图名称),公用程序包函数 GET_REPORT_COLUMNS 将从 TENANT_COLUMNS 表中返回一组以冒号分隔的列名称。例如,如果您使用 MIKE 用户名连接到应用程序,然后调用 CRM_PKG.GET_REPORT_COLUMNS 函数(其 P_ROW_SOURCE 参数设置为 CUSTOMERS),Oracle 将查询 TENANT_COLUMNS 表并返回字符串 Edit:Last Name:First Name:Address:City:State:Zip Code。
  • 如果提供一个在 TENANT_COLUMNS 表中注册的行源名(例如,表或视图名称),公用程序包函数 GET_FORM_COLUMNS 将从 TENANT_COLUMNS 表中返回一组以冒号分隔的列名称。例如,如果您使用 JOE 用户名连接到应用程序,然后调用 CRM_PKG.GET_FORM_COLUMNS 函数(其 P_ROW_SOURCE 参数设置为 CUSTOMERS),Oracle 将查询 TENANT_COLUMNS 表并返回字符串 ID:Last Name:First Name:Email。

添加一些数据


创建 CRM 应用程序之前,请花一些时间使用表 1 到 4 中显示的数据填充 TENANTS、CUSTOMERS、TENANT_COLUMNS 和 USERS 表。您可以使用 Oracle Application Express 的 Object Browser 页面(选择一个表,单击 Data,然后单击 Insert Row)或者以下 SQL 命令来进行该操作:
-- TENANTS
INSERT INTO tenants (company)
VALUES ('Mike''s Tires');
INSERT INTO tenants (company)
VALUES ('Joe''s Tires');
COMMIT;

-- CUSTOMERS
INSERT INTO customers (tenant_id, col_2, col_3, col_4, col_5, col_6, col_7)
VALUES (1,'Bobrowski','Steve','123 Maple Street','Springfield','MA','02001');
INSERT INTO customers (tenant_id, col_2, col_3, col_4, col_5, col_6, col_7)
VALUES (1,'Kyte','Tom','456 Elm Street','Reston','VA','01566');
INSERT INTO customers (tenant_id, col_2, col_3, col_4)
VALUES (2,'Ellison','Larry','lellison@aol.com');
INSERT INTO customers (tenant_id, col_2, col_3, col_4)
VALUES (2,'Gates','Bill','bgates@hotmail.com');
COMMIT;

-- TENANT_COLUMNS
INSERT INTO tenant_columns (tenant_id, table_name, column_id, column_name)
VALUES (1,'CUSTOMERS',2,'Last Name');
INSERT INTO tenant_columns (tenant_id, table_name, column_id, column_name)
VALUES (1,'CUSTOMERS',3,'First Name');
INSERT INTO tenant_columns (tenant_id, table_name, column_id, column_name)
VALUES (1,'CUSTOMERS',4,'Address');
INSERT INTO tenant_columns (tenant_id, table_name, column_id, column_name)
VALUES (1,'CUSTOMERS',5,'City');
INSERT INTO tenant_columns (tenant_id, table_name, column_id, column_name)
VALUES (1,'CUSTOMERS',6,'State');
INSERT INTO tenant_columns (tenant_id, table_name, column_id, column_name)
VALUES (1,'CUSTOMERS',7,'Zip Code');
INSERT INTO tenant_columns (tenant_id, table_name, column_id, column_name)
VALUES (2,'CUSTOMERS',2,'Last Name');
INSERT INTO tenant_columns (tenant_id, table_name, column_id, column_name)
VALUES (2,'CUSTOMERS',3,'First Name');
INSERT INTO tenant_columns (tenant_id, table_name, column_id, column_name)
VALUES (2,'CUSTOMERS',4,'Email');
COMMIT;

-- USERS
INSERT INTO users (username, tenant_id)
VALUES ('MIKE',1);
INSERT INTO users (username, tenant_id)
VALUES ('JOE',2);
COMMIT;

创建 CRM 应用程序


所有应用程序模式数据结构和相关对象都已就绪,现在可以构建应用程序的第一个版本了。如果这是您第一次使用 Oracle Application Express 构建应用程序,您会对只需单击几下鼠标即可轻松创建 Web 应用程序而感到吃惊。

要启动 Create Application 向导,单击 Application Builder -> Create,然后执行以下步骤:

  1. 单击 Create Application
  2. 指定应用程序的名称为 CRM,选择 From Scratch,然后单击 Next
  3. 简单的 CRM 应用程序只有两个页面:一个报表和一个表单。要在一个步骤中构建这两个页面,选择 Report and Form,指定 Table Name 为 CUSTOMERS,单击 Add Page,然后单击 Next
  4. 由于 CRM 应用程序只有两个页面,并不需要导航选项卡,因此单击 No Tabs
  5. CRM 应用程序不需要从其他应用程序复制任何共享组件,因此选择 No,并单击 Next 继续。
  6. CRM 应用程序将针对用户使用内置的 Oracle Application Express 验证模式,因此选择 Application Express,为 Language 指定 English,然后单击 Next 继续。
  7. 主题控制应用程序界面的外观。要匹配本文中的图形,单击 Theme 2
  8. 就这样了。只需单击 Create,Oracle Application Express 即会构建应用程序。
要查看应用程序的第一个雏形,单击 Run Application。Oracle Application Express 会提示用户提供用户凭证(使用您的开发人员用户名和口令),然后显示应用程序的第 1 页,即 Customers 报表页,如图 2 所示。

图 2. CRM SaaS 应用程序 Customers 报表页雏形



提供特定于承租商的数据安全性


您首先应该注意的是,Customers 报表页的初始版本显示了两个承租商的 CUSTOMERS 表记录,而这没有满足 SaaS 应用程序的安全性目标。要对此进行修正,您需要完成三个相关步骤:
  1. 注册与 USERS 表中的用户名相对应的 Oracle Application Express 最终用户。
  2. 添加一个应用程序项(应用程序端全局变量),CRM 应用程序可以使用该项来维护每个应用程序用户的承租商 ID。然后,修改应用程序的验证模式,以初始化该应用程序项。
  3. 修改 Customer 报表的查询,以便它只显示与当前用户的承租商 ID 相对应的记录。

创建 Oracle Application Express 用户


要创建两个与在 CRM 应用程序的 USERS 表中注册的用户相对应的 Oracle Application Express 最终用户,完成以下步骤:
  1. 单击 Application #####,其中 ##### 是与您的应用程序相对应的应用程序编号(在页面底部的 Developer 工具栏链接中)。
  2. 单击 Home
  3. 单击 Manage Application Express Users
  4. 单击 Create End User
  5. 指定 User Name 为 MIKE,并指定一个区分大小写的口令和您的电子邮件地址。
  6. 单击 CreateCreate Another
  7. 指定 User Name 为 JOE,并指定一个区分大小写的口令和您的电子邮件地址。
  8. 单击 Create User
在一个验证性 SaaS 应用程序中,承租商管理员将使用安装向导来简化应用程序用户的注册。

创建应用程序项


应用程序端全局变量在 Oracle Application Express 术语中称为应用程序项,要通过存储应用程序例程频繁引用的变量数据项来减少数据库调用次数并提高应用程序的可伸缩性和性能,它十分有用。在 CRM 应用程序中,在使用应用程序数据时,几乎每个数据库调用都会使用当前连接用户的 TENANT_ID。因此,最好创建一个可以存储每个用户的 TENANT_ID 的应用程序项。
  1. 单击 Application Builder
  2. 单击您要构建的应用程序 CRM
  3. 单击 Shared Components
  4. 单击 Application Items
  5. 单击 Create
  6. 指定新项的名称为 APP_TENANT_ID
  7. 在 Session State Protection 中,选择 Restricted— May not be set from browser
  8. 单击 Create
要在用户建立与 CRM 应用程序的连接时初始化 APP_TENANT_ID 项,完成以下步骤:
  1. 在当前页的“路径式菜单”中,单击 Shared Components
  2. 单击 Authentication Schemes
  3. 单击 Application Express—Current
  4. Login Processing 部分中,在 Post-Authentication Process 域中指定以下赋值语句。该赋值语句会调用 CRM_PKG.GET_TENANT_ID 函数,以通过引用 Oracle Application Express 的内置应用程序项 :APP_USER 来根据当前用户初始化 APP_TENANT_ID 应用程序项。
    :APP_TENANT_ID := crm_pkg.get_tenant_id(:APP_USER);
    
  5. 单击 Apply Changes。

修改客户报表的查询


要限制 Customer Report 针对当前应用程序用户显示的行,完成以下步骤:
  1. 在路径式菜单中,单击 Application #####
  2. 单击 1- CUSTOMERS 修改 Customers 报表页。
  3. Regions 部分中,单击 CUSTOMERS
  4. Source 部分中,将以下内容追加到报表的查询中:
    AND tenant_id = :APP_TENANT_ID
    
  5. 单击当前页面右上角的 Apply Changes

测试特定于承租商的报表


要查看更改并确认报表现在仅显示与特定承租商用户相对应的记录,完成以下步骤:
  1. 单击 Run
  2. 单击 Logout
  3. 使用 MIKE 用户名和这个 Oracle Application Express 用户的区分大小写的口令登录。
  4. 注意,报表仅显示第 1 个承租商的客户。
  5. 单击 Logout 注销 MIKE 身份。
  6. 使用 JOE 用户名和这个 Oracle Application Express 用户的区分大小写的口令登录。
  7. 注意,报表仅显示第 2 个承租商的客户。

    例如,当您以 JOE 身份建立连接时,显示的报表应与图 3 类似。

    图 3. Customers 报表页现在只显示与每个用户对应的特定行。

对报表的最新更改满足了小型 CRM SaaS 应用程序的基本安全性要求:也就是说,应用程序的安全性设计可以保证,即使应用程序将所有承租商的数据都存储在一起,各个承租商也只能查看自己的数据。在更高级的应用程序中,还可以使用授权模式来限制各种类型的用户可以查看的页面、区域或项。

显示特定于承租商的客户报表列


现在,承租商安全性已经得到满足,可以将注意力转向自定义应用程序的用户界面了,该界面是特定于承租商的。正如当前所示, Customers 报表页显示所有承租商的所有通用列,并使用通用列标签 COL_2...COL_9。首先,将报表修改为仅根据特定于承租商的偏好(作为 TENANT_COLUMNS 表中的元数据加以维护)显示特定的列,这通过以下步骤完成:
  1. 单击 Edit Page 1(在 Developer 工具栏中)。
  2. Regions 部分中,单击 Report(与 CUSTOMERS 相邻)。
  3. 对于每个通用列(COL_2...COL_9),单击列的编辑图标,单击 Conditions,单击 [exists] 将 Condition Type 设置为 Exists(SQL 查询至少返回一行),然后在 Expression 1 域中指定一个 EXISTS 查询。例如,对于 COL_2,指定以下查询:
    SELECT NULL FROM TENANT_COLUMNS
    WHERE TABLE_NAME = 'CUSTOMERS'
    AND tenant_id = :APP_TENANT_ID
    AND COLUMN_ID = 2
    
    对于 COL_3,您将指定以下查询:
    SELECT NULL FROM TENANT_COLUMNS
    WHERE TABLE_NAME = 'CUSTOMERS'
    AND tenant_id = :APP_TENANT_ID
    AND COLUMN_ID = 3
    
    为每个通用列重复该步骤。注意,只有针对每个通用列的显示条件而更改的查询部分才是 COLUMN_ID 选择条件(上述每个查询中加黑的部分)。
  4. 您只需单击方便的 <> 图标即可在列属性页面间移动,而无需单击 Apply Changes 再单击每个列的编辑图标。在修改所有通用列之后,单击 Apply Changes
报表目前使用静态报表列标题,但是根据当前使用应用程序的承租商用户,您需要将它们改为动态的。要使报表的列标题特定于承租商,完成以下步骤:
  1. Headings Type 列表中,单击 PL/SQL
  2. 在所显示的函数返回的以冒号分隔的标题域中,指定以下 PL/SQL 块以调用 CRM_PKG.GET_REPORT_COLUMNS 函数,并返回一个特定于承租商的列标题列表。注意,在下面的调用中,无需为函数指定 P_TENANT_ID 参数,因为它有一个默认值,即全局程序包变量 G_TENANT_ID — 将这个使用默认值的 API 设计包含在内是为了帮助减少编程出错的可能性,以免导致承租商间的数据泄露。
    begin
    return crm_pkg.get_report_columns('CUSTOMERS');
    end;
    
  3. 报表不应显示 TENANT_ID 列,也不应泄漏共享该应用程序的其他承租商。因此,取消选中该列的 Show 复选框。
  4. 单击 Apply Changes
要测试现在仅根据元数据进行修改的报表的动态特性,完成以下步骤:
  1. 单击 Run
  2. 注意页面的左下角,页脚标明了连接用户名,根据您最近的登录,该用户名应该为 JOE。显示的报表应该类似于图 4。注意,现在报表只显示第 2 个承租商用户的行和相应的列标题。

    图 4. Customers 报表针对与第 2 个承租商相对应的用户所显示的内容

  3. 单击 Logout
  4. 以 MIKE 身份登录。
  5. 注意,现在报表只显示第 1 个承租商用户的行和相应的列标题,与图 5 类似。

    图 5. Customers 报表针对与第 1 个承租商相对应的用户所显示的内容

总之,根据使用应用程序的承租商用户的不同,即使是同一个报表,其显示的内容也是不同的。

显示特定于承租商的 Customers 表单


下一组任务将处理 Customers 表单(应用程序的第二个页面)最初版本中的几个问题。该表单仅使用通过承租商安全的 Customers 报表页面访问的行,因此没有需要解决的安全问题,但表单有几个用户界面问题需要解决。要显示 Create Application 向导构建的表单的初始版本,单击其中一个客户的 Edit 图标。Customers 表单应类似于图 6。

图 6. 未经修改的 Customers 表单



首先,请注意,该表单显示了外键列 (TENANT_ID) 的输入域,以及所有通用列(COL_2 到COL_9)的域。无论使用该应用程序的承租商用户是谁,Oracle Application Express 都会显示表单的同一版本。要使表单中的域特定于承租商,您需要隐藏 Tenant Id 域,然后修改每个通用列所对应的项,以根据 TENANT_COLUMNS 表中的元数据有条件地显示。下面是操作步骤:
  1. 单击 Edit Page 2(在 Developer 工具栏中)。
  2. Items 部分中,单击 P2_TENANT_ID。
  3. Name 部分中,单击 [Hidden] 隐藏 P2_TENANT_ID 域。
  4. Default 部分中,将 Default Value 指定为 :APP_TENANT_ID 并将 Default Value Type 设置为 PL/SQL Expression,以便表单插入的所有新记录都使用承租商用户的承租商 ID。
  5. 单击页面顶部的 > 按钮,移到下一表单项 P2_COL_2 的页面。
  6. 要查看当前项的 Conditions 部分,单击 Conditions
  7. 您可以使用项的 Conditions 部分指定一个在 Oracle Application Express 要在运行时显示该项时必须为 true 的条件。首先,单击 [exists] 表明您要指定一个 EXISTS 查询。然后,在 Expression 1 域中,输入使用与 COL_2 对应的元数据的以下查询:
    SELECT NULL FROM TENANT_COLUMNS
    WHERE TABLE_NAME = 'CUSTOMERS'
    AND tenant_id = :APP_TENANT_ID
    AND COLUMN_ID = 2
    
  8. 单击 > 移到下一个通用列,并为与通用列 COL_3 到 COL_9 对应的其余所有项重复步骤 7。对于每一项,只需更改 COLUMN_ID 选择标准(上面高亮显示的部分)即可。例如,对于 COL_3,查询将如下所示:
    SELECT NULL FROM TENANT_COLUMNS
    WHERE TABLE_NAME = 'CUSTOMERS'
    AND tenant_id = :APP_TENANT_ID
    AND COLUMN_ID = 3
    
  9. 指定表单项 P2_COL_9 的显示条件之后,单击 Apply Changes
  10. 单击 Run 查看表单的新版本,该版本应类似于图 7。

    图 7. 经过修改的 Customers 表单(具有特定于承租商的域)

注意,表单中不再显示 P2_TENANT_ID、P2_COL_8 和 P2_COL_9 表单项,这是因为针对每个表单项使用了基于元数据的条件显示查询。

需要在表单上调整的最后一项内容是 Customer 表单域的标签集。与报表列标签不同,您需要费点脑筋来解决这个问题。首先,您需要为表单添加一个新的隐藏域,用于存储一组以冒号分隔的、特定于承租商的列标签,这些列标签可以通过调用 CRM_PKG.GET_FORM_COLUMNS 函数来检索。

  1. 单击 Edit Page 2。在 Items 部分中,单击 Create 图标启动 Create Item 向导。
  2. 单击 Hidden
  3. Item Name 设为 P2_LABELS,将 Sequence 设为 0,并将 Region 设为 Breadcrumbs(1),然后单击 Next 继续。
  4. Item Source 设为 PL/SQL Expression or Function,将 Item Source Value 设为 crm_pkg.get_form_columns('CUSTOMERS'),然后单击 Create Item
接下来,您需要在区域标头中添加一段 JavaScript 代码,表单将使用该代码分析 P2_LABELS 隐藏域中的标签。
  1. 在 Regions 部分中,单击 CUSTOMERS
  2. Header and Footer 部分中,在 Region Header 域中指定以下 JavaScript,然后单击 Apply Changes
    <script type="text/javascript">
     var all_labels = document.getElementById('P2_LABELS').value;
     var aLabels=all_labels.split(":");
     function Label(pNum) {document.write(aLabels[pNum-1]);}
    </script>
    
最后,您必须编辑每个通用列的 Label 设置,以便列调用新的 JavaScript 函数在运行时动态设置其标签。为此,完成以下步骤:
  1. Items 部分中,单击 P2_COL_2
  2. Label 部分中,在 Label 域中指定以下内容:
    <script>Label(2);</script>
    
  3. 单击 > 移到下一项,即,通用列 P2_COL_3。
  4. Label 部分中,在 Label 域中指定以下内容:
    <script>Label(3);</script>
    
    注意,每个 Label 函数调用中的参数都与通用列编号相匹配。
  5. 为与通用列对应的每一项重复步骤 3 和 4,并更改 Label 函数调用中的参数。
  6. 修改 P2_COL_9 之后,单击 Apply Changes
  7. 要查看 Customers 表单的最终版本,单击 Run
Customers 表单的新版本(如果您以 MIKE 身份连接)应类似于图 8。

图 8. Customers 表单的最终版本(包含第 1 个承租商的特定于承租商的列标签)



如果您注销身份,再以 JOE 身份连接,并编辑一个客户,则新表单将类似于图 9。

图 9. Customers 表单的最终版本(包含第 2 个承租商的特定于承租商的列标签)

实践总结


这个使用 Oracle Application Express 构建 SaaS 应用程序的实际演示证明了几个值得回顾的关键点:

  • Oracle Application Express 是一个基于向导的应用程序开发工具,有了它,构建和维护基于 Web 的数据库应用程序变得很简单。
  • Oracle Application Express 可创建一个参数应用程序,可以使用能够读取 Oracle 数据库中存储的元数据的 PL/SQL 例程在运行时动态生成 Web 页。
  • 通过使用自定义元数据扩展应用程序的数据模型,您可以利用 Oracle Application Express 的元数据驱动方法创建多态应用程序,以便动态适应特定条件(例如,当前连接的用户)。要开发健壮的 SaaS 应用程序,元数据的使用很关键。
  • 从应用程序开发人员/提供商的角度来看,SaaS 应用程序必须易于维护。单一版本的 CRM 演示应用程序即可满足所有承租商的需求,并且应用程序使用单一数据库模式和单一应用程序数据结构集来存储所有承租商的混合数据。
  • SaaS 应用程序还必须满足每个承租商的各种需求。CRM 应用程序的身份验证和安全设计可确保承租商只能查看自己的数据,并且该应用程序完全支持特定于承租商的自定义,而无需任何自定义应用程序代码。
  • 在 SaaS 应用程序开发过程中,应用程序效率和调整是您必须事先考虑的关键事项。随着越来越多的用户订阅服务,绑定变量的使用、正确的数据访问结构(索引、按索引组织的表等)、应用程序端全局变量以及其他减少应用程序功能整体开销的方法均有助于提高 SaaS 应用程序的可伸缩性。

SaaS 应用程序托管和维护


恭喜!如果您完成了本文前面部分中的步骤,那么您已经构建了第一个 SaaS 应用程序(虽然非常简单)。但现在,您必须关注另一方面:SaaS 的操作方面。请记住,SaaS 应用程序是托管解决方案,服务提供商必须支持它们才有希望获得大量承租商。SaaS 的可用性、可伸缩性和管理是下一步要考虑的事项。以下部分简要介绍了其中每个主题,然后继续讨论本文基于 Oracle 数据库的主题,以演示目前可用的实际解决方案。

可用性


当客户使用 SaaS 运行任务关键的业务操作时,服务必须随时可用。在客户看来,如果应用程序随时都会中断(而不是在偶尔进行的定期维护时段才中断),那么 SaaS 的成本优势和便捷优势将变得毫无意义。因此,SaaS 提供商通常要承诺一个合法的服务级协议 (SLA),然后必须立即实现符合该协议所需的技术;否则,服务毫无疑问会失败。

SaaS 提供商可以承诺的正常运营时间主要取决于该提供商决定使用的硬件和数据库系统。图 10 展示了 Oracle 技术系列的某些关键特性,慎重的 SaaS 提供商通常会使用这些特性来帮助确保服务的可用性。

图 10. Customers 表单的最终版本(包含第 2 个承租商的特定于承租商的列标签)



注意,图 9 特别演示了各个级别(包括硬件、软件和数据)的冗余如何有助于维护 SaaS 应用程序系统的可用性:
  • 独立磁盘冗余阵列 (RAID) 存储体系结构在关键任务系统中很常见。通过跨多个磁盘分割和镜像数据块,RAID 可以帮助减少数据访问次数,并消除与物理存储相关的单点故障。Oracle 数据库的自动存储管理 (ASM) 特性是一个以数据库为中心的文件系统和卷管理器,Oracle 系列产品可以依赖这个特性更轻松地管理和帮助提高与 Oracle 相关的数据库文件的可用性。
  • 高可用性集群(或故障切换集群)是硬件和软件实现,可以通过提供多个冗余系统组件来帮助确保相关服务的可用性。例如,Oracle 真正应用集群 (Oracle RAC) 是 Oracle 数据库的一个特性,它允许执行 Oracle 数据库软件的多个节点同时访问同一共享数据库;如果其中一个节点由于操作系统、软件或硬件问题而发生故障,仍然可以通过其余完好的节点访问物理数据库。Oracle RAC 还支持滚动升级,以帮助减少计划的停机时间。通过滚动升级,您可以在 Oracle RAC 配置中一次关闭一个节点,然后对其进行升级或修补,而集群中的所有其他节点将继续提供对底层数据库的访问。
  • SaaS 提供商通常需要设计并实现一个灾难恢复计划,该计划应该包含所需的数据、硬件和软件,以便在整个网站由于灾难性事件(例如,火灾、地震或更大的灾难)而发生故障时,能够支持正常的业务运营。灾难恢复策略通常需要配置一个或多个远程备用系统,并对其进行实时维护,以便在整个网站发生故障导致主站点不可用时立即实现故障切换。Oracle Data Guard 是 Oracle 数据库的一个特性,它能够配置并自动维护备用数据库,以支持灾难恢复计划。

高级数据库特性(例如,ASM、Oracle RAC 和 Oracle Data Guard)并不是数据库系统中可以帮助维护高可用性的唯一特性。在更基础的级别上,例行的数据备份操作不会明显地降低客户管理日常业务运营所依赖的 SaaS 应用程序的可用性或性能。Oracle 数据库及其随附的恢复管理器 (RMAN) 实用程序为 SaaS 提供商提供了一组完美的综合数据库备份特性,包括在线(热)、增量和并行数据库备份。补充的 RMAN 数据库恢复特性(例如,在线表空间、数据文件和块级恢复)还可以在物理媒介故障使得计划最周密的冗余防御出现漏洞时,帮助确保 SaaS 应用程序的一般可用性。

但是,或许 SaaS 提供商必须考虑的最可能发生、最棘手的数据可用性情况是特定于承租商的数据问题。例如,如果您的 CRM 应用程序的承租商错误地删除了所有客户,并要求立即进行数据恢复操作,您该怎么办?当然,这种要求很不合理,由于一个承租商的错误,SaaS 系统的所有其他承租商都必须承受数据库恢复所导致的停机时间,并丢失已提交事务的工作。Oracle 数据库具有许多逻辑数据恢复特性,可以帮助 SaaS 提供商从不可避免的事件(例如,用户错误、故障批处理作业以及错误事务)中恢复。例如,在上述案例中,您可以使用 Oracle 数据库的易于使用的闪回事务查询特性来撤消错误事务的结果,并恢复表中特定于承租商的行,同时其他承租商可以继续使用应用程序及其基础表,而不会出现任何中断情况。

可伸缩性


对于独立软件供应商来说,SaaS 在经济上并不可行,并且应用程序的性能也无法满足应用程序的客户,除非随着越来越多的用户订阅服务,该应用程序可以借助其底层系统设计进行扩展。要想获得成功,SaaS 提供商必须从头设计兼具吞吐量和性能的应用程序和支持系统。

用于 SaaS 实现的产品系列中的许多特性(无论大小)都可以作为一个整体对系统的可伸缩性做出重大贡献。例如,考虑与在 SQL 语句中使用绑定变量一样简单而完善的特性如何帮助提高 SaaS 应用程序的可伸缩能力,以获得较高的事务处理率。如果 SQL 语句的 WHERE 子句使用绑定变量(参数)和运行时值替代,则数据库系统将进行分析和优化,然后在内存中缓存编译的语句,以最小化执行重复调用语句所需的 CPU 和内存的数量。如果没有绑定变量,数据库系统会不断浪费大量 CPU 时间和服务器端内存,来代替标准事务处理一次又一次地重新编译实际上完全相同的 SQL 语句。

数据分区是大部分 SaaS 应用程序广泛用于提高可伸缩性的另一个数据库特性。分区是一个存储选项,可以在物理上分离一个表中的数据。例如,在 CRM 演示应用程序中,您可以使用 Oracle 数据库创建 CUSTOMERS 表,以便数据库在单独的分区中存储每个承租商的行。从性能和可伸缩性的角度来看,良好的分区布局可以帮助减少或完全消除承租商之间为了数据访问而进行的物理资源争用。从应用程序开发人员的角度来看,分区表和非分区表的逻辑结构完全相同,因此利用分区功能不需要额外技能。

SaaS 环境中的另一个特殊的可伸缩性考虑事项是,应该指出如何控制共享一个应用程序实例的每个承租商的资源使用。例如,一个非常活跃的承租商不应该长期占用数据库服务器的 CPU 时间,以至于影响到共享同一应用程序实例的其他承租商的体验。Oracle 数据库的资源管理器特性提供了一个巧妙的方法,可以让应用程序承租商公平地分区访问系统范围的资源(例如 CPU)。

如果您确定应用程序的底层机制高效且可伸缩,则利用负载平衡和网格计算等面向硬件的方法,连用户最多的 SaaS 应用程序都可以近乎无限制地伸缩。例如,您可能使用一个所有承租商用户引用的虚拟 HTTP 服务器来连接到 SaaS 应用程序。负载均衡器可以在用户不知情的情况下将页面请求重定向到中间层 Oracle 应用服务器来处理请求。反过来,每个应用服务器都连接到 Oracle RAC 配置中的特定实例。如果特定承租商的请求与特定应用服务器/数据库实例的数据库访问方式相关联,则每个承租商的数据将保留在特定数据库实例的缓冲区缓存中,这将在多个承租商之间进一步提高应用程序的可伸缩性。随着越来越多的承租商订阅服务,您可以向负载平衡配置中添加更多的应用服务器和数据库实例。

数据和应用程序管理


管理 SaaS 操作中的数据和应用程序部署会遇到许多特殊挑战,提供商必须解决这些挑战才能获得成功。例如,考虑以下几个可能的情形:

  • 承租商需要应用程序的临时实例(通常称为沙箱),以便在将新的自定义移到生产应用程序实例之前对其进行测试。
  • 具有超大处理负载的承租商要求您将数据从共享应用程序实例移植到专用实例,以获得更高的订阅费。
  • 承租商希望将每天的数据转储为 XML 格式,以消除对数据丢失控制的担忧。
此类要求可能很常见,因此 SaaS 提供商最好能够设计并验证可以轻松处理这些要求的过程。例如,如果提供商选择 Oracle 数据库来支持 SaaS 应用程序,则提供商可以使用 Oracle Data Pump 导出和导入实用程序轻松地将特定于承租商的数据从一个数据库移植到另一个数据库。此外,Oracle 数据库和 Oracle Application Express 都具有用于将数据库数据导出到 XML 文件的实用程序。

应用程序的供应和维护也是 SaaS 提供商必须考虑的关键问题。使用 Oracle Application Express,许多常见的应用程序维护步骤都得到了简化。例如,一旦构建了 Oracle Application Express 应用程序,您就可以将其定义和支持对象打包到一个文件中,将文件上载到其他服务器,并使用向导在新实例中安装该应用程序。未来版本的 Oracle Application Express 还将提供一个支持对象特性,用于简化将更新分发和应用到现有应用程序的过程。

安全性


考虑到安全性是采用 SaaS 的主要障碍之一,提供商需要向潜在客户证明已经采取了任何可能的措施来保护他们的数据。以下是 SaaS 提供商在设计安全性策略时应该考虑的几个方面。

首先,在管理关键客户数据的按需软件服务中进出的所有数据传输都必须加密,以保护数据的完整性。所有 Web 浏览器通信都应该使用安全的 HTTPS 协议(而非 HTTP),Telnet 和 FTP 访问应该禁用,而由 SSH 和 SFTP 取而代之,并且所有直接的 Oracle 数据库连接都应该使用加密的 Oracle Net 连接(通过 Oracle Advanced Security 选件启用)。

机密业务信息 (CBI) 的加密格式的物理存储可以在介质失窃的情况下帮助保护敏感数据。例如,假设 SaaS 提供商加密了 CRM 数据库中的敏感数据,然后备份数据库,并将备份磁带(而不是数据的解密密钥)远程存储在第三方存储设备中。由于所有关键业务数据都已加密,因此数据窃贼无法使用偷窃的备份磁带恢复数据库,也就无法查看机密业务信息。Oracle 数据库的 Advanced Security 选件具有一个称为透明数据加密 (TDE) 的特性,您可以使用该特性加密数据库中选定的列,并保护 CBI 以防止所有下游组件(备份、重做日志等)中的刺探程序。

毫无疑问,数据在宿主设备中遭到破坏的可能性是每个 SaaS 提供商必须考虑和解决的问题。一旦提供商实现、调试并验证了 SaaS 应用程序,生产服务的开发人员和管理员就没有充分的理由来证明后续的数据访问 — 换言之,一旦提供商部署了 SaaS 应用程序,则只有客户自己才能查看他们的数据。SaaS 提供商可以使用 Oracle Database Vault 来限制数据库管理员、应用程序管理员和应用程序开发人员的数据访问权。

结论


这篇介绍性文章涵盖了大量与“软件即服务”(SaaS) 应用程序的设计、开发和托管相关的主题。此外,本文还介绍了
  • SaaS 应用程序如何为软件使用者节省金钱,以及如何为应用程序提供商带来更多利润
  • 一些基本的 SaaS 概念,包括多租户和参数应用程序
  • SaaS 应用程序使用元数据动态调整它们的运行时行为是多么高效
  • 一些使用 Oracle Application Express 开发应用程序的基本技巧,包括使用向导、命令行工具、实用程序、PL/SQL 和 JavaScript 编码等等
  • 许多 Oracle 数据库特性,独立软件供应商可以使用这些特性提供可靠的、可伸缩的、可维护的以及安全的 SaaS 应用程序

边栏:可扩展的数据模型选项


本文中的演示应用程序展示了支持承租商可配置的 SaaS 应用程序的静态通用列数据模型。您可能会考虑的另一个实现方法是一个三层数据模型,图 11 针对您熟悉的 CUSTOMERS 表对该模型进行了说明。

图 11. 用于扩展 CUSTOMERS 表的三层数据模型


通过一个三层数据模型,每个应用程序实体的各个 _EXTENSIONS 表都会为每个特定于承租商的基本实体扩展记录一个标签和数据类型。第三个 _EXTENDED_DATA 表记录每个基本实体扩展的值。例如, 表 2 中的客户数据在三层数据模型中将显示如下。


CUSTOMER_ID TENANT_ID LAST_NAME FIRST_NAME RECORD_ID
1 1 Bobrowski Steve 1000
2 1 Kyte Tom 1001
3 2 Ellison Larry 1002
4 2 Gates Bill 1003

TENANT_ID EXTENSION_ID EXTENSION_LABEL EXTENSION_TYPE
1 1 地址 VARCHAR(100)
1 2 城市 VARCHAR(50)
1 3 VARCHAR(50)
1 4 邮政 编码 VARCHAR(15)
2 1 电子邮件 VARCHAR(100)

RECORD_ID EXTENSION_ID VALUE
1000 1 123 Maple Street
1000 2 Springfield
1000 3 MA
1000 4 02001
1001 1 456 Elm Street
1001 2 Reston
1001 3 VA
1001 4 01566
1002 1 lellison@aol.com
1003 1 bgates@hotmail.com

每种类型的可扩展数据模型都具有 SaaS 应用程序的优势和劣势。例如,将静态通用列数据模型与三层数据模型进行比较:

  • 通用列方法在承租商可以配置的列数上有上限,而三层方法在理论上没有限制。
  • 三层方法需要更大的工作量和复杂的应用程序代码。
  • 要显示实体(例如,一个客户)的相关数据,您需要联接三个表。这个要求会影响已完成应用程序的可伸缩性和性能。
与使用任何其他应用程序一样,您应该根据应用程序必须满足的要求优先顺序列表来选择设计。在这个特定示例中,如果无限制自定义是最高优先级,则最好选择三层数据模型,但是如果性能极为重要,则最好选择通用列方法。

Steve Bobrowski 自 Oracle 数据库版本 5 开始一直使用该软件。他以前在 Oracle 工作过,还是 The Database Domain (dbdomain.com) 的创始人以及五本 Oracle Press 书籍的作者(包括《Oracle 数据库 10g 快捷版上机操作》系列)。Steve 目前是 Computer Sciences Corporation 的 SaaS 业务首席技术官。