Первое что приходит в голову, хранить полную либо частичную копию купленного товара на момент оформления заказа
Ещё один вариант - использовать Event Sourcing, но у него те же самые проблемы, плюс тот факт, что нужно всё приложение под него переделывать. Или просто вести лог изменений и получать срез на нужный момент - Event Sourcing на минималках.
Другого варианта нет - старые данные нужно хранить, чтобы их использовать в дальнейшем. Можно хранить только какие-то основные поля: цену, название и т.п., чтобы уменьшить объём. Но нужно понимать, что чем больше данных вы храните, тем больше у вас в будущем возможностей для манёвра - вывести какую-то дополнительную информацию, посчитать статистику и т.п.
Я обычно либо завожу прям отдельную таблицу для товаров оформленных заказов, в которой дублируются данные, либо в корзине завожу json-колонку, в которой хранится срез на момент оформления. Логирование при этом тоже ведётся, но его неудобно использовать. Да и логичнее один раз "вычислить" значение и навсегда его сохранить, чем каждый раз дёргать из логов (и всё равно так же хранить в кеше).
В случае с json-колонкой мы сначала хранили вообще всё - модель и связанные с ней - с большим запасом. А примерно через полгода, когда начали упираться в размер данных, проанализировали кейсы и оставили только нужные поля.