[email protected]

Manage future pricing with Nexus

Price changes in e-commerce are often planned in advance and scheduled: either a change to the list price or a campaign price that takes effect on a specific date. Getting every system to show the new price at the right moment is a harder challenge than you might think, and in this article we walk through how to solve it with Nexus.

Anders Ekdahl9 June 2024

Price changes in e-commerce are often planned in advance and scheduled: either a change to the list price, or a campaign price that takes effect on a specific date. Getting every system to show the new price at the right moment is a harder challenge than you might think, and in this article we walk through how to solve it with Nexus.

In the previous article we talked about how to use Nexus to secure the data flow between the e-commerce system and the search and relevance engine. In this article we build on that by walking through how, with Nexus, you can make sure future prices take effect at the right time. In the previous article we walked through some of the pitfalls of using, for example, serverless technology and a fully event-driven architecture for this sync. Future prices are another pitfall for the event-driven architecture, because such a price does not generate any event when it becomes active. The date is set previously, so the e-commerce system will not generate an update to the product. It becomes our job to handle the moment when the new price becomes active.

The best solution to this problem is to make sure all your systems can handle date-driven prices, and that this date-driven pricing can be synced in advance so that when the moment for a new price passes, no sync needs to happen, the new price simply becomes automatically available everywhere. But it is rare that all systems, and especially a search and relevance engine, can handle this.

Often you can index pretty much any data to a search engine, so one option is to push future prices to the search engine and then let the frontend figure out which price to show. The biggest problem with that is that it does not work with the search engine's built-in functionality such as filtering and sorting.

When in the previous article we had processed a queue message representing that a product had changed, we marked it as done, and that message will not be processed again until the product is updated. But in the case of a future price, the product will not be updated and generate a new event. The future price is already decided. Instead, it is up to us to react at the given moment and send the new price to the search engine.

We start by expanding the message to include a future price:

[QueueMessage("product")]
public class ProductChangedQueueMessage : IQueueMessageWithId
{
    public string? Id { get; set; }
    public ProductInformation? ProductInformation { get; set; }
    public ProductPrice? Price { get; set; }
    public FuturePrice? FuturePrice { get; set; }
}

public class ProductInformation
{
    public required string? Name { get; set; }
    public required string? Description { get; set; }
}

public class ProductPrice
{
    public required decimal Price { get; set; }
    public required string Currency { get; set; }
}

public class FuturePrice : ProductPrice
{
    public required DateTime StartDateUtc { get; set; }
}

In reality it probably looks more complex than this. There can be several future prices, and it may be a mix of future campaigns or future adjustments to the list price. But to demonstrate the concept in this article we keep it simple.

To handle this we need to ask Nexus to process the same message again in the future in our queue job:

public class ProductQueueJob(IPreviousMessageProvider previousMessageProvider) : IScheduledQueueJob<ProductChangedQueueMessage>
{
    public string DefaultSchedule => CronSchedule.TimesPerMinute(10);

    public async Task<ProcessResults> ProcessMessageAsync(ProductChangedQueueMessage message, CancellationToken cancellationToken)
    {
        var now = DateTime.UtcNow;
        var priceToUse = message.FuturePrice?.StartDateUtc <= now ? message.FuturePrice : message.Price;
        
        await SendProductToSearchEngineAsync(message, priceToUse);
        
        DateTime? processAgainAtUtc = now < message.FuturePrice?.StartDateUtc ? message.FuturePrice.StartDateUtc : null;

        return ProcessResults.Processed(
            processAgainAtUtc: processAgainAtUtc,
            newPriority: 100
        );
    }
}

What we do here is check whether the date of the future price has started, and if so send the future (now current) price to the search engine. And if the future price has not yet taken effect, we ask Nexus to process the message again at the moment the price becomes active.

What we also do is ask Nexus to process it with high priority at the next occasion, to ensure these price updates are processed before other messages. We want to avoid other product updates that are not as time-critical getting ahead in the queue and creating delay before the prices become visible.

At the moment the job asked to process the message again, Nexus will update the message's status to be handled by the job again. If many messages have the same date, all will be marked to be processed at the same time. When we then make sure our job can process many messages at once , we create minimal delay between the moment when the price becomes active and when it is visible on the site. If it is a smaller number of products it is a matter of a few seconds. If it is a large number of products it can take the search engine longer to receive the update, but should still happen within a minute.

What happens if the price's date changes?

The scheduling of the new price can change at any time. The campaign may be moved earlier, or another campaign may be planned to run before the intended one.

Either way, it is handled automatically in Nexus. The new date becomes an update to the product, which is sent to Nexus. The Nexus job will run right after the update comes in and then asks Nexus to process the message again on the new date for the price. The previously scheduled run of the message is cancelled because we now have new information, and the run is scheduled for a different date instead.

In the same way, many future prices can be planned up in advance. If the message contains ten different prices and dates, the job can index the current price to the search engine and then ask Nexus to run again when the next price becomes relevant. All without any update to the product coming from outside.

This was part two in the series on how to use Nexus to improve the data flow between the e-commerce system and the search engine. In the next article we will walk through how to handle related entities changing. For example, a category that a product sits in changing name, and how to avoid having to run a full sync for the name change to take effect.

Does this sound like it could help you with your data flows? Get in touch. Nexus is free forever, and you get full access to the code via an open source licence.

Anders Ekdahl

Author

Anders Ekdahl

Anders is the mind behind the technical frameworks that have taken the likes of Lyko and Nordic Nest to the next level. In his role as CTO of Sweden's leading e-commerce consultancy, he has led more than 200 developers to success, combining technology, strategy and business value in a distinctive way.

Related articles

Technical debt: when 'we will fix it later' becomes 'why is everything on fire?'

Technical debt is more than a technical concept, it is a business-critical reality that affects everything from time-to-market to customer experience. In e-commerce, where every millisecond and every click counts, the choices you make in your technical platform can have far-reaching consequences. When quick fixes are prioritised over long-term durability, an invisible but growing debt is built up. It affects not only development speed and stability, but at worst can slow the company's ability to innovate and compete. To face the future the right way, technical debt has to be understood, quantified and managed as the strategic investment it actually is.

John Järpling