1

Situation:

I'm creating a Magento 2 module that listens to specific event observers, gets the ID's associated with those events, and sends them to a third-party Node app. This Node app will receive those ID's, and then use the Magento Rest API to get the data related to those ID's. For instance, I'll listen to the sales_order_save_after event, POST the order ID to the third-party Node app, and then make an authenticated call to the Magento Rest API to GET that order data. The order data is then transformed and stored for use elsewhere.

Problem:

The observers I'm listening to are:

  • customer_address_save_after
  • admin_sales_order_address_update
  • customer_save_after_data_object
  • sales_order_save_after

The first 3 observers work perfectly, and the 4th (sales_order_save_after) fires, but it hangs on waiting for the third-party Node app to finish. The Node app takes a while though because, despite having the correct order ID, when it uses the Magento Rest API to fetch the order, it returns an error: The entity that was requested doesn't exist. Verify the entity and try again. While the module is waiting for this Node app call to resolve, it shows the spinning loader on the checkout page. My thinking is that while the order is indeed created (otherwise, how would we have the correct order ID?) in the database, it's not accessible via the Rest API yet. If I replay the payload once I'm on the order confirmation page, the Rest API returns the correct order as expected.

So it's able to get the ID from the observer, it's able to send that ID to the Node app, but it hangs while the Rest API tries multiple times to get that order and fails to do so. Ultimately the Node app gives up and resolves the request, rendering the order confirmation page, at which point the event can be retried, and the Node app will successfully get the order from the Rest API.

What I've tried to fix this:

  • I've tried having the Node app sleep for 5-10s before making the Rest API call, but it returns the same error.
  • I've tried using another observer, checkout_submit_all_after, which works perfectly. But this observer doesn't respond to changes in the order after it's created (invoices, shipments, status changes, credit memos, etc).
  • I've tried using exponential backoff on the Rest API call and this returns the same error every time, while Magento shows the loading spinner on the checkout page. Once exponential backoff ultimately fails for the last time, it resolves the network call back to the Magento module and shows the order confirmation page.
  • I've tried switching to using sockets so that I can "fire and forget". This would allow the order to resolve immediately, but implementing sockets on my Node app would be a lot of work and I feel like there's an easier solution within Magento that I'm not seeing yet.

Observer code:

events.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
    <event name="customer_address_save_after">
        <observer name="myorg_customer_address_save_after" instance="MyOrg\MyModule\Observer\Address" />
    </event>
    <event name="admin_sales_order_address_update">
        <observer name="myorg_admin_sales_order_address_update" instance="MyOrg\MyModule\Observer\OrderAddress" />
    </event>
    <event name="customer_save_after_data_object">
        <observer name="myorg_customer_save_after_data_object" instance="MyOrg\MyModule\Observer\Customer" />
    </event>
    <event name="sales_order_save_after">
        <observer name="myorg_sales_order_save_after" instance="MyOrg\MyModule\Observer\Order" />
    </event>
</config>

Order.php

<?php

namespace MyOrg\MyModule\Observer;

use Magento\Framework\Event\Observer;
use Magento\Framework\Event\ObserverInterface;
use MyOrg\MyModule\Helper\Data;

class Order implements ObserverInterface
{
  /**
   * Constructor
   * @param Data $helper
   */
  public function __construct(Data $helper)
  {
    $this->helper = $helper;
  }

  public function execute(Observer $observer)
  {
    // Get the data
    $order = $observer->getOrder();

    // Create the payload data
    $data = [
      'orders' => [$order->getId()],
    ];

    // Create the payload event
    $event = [
      'name' => $observer->getEvent()->getName(),
      'type' => 'order',
    ];

    // Create the payload
    $payload = [
      'data' => $data,
      'event' => $event,
    ];

    // Send the data to the Node app
    $this->helper->send($payload);
  }
}

I don't want to post the send() method in the Data.php helper file because it's proprietary. But just know it basically sends a cURL request - nothing fancy - and it's been fully tested with other observers and works perfectly. This isn't the problem.

Questions:

I see two ways this can be solved by answering either:

  1. Why is the sales_order_save_after observer blocked by network requests when other observers don't have this behavior? Is there a way to "save" the order, allowing it to continue to the confirmation page before firing the request to the Node app?
  2. Why is the REST API for Magento unable to find an order that it has the ID of? Why is it able to find addresses and customers using other observers that are virtually identical, but orders seem to not be available yet?

If I'm getting the correct order ID in the observer, then that order must be stored in the DB. But if it's stored in the DB, why can't the Rest API get it?


Any help would be greatly appreciated - thanks!

1 Answer 1

1

I ended up switching out the sales_order_save_after observer for sales_order_save_commit_after and it worked perfectly. This is apparently because all *_save_after observers allow for modification of the entity in question before MySQL commits it to the database, where as *_save_commit_after observers wait until after the MySQL transaction has been committed. Since I don't want to modify the order, and instead I want to wait until after it's been saved completely in the DB, this observer is perfect for my use-case.

1

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.