Développer vos fonctions

Pour traiter les données, vous aurez besoin de trois unités de code indépendantes : une fonction de transformation, une fonction de chargement et une fonction de rappel.

Ces fonctions ont été implémentées à l'aide d'Oracle Functions et écrites en Python. Oracle Functions est parfaitement adapté à ce travail car le chargement de données peut être quelque chose qui se produit avec une fréquence limitée (par exemple, une ou deux fois par heure ou par jour). L'utilisation d'Oracle Functions est avantageuse car la fonction n'est appelée que lorsqu'il y a quelque chose à faire et qu'une fois le traitement terminé, elle s'arrête. En outre, il n'y a aucun système d'exploitation, routage ou autre serveur à gérer ; il s'agit d'une architecture sans serveur. Pour cet exemple d'implémentation, nous avons choisi Python plutôt que d'autres options de langage car il est facile à comprendre et exendable, et il n'y a pas d'exigence de performances strictes pour ce travail de chargement de données.

Nos trois fonctions sont :

  • Fonction de transformation permettant de traduire le fichier de données d'un format JSON simplifié vers le fichier ZIP spécifique à Oracle Cloud ERP
  • Fonction de chargement permettant de charger le fichier dans Oracle Cloud ERP
  • Fonction de rappel permettant de gérer la réponse d'Oracle Fusion

Chacune de ces fonctions extrait ses données du bucket OCI Storage, les traite, puis les place dans un autre bucket.

Utilisation des buckets OCI Storage

Vous devez d'abord obtenir les données dans OCI Cloud pour pouvoir les traiter efficacement. Oracle OCI propose une option idéale pour les buckets Oracle Cloud Infrastructure Object Storage. Les buckets fournissent une capacité de stockage importante et plusieurs options pour télécharger des fichiers, notamment une CLI, une API REST et la console d'administration.

Les données transformées à charger sont similaires à ce fichier 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" 
                    }] 
}]

Il s'agit d'une méthode beaucoup plus simple que le fichier ZIP d'import FBDI natif au format Oracle Cloud ERP, qui comprend deux fichiers CSV, semblable à ce qui suit :

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

Le reste de cette solution utilisera également des buckets supplémentaires pour stocker le fichier au cours de son traitement. Lorsque le processus se déplace, le fichier passe au bucket suivant. Dès que le fichier est chargé dans Oracle Cloud ERP, il est renommé pour inclure le JOBID de chargement de données ERP.
Description de l'image load-data-serverless-overview.png
Description de l'illustration load-data-serverless-overview.png

  1. Créez des buckets de stockage dans votre instance OCI.
  2. Téléchargez le fichier JSON obtenu vers un bucket de stockage à l'aide de la commande d'interface de ligne de commande suivante :
    oci os object put -ns mynamespace -bn JSONIncoming --name mysimpliedJSONInvoice.json --file mysimpliedJSONInvoice.json 

Créer une fonction de transformation

Lors du chargement de données dans Oracle Fusion, une étape obligatoire consiste à transformer les données d'entrée au format CSV correct, puis à compresser les fichiers dans un fichier ZIP unique. Dans cet exemple, nous illustrons cette étape de transformation en acceptant une structure de données JSON simplifiée, que nous transformons ensuite en format CSV requis par Oracle Cloud ERP. Ensuite, nous plaçons les fichiers dans un fichier ZIP unique prêt à être chargé.

Lorsque vous effectuez cette opération manuellement, vous devez normalement télécharger les fichiers de macro Excel qu'ils peuvent remplir et générer. (Voir Explorer plus pour un lien.) Au lieu de cela, vous pouvez effectuer la transformation à l'aide d'une fonction qui exécute du code Python.



La fonction de transformation extrait les données JSON du bucket JSON entrant, les transforme à l'aide du code en fichier CSV, les zips et les stocke dans un bucket ZIP entrant. La fonction de transformation utilise une approche de modèle pour générer le fichier CSV. L'interaction avec les fichiers OCI est simple : vous pouvez placer et supprimer des objets. Si vous devez copier des fichiers volumineux, vous pouvez l'exécuter de manière asynchrone. Cet exemple utilise de petits fichiers afin que la copie asynchrone ne soit pas utilisée ici.

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

Créer une fonction de chargement

Le chargement de données dans Oracle Cloud ERP est simple. Pour cet exemple, nous utilisons l'API REST importBulkData standard.

Voici un fragment de code montrant comment charger des données à l'aide de l'API REST requests open source :

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)

A propos de la liaison d'objets à l'aide d'événements

Lorsqu'un fichier est téléchargé dans des buckets Oracle Cloud Infrastructure Object Storage, il est possible de configurer le bucket pour émettre un événement lors de l'exécution d'opérations CRUD. Cet événement peut être capturé par le service Oracle Cloud Infrastructure Events.

La création de règles basées sur des événements d'objet est déclarative. Par exemple, vous pouvez créer une règle qui implémente une logique telle que "si un fichier est créé dans le bucket nommé INCOMING_JSON, appelez la fonction sans serveur erp-transform". Vous pouvez configurer un bucket pour Emettre des événements d'objet en tant qu'option dans la section Fonctionnalités de l'onglet Informations sur le bucket.



Voici un exemple de règle basée sur un événement Object Storage émis par le bucket JSON entrant :



