Ensuring Customer Address Data Integrity: A Critical ESHOPMAN Workflow Fix
Ensuring Customer Address Data Integrity: A Critical ESHOPMAN Workflow Fix
At Move My Store, we understand that maintaining impeccable data integrity is paramount for any successful e-commerce operation, especially when leveraging a powerful headless commerce platform like ESHOPMAN. ESHOPMAN, with its robust Node.js/TypeScript backend and seamless integration with HubSpot for storefront management, relies on precise data handling. Recently, our community identified a critical issue affecting customer address data integrity during specific workflow rollbacks, which could lead to addresses being permanently lost despite a reported system failure.
Understanding the ESHOPMAN Customer Address Rollback Challenge
The core of the problem lies within ESHOPMAN's internal deleteCustomerAddressesWorkflow. This workflow is designed to manage the deletion of customer addresses, and crucially, it includes a compensation mechanism. In a perfect scenario, if any subsequent step in the workflow fails after an address has been deleted, the compensation step should gracefully restore the deleted address, ensuring no data is lost. However, a specific scenario revealed that this compensation was not functioning as expected.
When the deleteCustomerAddressesWorkflow encountered a failure, its compensation step, intended to recreate the deleted addresses using service.createCustomerAddresses, would unfortunately fail itself. The root cause was identified in how the previously deleted address data was being prepared for restoration. The snapshot of the address, captured before deletion, included a nested customer object alongside the customer_id. When this entire object was passed back to createCustomerAddresses, the underlying data persistence layer (MikroORM in ESHOPMAN's Node.js environment) attempted to insert a new customer entity with an ID that already existed in the database. This resulted in a database constraint violation (specifically, a primary key constraint error on the customer table), manifesting as an error message: "Customer with id: ..., already exists."
The consequence? The compensation step would throw an error, preventing the address from being restored. This meant that despite the workflow reporting a failure and attempting a rollback, the customer address remained permanently deleted, leading to inconsistent customer data within the ESHOPMAN system and potentially impacting the storefront experience deployed via HubSpot CMS.
Replicating the Issue
This issue could be triggered by various failures occurring after the initial address deletion, such as a custom hook handler (e.g., addressesDeleted) throwing an error, or a subsequent step in a larger workflow failing. An integration test demonstrated the problem:
import { deleteCustomerAddressesWorkflow } from "@eshopman/core-flows"
import { Modules } from "@eshopman/framework/utils"
deleteCustomerAddressesWorkflow.hooks.addressesDeleted(async () => {
throw new Error("fail after delete")
})
const customers = container.resolve(Modules.CUSTOMER)
const customer = await customers.createCustomers({ email: "[email protected]" })
const [address] = await customers.createCustomerAddresses([
{ customer_id: customer.id, address_1: "1 Test Street", city: "Test City", country_code: "us" },
])
const { errors } = await deleteCustomerAddressesWorkflow(container).run({
input: { ids: [address.id] },
throwOnError: false,
})
// Expects errors, but also expects address to be restored.
// The last assertion 'expect(restored).toHaveLength(1)' would fail.
The ESHOPMAN Community Solution
The ESHOPMAN community quickly converged on an effective solution to ensure the integrity of customer address data during rollbacks. The fix involves carefully preparing the address data before passing it to the createCustomerAddresses service during compensation. Instead of passing the entire snapshot, which includes the problematic nested customer object, we need to extract only the relevant address fields, excluding the customer relation object.
A minimal and effective fix involves mapping the previous addresses to exclude the nested customer object:
await service.createCustomerAddresses(
prevAddresses.map(({ customer, ...address }) => address)
)
Alternatively, the initial snapshot retrieval (service.listCustomerAddresses) could be modified to select only scalar fields, preventing the nested customer object from being included in the first place. This ensures that when the compensation attempts to restore the address, it only creates the address entity itself, correctly linking it to the existing customer via customer_id without attempting a duplicate customer insert.
Ensuring Robust ESHOPMAN Development
This insight highlights the importance of meticulous data handling within ESHOPMAN's Node.js/TypeScript services, especially for critical operations like customer data management. By implementing this fix, ESHOPMAN developers can ensure that their headless commerce applications maintain high data integrity, providing a reliable and seamless experience for merchants managing their storefronts via HubSpot and for their end-customers. It's a testament to the ESHOPMAN community's commitment to building a robust and dependable platform for modern e-commerce.