Strategic memory caching in Commerce SDK CRT extensions

Strategic memory caching in Commerce SDK CRT extensions (including sample code)

Introduction

The store-side business logic for Dynamics 365 Commerce is often extended with custom code. There are no limits to what a CRT extension can do. It can use process existing data from the database, can process data from extended tables, or can call external systems. Some of these changes to the out-of-box experience (OOBE) may impact performance negatively. Assuming the custom code is already optimized, meaning proper database indexes are applied, there may be cases when caching can lead to additional performance improvements. Note that there are ways to cache to the database, but in my experience memory caching is easier to implement and maintain. I have come across a few cases when caching was useful:

  1. Calculation/fetching of local data is expensive. Data needed often enough that the processing impacts the experience. Data are not needed in real-time (some delay acceptable)
  2. Calculation/fetching of local data from the database. Data are needed very frequently and even 50ms database calls may add up in total. Data are not needed in real-time.
  3. Calling an external API (i.e. Real-time transaction service or other services). Data is needed frequently and data does not need to be real-time or changes rarely (i.e. secret keys)
  4. Fetching data that is always static for a store, customer, cart, product, etc. but still expensive to fetch.

Cases 1 and 2 are similar to any data that can only be edited in the back office and need to be synced to the channel. Because of that, they are by definition not real-time data and could be cached further if its needed.

Focusing on the cases where the data is not real-time or truly external will usually give already good gains to the user experience.

There are some cases where data can also be written in the channel. These are usually a little trickier to cache. Think about saving customer data and still seeing the old data after refreshing. In some cases, these issues can be overcome, for example when the cache key is based not only on a simple key but maybe some other data that changed (i.e. cart, etc.).

Another important note: Since we are caching in memory of a web server and there are multiple load-balanced servers, there will be multiple caches. This does not present a large issue, as requests from a single user usually get routed to the same server.

Memory caching use cases

The different use cases (including proper logging as described here and memory cache implementation) discussed below are available for download.

Feel free to take a look or use for your project at your own risk. Let me know of any bugs.

Case 1: Response caching inside own handler

You can override a request handler and have the new handler take care of caching for all responses of the original handler. In some scenarios, this is an advantage over caching at the caller. GetUserDefinedSecretStringValueRealtimeRequest is a good example to cache in the handler and not at every caller. We can assume that the secrets can be cached for a certain time safely.

Case 2: Response caching outside own handler

In this case, the caching is controlled at the caller. This way we could have different behaviors for different callers, dependent on the caller. If the same caching code would have to be added to multiple places, and the same caching behavior is needed, consider use case 1 instead.

Case 3: Simple value caching outside own handler

This case is similar to case 2. The caching occurs at the caller. However, we may not always have a “proper” Response but a simple value to cache.

Maximizing diagnostics capabilities with Application Insights for Dynamics 365 Commerce CSU

Operations Insights, Applications Insights and custom logger to maximize results for Dynamics 365 Commerce CSU code.

Anyone who has troubleshooted a production environment with diagnostics traces in order to understand why a certain operation did something unexpected knows that it can be quite difficult. Anyone who also had to find not enough traces were available will agree that it would have been great if they had just spent the time to do it right…

So, if you have not yet, let’s do it right this time.

There are 2 logging-related topics I want to discuss first with regards to tracing in the Commerce CSU.

Operational Insights

Operational Insights. Enabling it will allow you to see almost as much as Microsoft. You can check on slow queries, long running operations, errors, count how many Checkouts you had and so on. The following screen shot is from a dashboard that uses the Operational Insights data from a performance environment. It’s very easy to see what the slowest SQL statements are.

A simple performance dashboard that uses Operational/Application Insights

Details about how to use the data is something for another blog.

Extension code instrumentation

Instrument your extension code for Application Insights. When a customer or partner extends the Commerce functionality, Microsoft recommends to properly instrument the extension code with logging statements. The documentation includes a simple class that can be used for logging. Once done and the code is deployed, it will emit traces that look like this:

A custom trace in Application Insights

I have highlighted the customDimension property in red. It is an important piece of information as we have the capability to put anything into the trace for later querying.

Improving the logger class

Any out-of-the-box trace that comes from the CSU has many data inside customDimension. The ones that are very useful to filter or correlate with while querying Application Insights (with Kusto language) are highlighted below in yellow:

out-of-box experience (OOBE) traces and its customDimensions property

The concept of an ActivityId allows you to query for all traces that occurred as part of one request.

AppSessionId is for all data from a terminal.

UserSessionId is unique for each logged in user.

There are many more that are pretty self-explanatory.

I was able to improve the simple logging class that Microsoft provides in such a way that customDimensions holds more meaningful information. In the screen shot below you can see that many of the out-of-box experience (OOBE) properties are now included in the custom trace.

Once we instrument the code (in places where it would aid troubleshooting), we have a great chance to find issues just by looking into the Application Insights store. The code to write a trace is simple:

var logger = Logger.GetLogger(request.RequestContext);
var trace = logger.CreateTraceWithSessionContext(SeverityLevel.Information);
trace.Properties.Add("executionTimeMilliseconds", stopwatch.ElapsedMilliseconds.ToString(CultureInfo.InvariantCulture));
trace.Properties.Add("EventId", "100000");
trace.Message = "Some message";
logger.TrackTrace(trace);

Feel free to take a look or use the logger for your project at your own risk. Let me know if you find any bugs. It is ready to go for a Dynamics 365 CRT extension project based on the latest Commerce SDK.

I am looking forward to a future release when Microsoft supports sending traces from Dynamics 365 Finance and SCM to Applications Insights as well.

Thanks.