开发函数

要处理数据,您需要三个独立的代码单元:转换函数、加载函数和回调函数。

这些函数是使用 Oracle Functions 实现的,并以 Python 编写的。Oracle Functions 非常适合此作业,因为数据加载可能是频率有限(可能每小时或每天一次或两次)发生的事情。使用 Oracle Functions 是非常有利的,因为只有在需要执行某些操作并且完成处理后,该函数才会被调用,然后会关闭。此外,没有可维护的操作系统、路由或其他服务器;这是无服务器体系结构。对于此示例实现,我们选择了 Python 而非其他语言选项,因为它易于理解和可扩展,并且对此数据加载作业没有严格的性能要求。

我们的三个函数是:

  • 用于将数据文件从简化 JSON 格式转换为 Oracle Cloud ERP 特定 Zip 文件的转换函数
  • 用于将文件加载到 Oracle Cloud ERP 中的加载功能
  • 用于处理 Oracle Fusion 响应的回调函数

其中每个函数都将从 OCI 存储桶中获取数据,然后处理数据,然后将其放入另一个存储桶中。

使用 OCI 存储桶

您首先需要将数据迁移到 OCI 云,以便高效处理数据。Oracle OCI 提供了称为 Oracle Cloud Infrastructure Object Storage 存储桶的理想选项。存储桶提供丰富的存储容量和多个用于上载文件的选项,包括 CLI、REST API 和管理控制台。

要加载的转换数据与此 JSON 文件类似:

"invoices": [ 
{ 
    "invoiceId": "222290", 
    "businessUnit": "US1 Business Unit", 
    "source": "External", 
    "invoiceNumber": "111190", 
    "invoiceAmount": "4242.00", 
    "invoiceDate" : "2019/02/01", 
    "supplierName": "Staffing Services", 
    "supplierNumber" : 1253, 
    "supplierSite" : "Staffing US1", 
    "invoiceCurrency": "USD", 
    "paymentCurrency": "USD", 
    "description" : "New Invoice from global Angels", 
    "importSet": "AP_Cloud_Demo", 
    "invoiceType": "STANDARD", 
    "paymentTerms": "Immediate", 
    "termsDate": "2019/02/01", 
    "accountingDate": "2019/02/01", 
    "paymentMethod": "CHECK", 
    "invoiceLines": [ 
                    { 
                        "amount": "200", 
                         "description" : "Invoice Line Description" 
                    }, 
                    { 
                        "amount": "300", 
                        "description" : "Invoice Line Description2", 
                        "invoiceQuantity": "10", 
                        "unitPrice": "5" 
                    }] 
}]

这比原生 Oracle Cloud ERP 格式 FBDI 导入 zip(包含两个 CSV 文件)简单得多,类似于以下 CSV 摘录:

222284,US1 Business Unit,External,111184,4242.00,2019/02/01,Staffing
              Services,1253,Staffing US1,USD,USD,New Invoice from global
              Angels,AP_Cloud_Demo,STANDARD,,,,,,Immediate,2019/02/01,,,2019/02/01,CHECK,Standard,#NULL,,,,,,,,,,,#NULL,,,,,#NULL,#NULL,,,,21,#NULL,,,#NULL,#NULL,,,,#NULL,,,,,,,,,,,,,,,N,N,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,END 
222284,1,ITEM,200,,,,Invoice Line
              Description,,,,,,,,,,,,,N,,#NULL,2019/02/01,,,,,#NULL,,,,,,,,,,,,,,,,,#NULL,,,N,1,,,N,,,,,,,N,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,END

此解决方案的其余部分还使用其他存储桶来存储正在处理的文件。当过程移动时,文件会沿着下一个存储桶移动。一旦将文件加载到 Oracle Cloud ERP 中,该文件将被重命名为包含 ERP 数据加载 JOBID。
后面是 load-data-serverless-overview.png 的说明
插图 load-data-serverless-overview.png 的说明

  1. 在 OCI 实例中创建存储桶。
  2. 使用以下 CLI 命令将生成的 JSON 文件上载到存储桶:
    oci os object put -ns mynamespace -bn JSONIncoming --name mysimpliedJSONInvoice.json --file mysimpliedJSONInvoice.json 

