- ID
- a8f9707f-26fc-4819-b5bd-c76f49cfaacb
BE-7395 PATCH /api/v2/customers/self/address/ creates duplicate address records on every call
- source :: https://hungryroot.atlassian.net/browse/BE-7395
- tags :: Hungryroot API Project
- epic :: EP-411 Eng Parking Lot
- sprint :: HR BE Sprint 107
- status :: Status - To Do
- priority :: Priority - Low
- points :: 1
Description
Problem
The PATCH /api/v2/customers/self/address/ endpoint creates a new Address row in
the database on every call, rather than updating the existing address in place.
The frontend calls this endpoint on every field blur (when a user clicks out of
any input field during checkout), which generates a large number of duplicate
address records.
This was observed on staging (api.staging.hungryroot.com) -- see attached
screenshot showing numerous PATCH calls in the network tab, each returning 200,
with a growing list of address records visible in the admin panel.
Root Cause Analysis
The Address model uses WormMixin (Write Once Read Many), which forces
save(force_insert=True) -- meaning every save creates a new row instead of
updating.
In app/rest/customer_address.py, the update() method explicitly documents this
behavior:
This is a custom implementation of PATCH. This will first create, then update
the customer model. This is done this way because we made address table WORM,
and this would work without breaking contract.
The WORM pattern is intentional for audit/history purposes, but the combination
with the frontend calling PATCH on every field blur creates excessive duplicate
records.
Key Code Paths
View: app/rest/customer_address.py - CustomerAddressDetail.update() (line 169)
Model: app/models/address.py - Address(WormMixin, BaseModelV1)
WORM mixin: app/lib/model_utils/worm_mixin.py - forces force_insert=True
Service: app/services/api/delivery.py - update_customer_address() also calls
Address.objects.create() (line 304)
Impact
Database bloat from duplicate address records (one per field blur event)
Unnecessary side effects on each PATCH: Braze phone sync, delivery day
recalculation, future order address assignment, and potential order reshuffles
Wasted API calls and server resources
Investigation Items
Frontend: Should the frontend debounce or batch address field changes, only
sending PATCH when the user explicitly submits (e.g., clicks "Continue to
payment")?
Backend: Should the backend deduplicate or short-circuit when the address data
hasn't meaningfully changed?
WORM pattern: Is the append-only pattern still the right tradeoff for addresses,
given the volume of records it generates? Should we consider soft-deleting old
records or adding a deduplication check before creating?
Side effects: The PATCH triggers delivery day recalculation, Braze sync, and
future order updates on every call -- these should only run when the address
actually changes.