For orders that do not fit into one box and must be shipped in multiple boxes going to the same delivery address, Shippo can now create one shipment and provide one tracking number for multiple boxes.
A multi-piece shipment is created the same way a normal shipment is generated. You can find a detailed tutorial for how to implement multi-piece shipments for your business in our API documentation. At this time, multi-piece shipments are available through the API and for UPS and FedEx shipments only.
Changing the Shippo Data Model
In the process of building multi-piece shipment support, we had to change the underlying structure of the Shippo database and API without any downtime that would affect our customers. Below we share how we did it so that you can better understand how objects relate to each other.
The biggest change we made to the data model was reversing the one-to-many relationship between the Shipment and the Parcel object.
The Shipment object contains information about the entire shipment including the to and from addresses, the parcel dimensions and weight, as well as extra attributes such as signature confirmation or 3rd party billing. It contains information used to generate shipping rates.
The Parcel object represents the dimensions and weight of the package that’s being shipped. Along with the Address objects, It’s used to generate the Shipment object.
Previously, Shipments and Parcels in our database had one-to-many relationship.
- A Shipment held a foreign key to a Parcel.
- A Parcel could be a part of many Shipments.
In order to support the multi-piece shipments, we needed to reverse the relationship so that one Shipment could be related to many Parcels.
Since we had to make this change to existing Shipments and Parcels as well as new ones that are being constantly generated, we decided to take a multi-step approach to changing the data structure:
First from one-to-many to one-to-one, and then to many-to-one – giving us clean data sets and easy development along the way.
(In the old model, the concept of the Parcel object was of a template that contained information about the dimensions and weight of a package. So if you were a store that only shipped 10″x10″x10″ size boxes weighing 5lbs, you could reuse the same Parcel object for every Shipments.
Now, each Parcel object will represent an unique package so that one Shipment going to the same destination can contain many packages.)
The Development Process
The first thing we did was add a foreign key column to the Parcels table for Shipment IDs, allowing us to link Parcels to Shipments.
Then we needed to account for two scenarios:
- The data migration to populate the Shipment ID column of the Parcel table.
- Writing to this column when creating new Shipments.
In the data migration, we needed to cover the case where a Parcel belonged to many Shipments. So we created a duplicate Parcel for each additional Shipment that the Parcel belonged to. We then set the foreign keys on the respective Shipments and Parcels to point to each other – giving them a one-to-one relationship.
Next, we had to ensure that newly created Shipments also honored the one-to-one relationship between Shipments and Parcels.
For new Shipments created with Parcels already belonging to another Shipment, we duplicated the existing Parcel and associated the new Parcel with the new Shipment. However, this introduced a race condition:
When multiple Shipments with the same Parcel are created at the same time, our logic won’t notice that the Parcel already belongs to a Shipment, so multiple Shipments will be created without any parcels.
To fix this we introduced a single-column table with a unique constraint where we insert the Parcel id after we have attached it to a Shipment. If the database complains about an integrity error, then we know that we need to duplicate the Parcel. The atomicity of the database prevents this additional step from introducing a new race condition. We considered using locks or select-for-updates but those have been problematic in the past and this solution was much cleaner and simpler.
Now, all of our Shipments and Parcels have a one-to-one relationship.
All we had to do to create a one-to-many relationship between a Shipment and Parcels is switch our codebase to only read from the Parcel’s foreign key column (taking in the Shipment ID) and dropped the Parcel ID column on the Shipment table.
Want to help us solve challenges like this and use code to power shipments around the world? We’re hiring!
Shippo is a multi-carrier API and web app that helps retailers, marketplaces and platforms connect to a global network of carriers. Businesses use Shippo to get real-time rates, print labels, automate international paperwork, track packages and facilitate returns. Shippo provides the tools to help businesses succeed through shipping.