创建转换函数

将数据加载到 Oracle Fusion 时,必须步骤是将输入数据转换为正确的 CSV 格式,然后将文件压缩到单个 ZIP 文件中。在本示例中,我们通过接受简化 JSON 数据结构来演示此转换步骤,然后将该结构转换为 Oracle Cloud ERP 所需的 CSV 格式。然后,将这些文件包装到一个准备上载的 ZIP 文件中。

手动执行此操作时,通常需要下载 Excel 宏文件,这些文件可以填充并生成 zip 文件。(有关链接,请参见“浏览更多”。)相反,您可以使用执行某些 Python 代码的函数执行转换。



转换函数从传入 JSON 存储桶获取 JSON 数据,使用代码将其转换为 CSV,将其向上压缩,然后将其存储在传入 ZIP 存储桶中。转换函数使用模板方法生成 CSV 文件。与 OCI 文件的交互非常简单:您可以放置和删除对象,如果您需要复制大型文件,则可以异步运行。此示例使用小文件,因此此处不使用异步复制。

put_object_response = object_storage_client.put_object(namespace, param_processing_bucket_name, data_file_name + "_ERPJOBID_" + erp_job_id, data_file.data.content)

创建加载函数

将数据加载到 Oracle Cloud ERP 中非常简单。对于此示例,我们使用标准 importBulkData REST API。

下面是一段代码,其中显示了如何使用开源 requests REST API 加载数据:

erp_payload = {
        "OperationName": "importBulkData",
        "DocumentContent": base64_encoded_file,
        "ContentType": "zip",
        "FileName": data_file_name,
        "JobName": jobname,
        "ParameterList": paramlist,
        "CallbackURL": param_fa_callback_url,
        "NotificationCode": "10"
    }
    logging.info(f'Sending file to erp with payload {erp_payload}')
    result = requests.post(
        url=param_erp_url,
        auth=param_erp_auth,
        headers={"Content-Type": JSON_CONTENT_TYPE},
        json=erp_payload
    )
 
    if result.status_code != 201:
        message = "Error " + str(result.status_code) + " occurred during upload. Message=" + str(result.content)
        raise FA_REST_Exception("Error " + message)

关于使用事件将对象链接在一起

将文件上载到 Oracle Cloud Infrastructure Object Storage 存储桶时,可以在执行 CRUD 操作时将存储桶配置为发出事件。此事件可由 Oracle Cloud Infrastructure Events 服务捕获。

基于对象事件的规则创建是声明性的。例如,您可以创建一个实施逻辑的规则,例如“如果在名为 INCOMING_JSON 的存储桶中创建了新文件,则调用 erp-transform 无服务器函数。”您可以将存储桶配置为在存储桶信息选项卡的“功能”部分中发出对象事件



下面是一个基于入站 JSON 存储桶发出的对象存储事件的规则示例:



通过事件,您可以将存储桶(或其他对象)发生的操作与函数调用关联起来以进行处理。基于事件的功能允许您创建一系列操作,这些操作由事件以真正分离的方式触发。

此图显示实施了事件的新操作流:



每个存储桶都已标记为发出事件,事件服务将捕获此事件并调用相应的 Oracle 函数。调用函数时,事件服务会传递事件有效负载,而后者又会调用函数。事件有效负载中是发出的事件类型,在本例中也是存储桶和文件名。

使用 Oracle Vault 保护密码

LoadToSaaS 函数需要能够通过 Oracle Cloud ERP 进行验证。验证需要集成用户的用户名和密码。Oracle Cloud Infrastructure Vault 服务提供了一个存储加密密码的安全位置。

可以将用户名存储为函数配置变量,但是将口令存储在其中不安全的做法。Oracle OCI 提供 Oracle Cloud Infrastructure Vault 作为理想解决方案。在代码内,您可以查询 Vault 并提取密钥。检索到该密钥后,您可以使用此密钥对 Oracle Cloud ERP 执行经过验证的 REST 调用。请注意,如果您更新密钥,此密钥的 OCID 不会更改,因此您可以安全地更新密码,而不会破坏解决方案。