Les événements vous permettent d'associer des opérations qui se produisent sur un bucket de stockage (ou d'autres objets) à des appels de fonction pour traitement. La fonctionnalité basée sur les événements vous permet de créer une chaîne d'opérations qui sont déclenchées par des événements de manière véritablement découplée.

Cette image présente le nouveau flux opérationnel avec les événements implémentés :



Chaque bucket a été marqué pour émettre un événement et le service d'événement capture cet événement et appelle la fonction Oracle appropriée. Lors de l'appel de la fonction, le service d'événement transmet une charge utile d'événement qui appelle à son tour la fonction. Dans la charge utile d'événement, il s'agit du type d'événement émis, et dans ce cas également du bucket et du nom de fichier.

Sécuriser les mots de passe à l'aide d'Oracle Vault

La fonction LoadToSaaS doit pouvoir s'authentifier auprès d'Oracle Cloud ERP. L'authentification requiert le nom utilisateur et le mot de passe de l'utilisateur d'intégration. Le service Oracle Cloud Infrastructure Vault fournit un emplacement sécurisé pour stocker les mots de passe cryptés.

Vous pouvez stocker le nom d'utilisateur en tant que variable de configuration Functions, mais sa pratique non sécurisée ne consiste pas à y stocker le mot de passe. Oracle OCI fournit Oracle Cloud Infrastructure Vault comme solution idéale. Dans le code, vous pouvez interroger le coffre et extraire la clé secrète. Une fois récupérée, vous pouvez utiliser cette clé secrète pour exécuter un appel REST authentifié vers Oracle Cloud ERP. L'OCID de cette clé secrète ne change pas si vous la mettez à jour, de sorte que vous pouvez mettre à jour les mots de passe en toute sécurité sans casser la solution.



Dans l'écran Détails du coffre, sous Ressources, sélectionnez Clés secrètes pour créer des clés secrètes.

L'exemple de code Python suivant peut être utilisé pour extraire la clé secrète :

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'))

Créer une fonction Callback

Lorsque des données sont chargées dans Oracle Fusion Applications, un rappel au client est envoyé avec des données de statut. Vous pouvez créer une fonction de rappel pour recevoir ces informations.

Lorsque les données sont chargées dans Oracle Cloud ERP, le service effectue les étapes suivantes (simplifiées) :

  1. Les données sont chargées dans le référentiel Fusion UCM
  2. Un travail ESS transfère le contenu du fichier dans les tables d'intégration ERP
  3. Un travail ESS importe les données dans des tables transactionnelles
  4. Un rapport est généré, indiquant les lignes qui ont été insérées dans le référentiel UCM
  5. Un rappel au client est envoyé avec une charge utile d'état

La fonction finale appelée dans cet exemple de solution est celle qui implémente le côté client ou le côté réception de ce callback d'Oracle Cloud ERP. Le callback est un appel HTTP avec des données XML. Oracle Functions ne sont pas des adresses REST. Par conséquent, pour pouvoir recevoir l'appel HTTP GET d'Oracle Cloud ERP, vous devez front-end la fonction avec API Gateway.

Comme précédemment, il s'agit d'une opération déclarative qui implique la mise en correspondance de l'adresse et de l'URL de ressource avec une fonction. Dans l'assistant de déploiement Créer (ou Modifier), entrez les informations de routage à l'étape Routages. Voici un exemple :



Cette fonction erp-callback est déclenchée lorsqu'Oracle Cloud ERP émet un rappel. La fonction décode la charge utile XML et extrait le JOBID et le statut. Avec JOBID, vous pouvez déterminer le fichier du bucket de traitement concerné par l'événement, puis déplacer le fichier du bucket de traitement vers le bucket Succès ou Erreur. Fait important, dans les rappels ERP, un travail réussi ne signifie pas nécessairement que les données ont été chargées dans Oracle Cloud ERP : il peut s'agir de données en double, d'une organisation commerciale inconnue, etc. Une amélioration que vous pouvez implémenter dans le modèle illustré ici serait que cette fonction de rappel télécharge l'état depuis UCM et l'introduise pour déterminer si toutes les lignes ont été insérées avec succès.

Etendre la solution en vous abonnant à Notifications

Le service Oracle Cloud Infrastructure Notifications vous permet de créer des sujets dans lesquels vous pouvez publier des messages. Ces rubriques peuvent contenir des abonnés qui écoutent un message, puis l'envoient quelque part.

Grâce au flux complet, vous pouvez tirer parti des avantages de l'intégration à OCI. Etant donné que le flux a été conçu en tant que microservices et que vous utilisez des services natifs tels que des événements, vous pouvez tirer parti de fonctionnalités et de services supplémentaires tels qu'Oracle Cloud Infrastructure Notifications. Dans cet exemple de code, les notifications sont des courriels, mais l'abonné peut plutôt être une autre fonction, un canal PagerDuty ou un autre mécanisme. L'architecture faiblement couplée se prête à être étendue de plusieurs façons. Par exemple, vous pouvez ajouter une fonction qui insère des données dans un tableau de bord Grafana et la notification peut appeler cette fonction avec certaines données que vous souhaitez afficher dans Grafana.

Par exemple, voici un fragment de code d'une fonction helper qui montre comment envoyer une notification à un sujet (à l'aide de son 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}')