Funktionen entwickeln
Zur Verarbeitung der Daten benötigen Sie drei unabhängige Codeeinheiten: eine Transformationsfunktion, eine Ladefunktion und eine Callback-Funktion.
Diese Funktionen wurden mit Oracle Functions implementiert und in Python geschrieben. Oracle Functions eignet sich perfekt für diesen Job, da das Laden von Daten etwas sein kann, das mit begrenzter Häufigkeit geschieht (z.B. ein- oder zweimal pro Stunde oder pro Tag). Die Verwendung von Oracle Functions ist vorteilhaft, weil die Funktion nur aufgerufen wird, wenn etwas zu tun ist und wenn die Verarbeitung abgeschlossen ist, wird sie heruntergefahren. Darüber hinaus gibt es kein Betriebssystem, kein Routing oder einen anderen Server, den Sie verwalten können. Dies ist eine serverlose Architektur. Für diese Beispielimplementierung haben wir Python über andere Sprachoptionen gewählt, weil sie leicht verständlich und erweiterbar ist und für diesen Datenladejob keine strenge Performanceanforderung vorliegt.
Unsere drei Funktionen sind:
- Eine Transformationsfunktion zum Übersetzen der Datendatei aus einem vereinfachten JSON-Format in die spezifische ZIP-Datei von Oracle Cloud ERP
- Eine Ladefunktion zum Laden der Datei in Oracle Cloud ERP
- Callback-Funktion für die Verarbeitung der Antwort von Oracle Fusion
Jede dieser Funktionen bezieht ihre Daten aus dem OCI-Speicher-Bucket, verarbeitet sie und fügt sie dann in einen anderen Bucket ein.
OCI-Speicher-Buckets verwenden
Die zu ladenden transformierten Daten ähneln dieser JSON-Datei:
"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"
}]
}]
Dies ist viel einfacher als die native FBDI-Import-ZIP im Oracle Cloud ERP-Format, die zwei CSV-Dateien enthält, ähnlich wie diese CSV-Auszüge:
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
Der Rest dieser Lösung verwendet auch zusätzliche Buckets, um die Datei während der Verarbeitung zu speichern. Wenn der Prozess fortgesetzt wird, wird die Datei in den nächsten Bucket verschoben. Sobald die Datei in Oracle Cloud ERP geladen wird, wird die Datei umbenannt, sodass sie die JOBID zum Laden von ERP-Daten enthält.
Beschreibung der Abbildung load-data-serverless-overview.png
Transformationsfunktion erstellen
Wenn Sie dies manuell ausführen, müssen Sie normalerweise Excel-Makrodateien herunterladen, die sie auffüllen und die ZIP-Dateien generieren können. (Siehe "Mehr entdecken" für einen Link.) Stattdessen können Sie die Transformation mit einer Funktion ausführen, die einen Python-Code ausführt.
Die Transformationsfunktion ruft die JSON-Daten aus dem eingehenden JSON-Bucket ab, transformiert sie mit Code in eine CSV-Datei, zerlegt sie nach oben und speichert sie dann in einem eingehenden ZIP-Bucket. Die Transformationsfunktion verwendet einen Vorlagenansatz zur Generierung der CSV-Datei. Die Interaktion mit OCI-Dateien ist einfach: Sie können Objekte speichern und löschen. Wenn Sie große Dateien kopieren müssen, können Sie diese asynchron ausführen. In diesem Beispiel werden kleine Dateien verwendet, damit hier kein asynchrones Kopieren verwendet wird.
put_object_response = object_storage_client.put_object(namespace, param_processing_bucket_name, data_file_name + "_ERPJOBID_" + erp_job_id, data_file.data.content)
Ladefunktion erstellen
Das Laden von Daten in Oracle Cloud ERP ist einfach. In diesem Beispiel wird die Standard-REST-API importBulkData
verwendet.
Das folgende Code-Snippet zeigt, wie Sie Daten mit der Open Source-REST-API requests
laden können:
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)
Informationen zum Verknüpfen von Objekten mit Ereignissen
Wenn eine Datei in Oracle Cloud Infrastructure Object Storage-Buckets hochgeladen wird, kann der Bucket so konfiguriert werden, dass ein Ereignis ausgegeben wird, wenn CRUD-Vorgänge ausgeführt werden. Dieses Ereignis kann vom Oracle Cloud Infrastructure Events-Service erfasst werden.
Die Erstellung von Regeln basierend auf Objektereignissen ist deklarativ. Beispiel: Sie können eine Regel erstellen, die Logik implementiert, wie "wenn eine neue Datei im Bucket INCOMING_JSON
erstellt wird, dann rufen Sie die serverlose Funktion erp-transform
auf". Sie können einen Bucket so konfigurieren, dass Objektereignisse ausgeben als Option im Abschnitt "Features" der Registerkarte Bucket-Informationen angegeben wird.
Im Folgenden finden Sie ein Beispiel für eine Regel, die auf einem Object Storage-Ereignis basiert, das vom eingehenden JSON-Bucket ausgegeben wird:
Mit Ereignissen können Sie Vorgänge, die mit einem Speicher-Bucket (oder anderen Objekten) ausgeführt werden, mit Funktionsaufrufen zur Verarbeitung verknüpfen. Mit ereignisbasierter Funktionalität können Sie eine Reihe von Vorgängen erstellen, die von Ereignissen in einer wirklich entkoppelten Weise ausgelöst werden.
Diese Abbildung zeigt den neuen Betriebsablauf mit implementierten Ereignissen:
Jeder Bucket wurde für die Ausgabe eines Ereignisses markiert. Der Ereignisservice erfasst dieses Ereignis und ruft die entsprechende Oracle-Funktion auf. Beim Aufrufen der Funktion übergibt der Ereignisservice eine Ereignis-Payload, die wiederum die Funktion aufruft. Innerhalb der Ereignis-Payload ist der Typ des ausgegebenen Ereignisses, in diesem Fall auch der Bucket und Dateiname.
Passwörter mit Oracle Vault sichern
Die LoadToSaaS
-Funktion muss sich mit Oracle Cloud ERP authentifizieren können. Für die Authentifizierung sind Benutzername und Kennwort für den Integrationsbenutzer erforderlich. Der Oracle Cloud Infrastructure Vault-Service bietet einen sicheren Ort zum Speichern verschlüsselter Kennwörter.
Sie können den Benutzernamen als Functions-Konfigurationsvariable speichern, aber es ist nicht sicher, das Kennwort dort zu speichern. Oracle OCI bietet Oracle Cloud Infrastructure Vault als ideale Lösung. Innerhalb des Codes können Sie den Vault abfragen und das Secret extrahieren. Nach dem Abruf können Sie dieses Secret verwenden, um einen authentifizierten REST-Aufruf an Oracle Cloud ERP auszuführen. Beachten Sie, dass sich die OCID dieses Secret Keys beim Aktualisieren nicht ändert. Sie können Passwörter sicher aktualisieren, ohne die Lösung zu beschädigen.
Wählen Sie im Fenster "Vault-Details" unter Ressourcen die Option Secrets aus, um neue Secrets zu erstellen.
Mit dem folgenden Python-Beispielcode kann das Secret extrahiert werden:
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'))
Callback-Funktion erstellen
Wenn Daten in Oracle Fusion Applications geladen werden, wird ein Callback an den Client mit einer Status-Payload gesendet. Sie können eine Callback-Funktion erstellen, um diese Informationen zu erhalten.
Wenn Daten in Oracle Cloud ERP geladen werden, führt der Service die folgenden (vereinfachten) Schritte aus:
- Daten werden in das Fusion UCM-Repository geladen
- Ein ESS-Job überträgt den Dateiinhalt in ERP-Integrationstabellen
- Ein ESS-Job importiert Daten in Transaktionstabellen
- Ein Bericht wird generiert, der zeigt, welche Zeilen in das UCM-Repository eingefügt wurden
- Ein Callback an den Client wird mit einer Status-Payload gesendet
Die letzte Funktion, die in dieser Beispiellösung aufgerufen wird, ist diejenige, die die Clientseite oder die Empfangsseite dieses Callbacks von Oracle Cloud ERP implementiert. Der Callback ist ein HTTP-Aufruf mit XML-Daten. Oracle Functions sind keine REST-Endpunkte. Um den GET-HTTP-Aufruf von Oracle Cloud ERP empfangen zu können, müssen Sie die Funktion mit dem API-Gateway als Frontend beenden.
Wie bisher ist dies ein deklarativer Vorgang, bei dem der Endpunkt und die Ressourcen-URL einer Funktion zugeordnet werden. Geben Sie im Deployment-Assistenten "Erstellen" (oder "Bearbeiten") Routeninformationen im Schritt "Routen" ein. Diese Abbildung zeigt ein Beispiel:
Diese erp-callback
-Funktion wird ausgelöst, wenn Oracle Cloud ERP einen Rückruf ausgibt. Die Funktion decodiert die XML-Payload und extrahiert JOBID und Status. Mit der JOBID können Sie bestimmen, für welche Datei im Verarbeitungs-Bucket das Ereignis ausgeführt wurde, und die Datei dann aus dem Verarbeitungs-Bucket in den Bucket "Erfolgreich" oder "Fehler" verschieben. Wichtig ist, dass bei ERP-Callbacks ein erfolgreicher Job nicht unbedingt bedeutet, dass die Daten in Oracle Cloud ERP geladen wurden: Möglicherweise sind doppelte Daten, eine unbekannte Geschäftsorganisation usw. vorhanden. Eine Verbesserung, die Sie für das hier dargestellte Muster implementieren könnten, wäre, wenn diese Callback-Funktion den Bericht von UCM herunterladen und prüfen würde, ob alle Zeilen erfolgreich eingefügt wurden.
Lösung durch Benachrichtigungen erweitern
Mit dem Oracle Cloud Infrastructure Notifications-Service können Sie Themen erstellen, in denen Sie Nachrichten posten können. Diese Themen können Abonnenten haben, die auf eine Nachricht hören und sie dann irgendwo versenden.
Mit dem Ablauf können Sie die Vorteile der Integration mit OCI nutzen. Da der Ablauf als Microservices entwickelt wurde und Sie native Services wie Ereignisse verwenden, können Sie zusätzliche Features und Services wie Oracle Cloud Infrastructure Notifications nutzen. In diesem Beispielcode sind die Benachrichtigungen E-Mails, der Abonnent kann jedoch stattdessen eine andere Funktion, einen PagerDuty-Kanal oder einen anderen Mechanismus sein. Die lose gekoppelte Architektur ermöglicht es, auf vielfältige Weise erweitert zu werden. Beispiel: Sie können eine Funktion hinzufügen, die Daten in ein Grafana-Dashboard einfügt, und die Benachrichtigung könnte diese Funktion mit einigen Daten aufrufen, die Sie in Grafana anzeigen möchten.
Beispiel: Im Folgenden finden Sie ein Code-Snippet einer Helper-Funktion, das zeigt, wie eine Benachrichtigung an ein Thema gesendet wird (mit der 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}')