从 "Vault Details" 屏幕的 Resources 下,选择 Secrets 创建新密钥。

以下示例 Python 代码可用于提取密钥:

signer = oci.auth.signers.get_resource_principals_signer()
secret_client = oci.secrets.SecretsClient(config={}, signer=signer)
secret_response = secret_client.get_secret_bundle("ocid1.vaultsecret.oc1.phx.xxxxxxxx")
base64_secret_bytes = secret_response.data.secret_bundle_content.content.encode('ascii')
base64_message_bytes = base64.b64decode(base64_secret_bytes)
print("secret value is " + base64_message_bytes.decode('ascii'))

创建回调函数

将数据加载到 Oracle Fusion Applications 中时,会向客户端发送状态有效负载的回调。您可以创建一个回调函数来接收此信息。

将数据加载到 Oracle Cloud ERP 中时,该服务将执行以下(简化)步骤:

  1. 数据加载到 Fusion UCM 资料档案库中
  2. ESS 任务将文件内容传输到 ERP 集成表中
  3. ESS 作业将数据导入事务处理表中
  4. 将生成一个报告,显示哪些行是在 UCM 资料档案库中的位置插入的
  5. 将使用状态有效负载向客户机发送回调

此示例解决方案中调用的最终功能是实施客户端的函数,或者从 Oracle Cloud ERP 接收此回调的接收方。回调是带有 XML 数据的 HTTP 调用。Oracle Functions 不是 REST 端点,因此,要能够从 Oracle Cloud ERP 接收 GET HTTP 调用,您需要将函数与 API 网关置于前端。

与以前一样,这是一个声明性操作,涉及将端点和资源 URL 映射到函数。在创建(或编辑)部署向导的“路由”步骤中输入路由信息。此图像显示一个示例:



Oracle Cloud ERP 发出回调时,将触发此 erp-callback 函数。该函数解码 XML 有效负载并提取出 JOBID 和状态。使用 JOBID,您可以确定事件对应的“处理”存储桶中的哪个文件,然后将该文件从“处理”存储桶移动到“成功”或“错误”存储桶。重要的是,在 ERP 回调中,成功的作业并不一定意味着数据已加载到 Oracle Cloud ERP 中:它可能是重复数据、未知业务组织等。可对此处演示的模式实施的一个增强功能是,此回调函数从 UCM 下载报告并自测它来确定是否已成功插入所有行。

通过订阅通知来扩展解决方案

Oracle Cloud Infrastructure Notifications 服务允许您创建主题,可以在其中发布消息。这些主题可以让订阅者监听消息,然后将其发送出去。

通过流程完成,您可以利用与 OCI 集成的优势。由于该流程设计为微服务,并且您使用的是事件等原生服务,因此您可以利用 Oracle Cloud Infrastructure Notifications 等其他功能和服务。在此示例代码中,通知是电子邮件,但订户可以是另一个函数、PagerDuty 渠道或某种其他机制。松散耦合的体系结构会以多种方式扩展自身。例如,您可以添加将数据插入 Grafana 仪表盘的函数,并且通知可以使用要在 Grafana 中显示的一些数据调用此函数。

例如,下面是帮助函数的代码片段,该片段演示了如何向主题发送通知(使用其 OCID):

def publish_ons_notification(topic_id, msg_title, msg_body):
    try:
        signer = oci.auth.signers.get_resource_principals_signer()
        logging.info("Publish notification, topic id" + topic_id)
        client = oci.ons.NotificationDataPlaneClient({}, signer=signer)
        msg = oci.ons.models.MessageDetails(title=msg_title, body=msg_body)
        client.publish_message(topic_id, msg)
    except oci.exceptions.ServiceError as serr:
        logging.critical(f'Exception sending notification {0} to OCI, is the OCID of the notification correct? {serr}')
    except Exception as err:
        logging.critical(f'Unknown exception occurred when sending notification, please see log {err}')