为 OTN 撰稿
为 Oracle 技术网撰写技术文章,获得报酬的同时可提升技术技能。
了解更多信息
密切关注
OTN 架构师社区
OTN ArchBeat 博客 Facebook Twitter YouTube 随身播图标

使用 C++ 和 Web 服务调用 OSB 和 EDN 的实战指南

作者:Sebastian Lik-Keung Ma

演示在 Oracle SOA Suite 11g 中如何使用 OSB 和 EDN 实现极端分离。

2014 年 1 月

下载
download-icon13-1Oracle SOA Suite
download-icon13-1Oracle 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 所示架构显示了具体实现方式:

lik-keung-osb-edn-fig01
图 1:采用与 OSB 和 EDN 极其松散耦合的集成

OSB 在安全性、物理位置业务术语以及数据结构和通信协议方面提供了企业级的分离。应用程序调用代理服务;OSB 则将这些调用路由到所配置的各个业务服务。

不同应用程序彼此独立运行。我们不希望一个应用程序的事务在另一个应用程序出现故障、降级或关闭时发生堵塞或失败。单向调用、异步 Web 服务调用以及 JMS(Java 消息传递服务)和 AQ (Advanced Queuing) 等消息队列都可以提供异步功能。图 1 中的架构使用 EDN,这是一种更高级、更抽象的业务事件发布和订阅方式,不涉及 JMS 的底层连接细节。请注意,这还映射到事件驱动式架构 (EDA)。

该架构的事件生成部分独立于事件使用部分。控制流模型如下所示(参见图 1):

事件生成
  1. 自定义应用程序调用指向 OSB 代理服务的 Web 服务。
  2. OSB 代理服务将调用路由至业务服务。
  3. 业务服务调用 SOA 组合应用程序发布的 Web 服务。
  4. SOA 组合应用程序向 EDN 发布业务事件。

流程到此结束。

事件使用
  1. EDN 收到信号并接收业务事件。它将此事件发送至订阅方 — 一个监听感兴趣的事件的 SOA 组合应用程序。
  2. SOA 组合应用程序调用一个指向 OSB 代理服务的 Web 服务。
  3. OSB 代理服务将调用路由至业务服务。
  4. 业务服务调用另一个自定义应用程序发布的 Web 服务。

流程到此结束。

您很快可以看到,Web 服务、OSB 和 EDN 构成核心 SOA 要素,让这种极其松散耦合的架构成为可能。最终结果是事件生成部分完全独立于事件使用部分。

首先介绍事件生成部分。

事件生成

在本文中,事件生成部分的三个主要参与者包括:

  1. HelloPublisher:JDeveloper SOA 组合应用程序,包含一个发布事件的业务流程执行语言 (BPEL) 组合
  2. OSB:代理服务,业务服务和 Web 服务定义语言 (WSDL)
  3. HttpHelloOsbClient:Windows C++ 控制台应用程序,模拟事件触发应用程序

HelloPublisher

通过 Web 服务调用时,此应用程序将向 EDN 发布 NewEmployee 事件。控制将立即无阻塞返回 Web 服务调用方。

此 SOA 组合应用程序只包含一个具有以下事件定义的 BPEL 组合(参见图 2):

lik-keung-osb-edn-fig02
图 2:NewEmployeeEvent 定义

NewEmployee 事件的负载包含 3 个字符串:din、lastname 和 firstname(din 指员工 ID)。

lik-keung-osb-edn-fig03
图 3:NewEmployee 事件负载

BPEL 组合也只公开一个名为 process 的 Web 服务方法(参见图 4)。调用 process 方法时,此组合发布 NewEmployeeEvent:

lik-keung-osb-edn-fig04
图 4:BPEL 组合

放大 BPEL,可以看到名为 Assign_ReceivedInput 的 Assign 活动和名为 Invoke1 的 Invoke 活动(参见图 5)。

lik-keung-osb-edn-fig05
图 5:BPEL 组合活动

Assign_ReceiveInput 活动仅包含以下简单映射(参见图 6):

  • process 方法的输入变量 din、lastname 和 firstname 分别映射到类似的 NewEmployee 事件负载变量。
  • process 方法的输入变量 din 映射到其输出变量(即该方法只返回输入变量 din 的值)。
lik-keung-osb-edn-fig06
图 6:Assign 活动映射

Invoke 活动发布 NewEmployeeEvent(参见图 7):

lik-keung-osb-edn-fig07
图 7:Invoke 活动

总而言之,HelloPublisher 的 Web 服务方法 process 只发布 NewEmployeeEvent 并立即返回一个字符串值。

OSB — 代理服务、业务服务和 WSDL

要发挥 SOA 的优势,最佳做法是通过中介或代理提供 Web 服务访问。因此这些服务的使用方将不受实际 Web 服务的位置和实现变更的影响。

通常,我们使用 OSB 控制台按以下顺序将 OSB 的配置定义为资源:WSDL、业务服务和代理服务。图 8 给出了结果示例:

lik-keung-osb-edn-fig08
图 8:OSB 配置

WSDL

WSDL 资源包含实际目标应用程序的 Web 服务的位置。例如,HelloPublisher Web 服务的 URL/路径为:

http://<主机名>:<端口>/soa-infra/services/default/HelloPublisher/bpelpublisher_client_ep?WSDL

WSDL 资源还导入包含用于 HelloPublisher 事件定义的类型的 XSD 文件。

lik-keung-osb-edn-fig09
图 9:WSDL

业务服务

业务服务使用前面定义的 WSDL 资源。这将绑定到实际 Web 服务方法的端点 URI。

lik-keung-osb-edn-fig10
图 10:业务服务
lik-keung-osb-edn-fig11
图 11:业务服务使用 WSDL 绑定

代理服务

最后,OSB 代理服务将包含到前面定义的业务服务资源的路由。

lik-keung-osb-edn-fig12
图 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++ 代码片段:

lik-keung-osb-edn-fig13
图 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, 
    0, 
    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, 
       0,
       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 发布业务事件之后,现在我们讨论事件使用。

事件使用

事件使用部分的主要参与者包括:

  1. HelloSubscriber:JDeveloper SOA 组合应用程序,包含一个订阅事件的 BPEL 组合
  2. OSB:代理服务、业务服务和 WSDL
  3. SampleHRApp:Oracle ADF(应用开发框架)Web 应用程序,模拟自定义 HR 应用程序

HelloSubscriber

此 SOA 组合应用程序监听 NewEmployeeEvent 并调用代理 Web 服务(图 15)。

lik-keung-osb-edn-fig15
图 15:订阅事件并调用代理服务的 BPEL 组合

为了订阅 NewEmployeeEventHelloSubscriber 使用了类似 HelloPublisher 中的事件定义(参见图 16 和 17)。

lik-keung-osb-edn-fig16
图 16:NewEmployeeEvent 定义

NewEmployee 事件的负载:

lik-keung-osb-edn-fig17
图 17:NewEmployee 事件负载

可以有多个应用程序订阅 NewEmployeeEvent。在本例中,名为 SampleHRApp 的自定义 Oracle ADF Web 应用程序希望接收 NewEmployeeEvent 通知,这样如果该员工记录不存在,它可以在数据库中自动创建新员工。

事实上,HelloSubscriber 可以利用 ADF,直接使用 SampleHRApp 的 ADF 服务数据对象 (SDO)。但为了与最佳实践保持一致,HelloSubscriber 还是使用 OSB 代理服务。

OSB ProxyServiceBusinessServiceWSDL 的步骤与本文事件生成一节中介绍的 OSB 部分类似。

为简单起见,我们将 SampleHRApp 的代理服务的 WSDL 设定(同样,采用类似于“事件生成”一节中介绍的步骤)为:

http://localhost:7001/SampleHR/ProxyService/SampleHR?WSDL

收到 NewEmployeeEvent 后,BPEL 将调用图 18 中所示的 Web 服务。

lik-keung-osb-edn-fig18
图 18:BPEL 调用 OSB 代理服务

提示:在此将 Transaction Participation 属性保留为 NEVER 非常重要,因为我们不准备参与任何全局事务协作(即目标 Web 服务实现将管理自己的事务)。

HelloSubscriber BPEL 只包含两项活动:Assign 活动和 Invoke 活动(图 19):

lik-keung-osb-edn-fig19
图 19:BPEL 活动

AssignToInvoke 活动只是将来自 NewEmployeeEvent 负载的输入变量(dinlastnamefirstname)传递到被调用的 Web 服务方法 createEmployee 的各相应输出变量(暂时忽略字符串和整型之间的类型不匹配,参见图 20 和 21)。

lik-keung-osb-edn-fig20
图 20:AssignToInvoke 活动映射
lik-keung-osb-edn-fig21
图 21:InvokeCreateEmployee 活动调用 Web 服务方法 createEmployee

总结

本文介绍采用 SOA 和 EDA 原则在不同应用程序之间进行端到端集成的工作示例。重点介绍开发、部署和运行时的松散耦合。图 22 展示了实现概览,具体的已部署模块(可执行文件、jar 文件等)以橙色框突出显示。

lik-keung-osb-edn-fig22
图 22:采用与 OSB 和 EDN 极其松散耦合的集成 — 实现方式

图 23 是最终结果的概览图。自定义的 C++ 程序 HttpHelloOsbClient.exe 调用内部方法 InvokeOsbWebService(…)。这触发发送了一个新的员工事件。自定义的 Web 应用程序 SampleHRApp 负责自动新建员工记录。

lik-keung-osb-edn-fig23
图 23:最终结果

关于作者

Sebastian Lik-Keung Ma 目前是 Sembcorp Marine Limited 的软件应用程序开发经理。他曾在新加坡和德国做过 20 年的软件开发工程师和架构师,专攻 C++、Java 和 C#,提供分布式计算软件应用程序。他拥有英国谢菲尔德大学的理学硕士学位。