作者:Andrejus Baranovskis
2017 年 5 月
本文主要介绍 Java 云服务 (JCS) 生产应用,包括发票处理、仓库库存管理和生产线管理。此应用是针对立陶宛的一家糖果生产和销售初创公司实现的。
该初创公司此举的主要目标是尽量精简 IT 基础设施和投资,为此才选择了 JCS。Oracle ADF 已经集成到 Oracle PaaS 云产品中。JCS 提供了一个基于云的部署环境,预先配置了 Oracle ADF 特性支持。Oracle ADF 为 ADF BC 层中的业务逻辑实现提供了强大的功能。它允许通过 REST 服务公开 ADF BC 对象和方法。
虽然 ADF 支持是决定采用 JCS 的关键因素之一,但也可以从 JCS 实例提供 Oracle JET,这样非常方便,让我们可以从同一个云实例运行服务器端业务逻辑并提供客户端内容。
发票处理模块基于下列功能:
UI 完全用 Oracle JET 实现,它支持开箱即用的自适应 UI 行为。这意味着 UI 仅实现一次,然后针对用户查看资料所使用的任何屏幕进行调整,无论是台式机还是移动设备。
Invoice Search 屏幕上具有搜索表单块和分页结果表:
图 1
图 2 显示了同一个 Invoice Search 表单在移动设备屏幕上显示的情形。JET 表分页控件会自动更新,以适应窄屏布局:
用户可以选择发票进行编辑。Invoice edit 屏幕中有发票数据编辑功能。我们使用 JET 自带的 Alta UI 外观。Alta UI 模板提供了简洁明快的 UI 体验(图 3):
发票项目可在对话框内编辑。这样最终用户就可以专注于特定项目,更容易跟踪更改。Oracle JET 在客户端运行,发票项目之间的导航很迅速。不需要每次都关闭对话框;当用户想要打开另一个发票项目时,只需点击该发票项目即可(图 4):
该应用基于两个部署构件。有两个单独的 EAR 部署存档,一个用于 ADF 业务组件 REST 服务,另一个用于 Oracle JET 客户端应用。这两个构件都部署到一个 JCS 实例。ADF BC REST 服务由 Oracle JET 应用使用。
ADF BC REST 服务很安全,只有经过身份验证和授权的用户才能访问。(图 5)
包含 Oracle JET 的 EAR 仅作为包装器。该 EAR 的所有内容均不在服务器上执行。
使用以下 Grunt 命令生成 Oracle JET 精简版,用于生产部署:
sudo grunt build:release
将精简后的内容复制到一个包含 web.xml 的空 Web 应用中,打包成 EAR 文件,再部署到 Oracle Java 云 (JCS) 上运行的 WebLogic 实例。
ADF BC REST 应用经过编译后打包到自己的 EAR 中,部署到 Oracle JET 所在的 WebLogic 实例上。
复杂的业务逻辑和业务规则验证使用 ADF BC 自定义方法实现。这些自定义方法通过 ADF BC REST 接口公开,可供客户端调用访问。ADF BC REST 服务负责提供搜索、更新、创建和删除等标准操作。
实现 Oracle JET 客户端逻辑以尽量减少 REST 服务调用。例如,当用户更改发票项目数据时,我们正在同步客户端上的发票项目模型,无需重新从服务器查询完整的发票项目列表。
使用 Oracle JET 实现 UI
应用结构遵循 Oracle JET 模板结构。每个逻辑应用模块都在单独的 JET 模块中实现。我建议创建通用模块,在其中保留模块特有的通用函数和变量。这样更易于重用,尤其是您要在一个模块中使用另一个模块中的服务时。
图 6 显示了 JET 应用结构:
我们以 invoice 模块为例。该模块导入另外两个通用模块:invoiceController 和 customerController。在 define 块中声明模块将执行导入。要在 JET JS 函数中使用模块,应注意顺序。通常,您将先获取函数变量列表中 JET Core 和 Knockout 导入的变量;接下来应是用于您自己模块的变量,顺序与 define 块中设置的相同。(图 7)
invoice 模块提供客户列表,以便用户编辑发票和更改分配的客户。为此我们导入了 customerController 模块,在其中构造一个对应的 REST 服务调用来提取客户数据(图 8):
客户模块中使用了相同的 customerController 模块(图 9):
这种方法将功能拆分成多个可重用模块,将来更便于维护应用。
菜单结构用 JET 路由器实现。在更复杂的应用(如登录页面实现)中,静态菜单结构是不行的。在用户通过身份验证之前,应只能访问登录模块,不包括应用菜单列表;身份验证成功后,应用菜单应取代登录模块。
使用模板创建的 Oracle JET 应用自带 applicationController 模块,在首次应用访问时自动加载。默认情况下,它包含 JET Router 初始化代码。在本例中,我已将其更改为只包含 login 模块。这意味着默认情况下,用户只能访问 login 模块(图 10):
身份验证成功之后,我们在成功回调中用应用菜单结构重新初始化 JET 路由器。通过 login 模块,我们可以访问路由器根实例,用菜单结构对其进行配置(通过在数组中列出 JET 模块名称)。还应构造包含导航数据(带有对用户可见的标签)的数组,就是用菜单标签重新设置原始数组(图 11):
最后应调用 JET 路由器 API 函数 synch,强制在 UI 上显示更改后的菜单。
阅读更多内容:JET 路由器简明手册。
为了更好的维护,Oracle JET 应用可以分成多个模块。但有时不同模块彼此依赖(例如,发票模板模块中可能使用客户列表)。同时,客户模块中也在更新客户列表。每次客户模块中更新列表时,模板模块中的客户列表也应同步更新最新的更改。
我们可以通过在依赖模块中调用 JS 函数来实现。此类调用可以使用 define 块中导入的模块完成。在下面的示例中,我将介绍当客户列表在客户模块中发生更改时,如何在发票模板模块中刷新客户列表。
请确保在客户模块的 define 块中包含依赖模块(模板模块)(图 12):
一旦包含了该模块,我们就可在客户模块中的任何位置引用它并调用函数。如图 13 所示,在客户更改成功回调中,我们在模板模块中调用 synchCustomersList 函数:
我们来看看这个函数执行了哪些操作。在这个特定的实现中,它清除模板模块中的客户条目数组,通过 REST 服务调用提取刷新列表,从头开始填充数组。我们可以只传递更改的客户模型并将其同步到数组中;但这里的业务逻辑要求是在每次客户数据发生更改时重新提取整个客户列表(图 14):
Oracle JET 可以构建复杂布局的表单。这些表单本身就是自适应的。请在 JET 简明手册中阅读更多相关信息:列网格。
在宽屏布局中,表单显示为三列 — 第一列两行,第二列一行,第三列两行:
如果是窄屏显示,表单布局就调整为一列(图 16):
为了实现这种行为,我们使用 Oracle JET flex 类。下面是第一列的示例,其中包含客户搜索列表和状态选择列表(图 17):
Oracle JET flex 类中还封装了另外两个列(图 18):
必须保护 REST 服务调用。只有经过身份验证和授权的用户才能访问 REST 服务和数据。
在我们的系统中,我们依赖对 ADF BC REST 服务的第一次 REST 调用中生成的服务器端会话 ID。我们调用通过 ADF BC REST 服务公开的自定义方法,并通过 Authorization 标头传递用户名/密码(图 19):
如果身份验证成功,在回调中,我们从自定义响应参数中读取分配的服务器端会话 ID,并将其存储在客户端。同时,我们清除用户名/密码值,以免将它们保留在客户端上。所有后续 REST 调用均使用该会话 ID 完成,此值随每次 REST 调用传递。服务器端会话存在相同的 ID,以便访问服务器端服务,而无需在客户端保留用户名/密码(图 20):
Oracle JET 在客户端验证和服务器验证方面都非常出色。我们同时采用这两种验证。决定在何处实现验证逻辑并不容易;根据我们的经验,最好在客户端和服务器上复制一些重要验证。客户端验证将提高性能,而服务器验证将确保仅将正确的数据提交到数据库。当验证逻辑很复杂时,在服务器端实现可能轻松些。
在本例中,客户端验证检查列表中是否存在该值(图 21):
如果列表未返回任何键,则意味着该值不存在,我们将提供错误消息文本。JET 提供了一个跟踪器对象,可以在所有带验证的字段上注册。如果出现验证错误,可以在方法的开头检查跟踪器状态,停止进一步的执行。
可以通过 messageCustom 属性定义 Oracle JET HTML 组件的错误消息。跟踪器通过 invalidComponentTracker 属性定义(图 23):
服务器端验证的实现方式非常类似:它在 UI 上的报告方式与客户端验证相同(图 24):
在 REST 服务调用失败回调中指定错误消息。我们读取错误响应文本,根据键确定要返回的错误文本(图 25):
阅读更多内容:跨字段验证简明手册。
使用 ADF BC REST 实现服务器端逻辑和服务是我们自然而然的选择。Oracle ADF BC 技术提供了强大的 API 在后端管理和处理数据 — 它与数据库很好地集成,提供了各种选项来覆盖数据处理事件(如更新前、删除前等)。这便于编写业务逻辑和验证规则来保护和确保数据的一致性。可以从各种客户端访问 ADF BC 实现,包括 Excel、ADF Faces 和 REST 客户端。ADF BC 提供的 REST API 很丰富,提供了分层数据查询、分页、批处理和自定义方法调用等选项。
ADF BC REST 标配 ADF Security 保护。这可以确保对 REST 资源的访问经过身份验证,并提供授权支持。配置很简单;开发人员需要运行一个向导,在项目中启用身份验证/授权支持(图 26):
该向导更新配置文件条目,让 REST 服务得到保护。在身份验证阶段,它依赖 WebLogic 安全提供程序来验证用户在系统中是否通过身份验证。这样就使得 ADF Security 实现非常灵活,可兼容各种安全解决方案 — 默认身份验证程序、基于 SQL 的身份验证程序、Active Directory 等。
可以使用基本身份验证来完成客户端的初始身份验证。但是,我们不想在客户端保留用户名/密码值以用于同一会话中的后续调用。解决方案是将当前会话 ID 返回给客户端,用于后续调用。要将会话 ID 返回给客户端,可以将其包含到自定义响应参数中。
这就必须先定义一个自定义筛选器类来拦截对 ADF BC REST servlet 的请求(图 27):
自定义筛选器可以映射到 ADF BC REST servlet,从而拦截请求(图 28):
我们只对调用自定义登录方法的第一个请求感兴趣。仅当身份验证成功时,才调用筛选器。仅当对自定义方法 (POST) 进行调用并且请求包含 Authorization 标头时,我们才设置自定义响应参数。后续调用均不包含 Authorization 标头;请求将用会话 ID 完成(图 29):
ADF BC 支持对从数据库提取的数据进行范围分页。这样我们就可以尽可能压缩提取的行数,不提取客户端未请求的任何行(图 30):
ADF BC REST 也支持范围分页。客户端可以请求只返回特定页面的行。以下是此类请求的一个示例:
invoicing/rest/v0/Invoices?limit=5&offset=20&totalResults=true
我们请求返回从位置 20 开始的 5 行。响应中必须包含结果总数,以便计算总页数。这样我们就可以构建支持分页的客户端表(图 31):
ADF BC REST 的一项优势就是可在实体类、视图类和应用模块类中选择自定义方法实现。自定义方法中编写的逻辑可以处理用户输入、更新/创建数据并执行复杂查询。View 对象中实现的自定义方法可通过 ADF BC REST 接口公开。要在 ADF BC REST 中公开,该方法应在客户端接口中定义(图 32):
以下是此类方法的一个示例(图 33):
ADF BC 中的自定义方法可以将复杂逻辑从客户端移至后端,并通过 API 从客户端调用。在本示例中,我们将基于发票模板新建一个发票。首先根据提取的行提取发票模板数据;然后创建发票行并存储在数据库中。
用于 REST 访问的自定义方法在 ADF BC REST 资源向导中定义(图 34):
如果您查看 ADF BC REST 资源源代码,就会发现其结构与 ADF 页面定义非常类似。这样,ADF 开发人员就很容易了解 REST 服务是如何定义的,甚至还可以直接在源代码中调整某些属性(图 35):
ADF BC 提供了内置验证规则的列表。除了内置规则,它还允许用 Java 或 Groovy 构建自定义验证规则。这证明是非常有用的,因为确保了数据完整性:未通过验证规则的无效数据不会提交。部分验证规则可在客户端实现。但是,在后端复制关键验证规则也是有道理的 — 只是为了确保客户端不会绕过客户端检查滥用服务和提交无效数据。复杂的验证规则(需要提取大量数据和比较各种元素)应在客户端进行。但是没有灵丹妙药;一切都取决于具体要求。
以下是 ADF BC 中验证规则的示例:
经验证,发票日期并不在付款到期日期之后。如果发生验证错误,将向客户端报告错误代码,并在客户端显示相应的错误文本。
下面的示意图介绍了我们 Oracle 云的使用情况。开发阶段用 Oracle 开发人员云服务 (DCS) 完成。这包括 Wiki、问题跟踪、敏捷冲刺、Git 源代码控制和部署自动化。开发和生产运行时在 Oracle Java 云和相关 DB 云实例上运行(图 37)。
图 38 显示了 Oracle 开发人员云服务提供的冲刺报告中的问题数量:
对我们开发团队而言,Oracle 云在基础设施设置和管理方面有巨大的优势。我们不需要关心 WebLogic 服务器的安装和配置。使用 Oracle 云,我们只需要键入几个参数,等待大约 15 分钟,让实例后台进程完成 — 就准备好了。之前,基础设施设置可能要数天时间。
当前 Oracle 云环境支持新的 ADF 版本 (12.2.1.2)。我们将使用此版本运行 ADF BC REST 后端服务。版本在云实例创建向导的第一步中指定(图 39):
我们的生产实例运行在一个 1 CPU、7.5 GB RAM 的实例上。我们发现,这种配置对于我们的开发环境和生产使用已经足够。Oracle 云 JCS 实例吞吐能力很大,但对于内部部署设置而言,您可能需要更多计算资源来处理相同的负载量(图 40):
在大约 15 分钟内,将创建一个 JCS 实例 — 又快又简单(图 41):
在 JCS 实例中,使用 Oracle Enterprise Manager 分配 ADF BC REST 服务资源权限。
权限映射到应用角色(图 42):
应用角色映射到组。组和用户映射在 JCS 实例身份验证程序存储中定义(图 43):
如此一来,安全设置过程就相当简单。
我们在同一 JCS 实例上运行两个不同的环境 — 开发环境和生产环境。这些环境必须彼此隔离。因此,我们有两个独立的 DB 模式,专用于开发和生产,还有两个独立的托管服务器。这样我们就可以简化维护,因为可以分开单独管理开发实例和生产实例(图 44):
对于开发环境,我们使用默认的托管服务器。对于生产环境,我们自己创建了一个新的托管服务器。
可以通过 Enterprise Manager UI 按与内部部署完全相同的方式在 JCS 实例中新建一个托管服务器。确保指定一个唯一的端口:
选择计算机:
新的托管服务器已启动运行。除了管理员和一个托管服务器的默认设置之外,我们还配置了一个托管服务器(图 47):
还需要一些其他步骤才能通过互联网访问新的托管服务器。您必须转到 Oracle 云实例管理控制台,从菜单中选择 Access Rules(图 48):
创建新的访问规则,提供托管服务器端口以及目标名称。一旦应用规则,托管服务器应该立即可通过互联网访问(图 49):
JCS 实例自带方便的监视指标屏幕。您可以通过各种指标了解实例性能(图 50):
JCS 实例配置了警报规则。如果符合其中一项指标,云实例管理员就会收到电子邮件通知(图 51):
只要 JCS 实例在运行,我们就不必担心备份。每天都有增量备份发生,一周进行一次完整备份。备份计划是可配置的,可由管理员更改(图 52):
补丁也会自动应用,虽然有些还需要手动干预。您可以先运行一份报告,检查补丁是否可以顺利应用。只需点击一个按钮即可控制补丁安装过程 — 您无需自行运行任何脚本,因为系统会帮您完成所有繁杂的工作(图 53):
Oracle 云会监视服务品质的下降,一旦发现即采取措施。我们在生产运行时收到过一份报告性能缓慢的电子邮件(图 54):
当性能事件自动解决时,我们很高兴地收到一封确认邮件(图 55):
Oracle ACE 总监 Andrejus Baranovskis 是总部位于立陶宛的 Red Samurai Consulting 的 CEO 兼技术架构师。Red Samurai 荣膺 2017 年 Oracle PaaS 合作伙伴社区的杰出 Java 云服务贡献奖、2015 年 Oracle 融合中间件合作伙伴社区的杰出 ACM/BPM 贡献奖等奖项。Andrejus 还是一位多产的博主,在其博客上分享了大量技术专业知识。
本文已经过相关 Oracle 产品团队审查,符合 Oracle 产品使用标准和实践。