開發您的函數
若要處理資料,您需要三個獨立的程式碼單位:轉換函數、載入函數以及回呼函數。
這些函數已經使用 Oracle Functions 實作並以 Python 撰寫。Oracle Functions 非常適合此工作,因為資料載入可能是頻率有限 (可能每小時或每天執行一次或兩次) 的情況。使用 Oracle Functions 有利之處,因為只有在有需要執行且完成處理後才呼叫函數,則會關閉。此外,您還沒有可維護的作業系統、路由或其他伺服器;這是無伺服器架構。對於此範例的實作,我們選擇了 Python 而非其他語言選項,因為它是容易理解且可擴充的,而且對於此資料載入工作來說,沒有嚴格的效能需求。
我們的三項職能包括:
- 將資料檔從簡化的 JSON 格式轉譯為 Oracle Cloud ERP 特定 Zip 檔案的轉換函數
- 可將檔案載入 Oracle Cloud ERP 的載入功能
- 處理來自 Oracle Fusion 回應的回呼函數
這些功能會從 OCI 儲存的儲存桶中取得其資料,然後處理資料,然後放入另一個儲存桶。
使用 OCI 儲存的儲存桶
要載入的轉換資料與此 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 匯入壓縮檔 (由兩個 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 圖解描述
建立轉換函數
手動執行此動作時,通常需要下載 Excel 巨集檔案,這些檔案可以植入並產生壓縮檔。(請參閱「瀏覽更多」以取得連結。)您可以改用執行某些 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 函數。呼叫函數時,事件服務會傳遞事件有效負載,進而呼叫函數。在事件有效負載內是發出的事件類型,在此情況下,Bucket 和檔案名稱也是。
使用 Oracle Vault 保護密碼
LoadToSaaS 函數需要能夠使用 Oracle Cloud ERP 進行認證。驗證需要整合使用者的使用者名稱與密碼。Oracle Cloud Infrastructure Vault 服務提供一個安全的方式來儲存加密的密碼。
您可以將使用者名稱儲存為 Functions 組態變數,但不能安全地儲存該變數的密碼。Oracle OCI 提供 Oracle Cloud Infrastructure Vault 作為理想的解決方案。在程式碼內,您可以查詢 Vault 並擷取加密密碼。擷取之後,您可以使用此加密密碼對 Oracle Cloud ERP 執行認證的 REST 呼叫。請注意,如果您更新此秘密金鑰的 OCID,它就不會發生變更,因此可以在不破壞解決方案的情況下安全地更新密碼。
從「保存庫詳細資訊」畫面的資源底下,選取加密密碼以建立新的加密密碼。
下列範例 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 時,服務會執行下列 (簡化) 步驟:
- 資料已載入至 Fusion UCM 儲存庫
- ESS 工作會將檔案內容移轉至 ERP 整合表格
- ESS 工作會將資料匯入交易表格
- 會產生報表,顯示已在 UCM 儲存庫中插入哪些資料列
- 會傳送含狀態有效負載的從屬端回呼
在這個範例解決方案中呼叫的最終函數是實行從屬端的函數,或是從 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}')





