为 OTN 撰稿
|
为 Oracle 技术网撰写技术文章,获得报酬的同时可提升技术技能。
|
了解更多信息
|
|
|
使用 C++ 和 Web 服务调用 OSB 和 EDN 的实战指南
作者:Sebastian Lik-Keung Ma
演示在 Oracle SOA Suite 11g 中如何使用 OSB 和 EDN 实现极端分离。
2014 年 1 月
下载
Oracle SOA Suite
Oracle JDeveloper
简介
在实际的组织中,针对不同平台用不同的编程语言(如 Java J2EE、C# .NET、C++ 等)编写软件程序。面向服务的架构 (SOA) 允许这些应用程序共存,同时根据业务变化的需要逐步提供新服务。
本文通过一个完整的、端到端的工作示例演示 C++ 应用程序如何在 SOA 基础架构中生成一个事件以及 JDeveloper 应用程序如何使用该事件。
用例示例
虚构的制造公司 SM Corp. 公司使用外部公司 HR Contracts Private Limited 的 HR 外包服务。HR Contracts 每天将新合同工的批处理文件发送给 SM Corp.。但现在 SM Corp. 需要有关这些新合同工的更实时的信息,因为他们经常在被 HR Contracts 招聘的当天就到公司工作。在 SM Corp. 内部,许多其他应用程序还需要为这些新合同工创建记录,这样他们才能开始工作,包括人力资源系统、安全访问控制系统和医疗保险系统。
架构
SM Corp. 已经安装了 Oracle SOA Suite。除了现有的批处理,还应向感兴趣的订阅方发布实时信息。此外,两家公司都希望在开发、部署和事务处理方面尽量不要影响现有系统和流程。下面图 1 所示架构显示了具体实现方式:
图 1:采用与 OSB 和 EDN 极其松散耦合的集成
OSB 在安全性、物理位置业务术语以及数据结构和通信协议方面提供了企业级的分离。应用程序调用代理服务;OSB 则将这些调用路由到所配置的各个业务服务。
不同应用程序彼此独立运行。我们不希望一个应用程序的事务在另一个应用程序出现故障、降级或关闭时发生堵塞或失败。单向调用、异步 Web 服务调用以及 JMS(Java 消息传递服务)和 AQ (Advanced Queuing) 等消息队列都可以提供异步功能。图 1 中的架构使用 EDN,这是一种更高级、更抽象的业务事件发布和订阅方式,不涉及 JMS 的底层连接细节。请注意,这还映射到事件驱动式架构 (EDA)。
该架构的事件生成部分独立于事件使用部分。控制流模型如下所示(参见图 1):
事件生成
-
自定义应用程序调用指向 OSB 代理服务的 Web 服务。
-
OSB 代理服务将调用路由至业务服务。
-
业务服务调用 SOA 组合应用程序发布的 Web 服务。
-
SOA 组合应用程序向 EDN 发布业务事件。
流程到此结束。
事件使用
-
EDN 收到信号并接收业务事件。它将此事件发送至订阅方 — 一个监听感兴趣的事件的 SOA 组合应用程序。
-
SOA 组合应用程序调用一个指向 OSB 代理服务的 Web 服务。
-
OSB 代理服务将调用路由至业务服务。
-
业务服务调用另一个自定义应用程序发布的 Web 服务。
流程到此结束。
您很快可以看到,Web 服务、OSB 和 EDN 构成核心 SOA 要素,让这种极其松散耦合的架构成为可能。最终结果是事件生成部分完全独立于事件使用部分。
首先介绍事件生成部分。
事件生成
在本文中,事件生成部分的三个主要参与者包括:
-
HelloPublisher
:JDeveloper SOA 组合应用程序,包含一个发布事件的业务流程执行语言 (BPEL) 组合
-
OSB
:代理服务,业务服务和 Web 服务定义语言 (WSDL)
-
HttpHelloOsbClient
:Windows C++ 控制台应用程序,模拟事件触发应用程序
HelloPublisher
通过 Web 服务调用时,此应用程序将向 EDN 发布 NewEmployee 事件。控制将立即无阻塞返回 Web 服务调用方。
此 SOA 组合应用程序只包含一个具有以下事件定义的 BPEL 组合(参见图 2):
图 2:
NewEmployeeEvent
定义
NewEmployee 事件的负载包含 3 个字符串:din、lastname 和 firstname(din 指员工 ID)。
图 3:
NewEmployee
事件负载
BPEL 组合也只公开一个名为 process 的 Web 服务方法(参见图 4)。调用 process 方法时,此组合发布 NewEmployeeEvent:
图 4:BPEL 组合
放大 BPEL,可以看到名为
Assign_ReceivedInput
的 Assign 活动和名为
Invoke1
的 Invoke 活动(参见图 5)。
图 5:BPEL 组合活动
Assign_ReceiveInput
活动仅包含以下简单映射(参见图 6):
-
process 方法的输入变量 din、lastname 和 firstname 分别映射到类似的 NewEmployee 事件负载变量。
-
process 方法的输入变量 din 映射到其输出变量(即该方法只返回输入变量 din 的值)。
图 6:Assign 活动映射
Invoke 活动发布
NewEmployeeEvent
(参见图 7):
图 7:Invoke 活动
总而言之,
HelloPublisher
的 Web 服务方法 process 只发布
NewEmployeeEvent
并立即返回一个字符串值。
OSB — 代理服务、业务服务和 WSDL
要发挥 SOA 的优势,最佳做法是通过中介或代理提供 Web 服务访问。因此这些服务的使用方将不受实际 Web 服务的位置和实现变更的影响。
通常,我们使用 OSB 控制台按以下顺序将 OSB 的配置定义为资源:WSDL、业务服务和代理服务。图 8 给出了结果示例:
图 8:OSB 配置
WSDL
WSDL 资源包含实际目标应用程序的 Web 服务的位置。例如,HelloPublisher Web 服务的 URL/路径为:
http://<主机名>:<端口>/soa-infra/services/default/HelloPublisher/bpelpublisher_client_ep?WSDL
WSDL 资源还导入包含用于
HelloPublisher
事件定义的类型的 XSD 文件。
图 9:WSDL
业务服务
业务服务使用前面定义的 WSDL 资源
。这将绑定到实际 Web 服务方法的端点 URI。
图 10:业务服务
图 11:业务服务使用 WSDL 绑定
代理服务
最后,OSB 代理服务将包含到前面定义的业务服务资源的路由。
图 12:代理服务使用业务服务
HttpHelloOsbClient — C++ 调用 Web 服务
HttpHelloOsbClient
是一个 Windows C++ 控制台应用程序,它只知道和调用 OSB 提供的代理服务。它触发将要发布的
NewEmployEvent
。
此 C++ 程序使用轻量级本机代码 Windows Web 服务 API (WWSAPI) 来使用 Web 服务。下面提供了有关如何使用 WWSAPI 的详细信息:
http://msdn.microsoft.com/en-us/library/windows/desktop/dd430435(v=vs.85).aspx
从外部应用程序的角度来看,下面是验证 OSB 代理 Web 服务 URL 路径的步骤:
-
登录到 OSB 控制台:
http://<主机名>:<端口>/sbconsole
-
单击左侧的
Resource Browser
查看已经部署的可用代理及其路径
-
如果单击其中一个代理,将看到您的端点 URI(如端点 URI
/service/test/YourService
)
-
在端点 URI 末尾添加
?WSDL
以访问代理 WSDL(如
http://<主机名>:<端口>/service/test/YourService?WSDL
)
例如,如果 OSB 运行在 localhost 上,端口为 7001,则代理服务的 WSDL 为:
http://localhost:7001/HelloEDN/ProxyService/HelloEDNProxyService?WSDL
图 13 显示调用 OSB 代理服务的 C++ 代码片段:
图 13:
InvokeOsbWebService
(…) 函数
提示:
下面图 14 中的代码注释包含如何让 WWSAPI 发挥作用的提示。
下面图 14 显示内部代码片段(含代码注释)。
#include <string>
#include <iostream>
#include <sstream>
#include <WebServices.h>
#include "WsHelpers.h"
#include "HelloPublisherURL.wsdl.h" // produced by wsutil.exe
#include "XMLSchema_-1547443801.xsd.h"
#include "XMLSchema_583194075.xsd.h"
using namespace std;
HRESULT InvokeOsbWebService(__in wstring url,
__in wstring din,
__in wstring lastname,
__in wstring firstname,
__out wstring& result,
__in_opt WS_ERROR* error)
HRESULT hr = S_OK;
WsHeap heap;
HR(heap.Create(2048, // max size
0, // trim size
0, // properties
0, // property count
error));
// Required settings for SOAP
WS_CHANNEL_PROPERTY channelProperties[2];
WS_ADDRESSING_VERSION addressingVersion = WS_ADDRESSING_VERSION_TRANSPORT;
channelProperties[0].id = WS_CHANNEL_PROPERTY_ADDRESSING_VERSION;
channelProperties[0].value = &addressingVersion;
channelProperties[0].valueSize = sizeof(addressingVersion);
WS_ENVELOPE_VERSION envelopeVersion = WS_ENVELOPE_VERSION_SOAP_1_1;
channelProperties[1].id = WS_CHANNEL_PROPERTY_ENVELOPE_VERSION;
channelProperties[1].value = &envelopeVersion;
channelProperties[1].valueSize = sizeof(envelopeVersion);
WsServiceProxy serviceProxy;
HR(WsCreateServiceProxy(
WS_CHANNEL_TYPE_REQUEST,
WS_HTTP_CHANNEL_BINDING,
NULL,
NULL,
channelProperties,
WsCountOf(channelProperties),
&serviceProxy,
error));
// C++ std::string... c_str() returns a readonly const pointer to a const object
// We will not modify any string contents here, just get the first character and let
// WS_STRING retrieve the rest of the string value based on its length
WS_STRING wsstrURL;
wsstrURL.length = url.length();
wsstrURL.chars = &url[0];
WS_ENDPOINT_ADDRESS address = { wsstrURL };
HR(serviceProxy.Open(&address,
0, // async context
error));
// Initialize web service input and output args
_NewEmployee employee;
employee.din.length = din.length();
employee.din.chars = &din[0];
employee.firstname.length = firstname.length();
employee.firstname.chars = &firstname[0];
employee.lastname.length = lastname.length();
employee.lastname.chars = &lastname[0];
_processResponse* pResponse = NULL; // response will be allocated by callee
// Invoke the web service call
HR(BPELPublisherBinding_process(
serviceProxy,
&employee,
&pResponse,
heap,
NULL,
NULL,
error));
// the response as result
if (0 != pResponse && pResponse->result.length > 0)
result.assign(pResponse->result.chars, 0, pResponse->result.length);
HR(serviceProxy.Close(0, // async context
error));
return S_OK;
图 14:InvokeOsbWebService(…) 使用 WWSAPI
向 EDN 发布业务事件之后,现在我们讨论事件使用。
事件使用
事件使用部分的主要参与者包括:
-
HelloSubscriber
:JDeveloper SOA 组合应用程序,包含一个订阅事件的 BPEL 组合
-
OSB
:代理服务、业务服务和 WSDL
-
SampleHRApp
:Oracle ADF(应用开发框架)Web 应用程序,模拟自定义 HR 应用程序
HelloSubscriber
此 SOA 组合应用程序监听
NewEmployeeEvent
并调用代理 Web 服务(图 15)。
图 15:订阅事件并调用代理服务的 BPEL 组合
为了订阅
NewEmployeeEvent
,
HelloSubscriber
使用了类似
HelloPublisher
中的事件定义(参见图 16 和 17)。
图 16:
NewEmployeeEvent
定义
NewEmployee
事件的负载:
图 17:
NewEmployee
事件负载
可以有多个应用程序订阅
NewEmployeeEvent
。在本例中,名为
SampleHRApp
的自定义 Oracle ADF Web 应用程序希望接收
NewEmployeeEvent
通知,这样如果该员工记录不存在,它可以在数据库中自动创建新员工。
事实上,
HelloSubscriber
可以利用 ADF,直接使用
SampleHRApp
的 ADF 服务数据对象 (SDO)。但为了与最佳实践保持一致,
HelloSubscriber
还是使用 OSB 代理服务。
OSB ProxyService
、
BusinessService
和
WSDL
的步骤与本文
事件生成
一节中介绍的 OSB 部分类似。
为简单起见,我们将
SampleHRApp
的代理服务的 WSDL 设定(同样,采用类似于“事件生成”一节中介绍的步骤)为:
http://localhost:7001/SampleHR/ProxyService/SampleHR?WSDL
收到
NewEmployeeEvent
后,BPEL 将调用图 18 中所示的 Web 服务。
图 18:BPEL 调用 OSB 代理服务
提示:
在此将 Transaction Participation 属性保留为 NEVER 非常重要,因为我们不准备参与任何全局事务协作(即目标 Web 服务实现将管理自己的事务)。
HelloSubscriber
BPEL 只包含两项活动:Assign 活动和 Invoke 活动(图 19):
图 19:BPEL 活动
AssignToInvoke
活动只是将来自
NewEmployeeEvent
负载的输入变量(
din
、
lastname
和
firstname
)传递到被调用的 Web 服务方法
createEmployee
的各相应输出变量(暂时忽略字符串和整型之间的类型不匹配,参见图 20 和 21)。
图 20:
AssignToInvoke
活动映射
图 21:
InvokeCreateEmployee
活动调用 Web 服务方法
createEmployee
总结
本文介绍采用 SOA 和 EDA 原则在不同应用程序之间进行端到端集成的工作示例。重点介绍开发、部署和运行时的松散耦合。图 22 展示了实现概览,具体的已部署模块(可执行文件、jar 文件等)以橙色框突出显示。
图 22:采用与 OSB 和 EDN 极其松散耦合的集成 — 实现方式
图 23 是最终结果的概览图。自定义的 C++ 程序
HttpHelloOsbClient.exe
调用内部方法
InvokeOsbWebService
(…)。这触发发送了一个新的员工事件。自定义的 Web 应用程序
SampleHRApp
负责自动新建员工记录。
图 23:最终结果
关于作者
Sebastian Lik-Keung Ma
目前是
Sembcorp Marine Limited
的软件应用程序开发经理。他曾在新加坡和德国做过 20 年的软件开发工程师和架构师,专攻 C++、Java 和 C#,提供分布式计算软件应用程序。他拥有英国谢菲尔德大学的理学硕士学位。
Integrated Cloud Applications & Platform Services