Sitecore’s History Engine part 2

Last year I wrote a post quickly describing the use of Sitecore’s HistoryManager. Well a lot’s happened since then and I thought I’d post a quick update.

I’ve re-worked some of the code that’s been used and re-used as an extension of Sitecore’s Database class and thought I would share in case others find it as a useful starting point. The extension methods follow Daniel Cazzulino’s ideas on extension methods and testing. I’ll link to his blog post below (or here), it’s a good read, especially if you’re in a group that tends to go a little extension method crazy.
Before we get started I should probably mention that any database using the HistoryEngine will need to have it enabled. This is usually done via a config patch.

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
      <sitecore>
        <databases>
          <database id="web">
            <Engines.HistoryEngine.Storage>
              <obj type="Sitecore.Data.SqlServer.SqlServerHistoryStorage, Sitecore.Kernel">
                <param connectionStringName="$(id)"/>
                <EntryLifeTime>30.00:00:00</EntryLifeTime>
              </obj>
            </Engines.HistoryEngine.Storage>
          </database>
        </databases>
      </sitecore>
    </configuration>

Now into the code…
First up we’re going to make an entry point extension method for our code.

public static class DatabaseExtensions
    {
        internal static Func<Database, IDatabaseHistory> DatabaseHistoryFactory = x => new DatabaseHistory(x);

        public static IDatabaseHistory DatabaseHistory(this Database db)
        {
            return DatabaseHistoryFactory(db);
        }
    }

This will allow you to change out the DatabaseHistoryFactory while running tests in order to supply a mock IDatabaseHistory that doesn’t have a real database dependency. It also has the added feature of allowing us to group our extension method so intellisense doesn’t look like a dog’s breakfast by the end of a project.
Next we’ll create our IDatabaseHistory interface. This defines 4 methods used to manage history entries.

public interface IDatabaseHistory
    {
        /// <summary>
        /// Gets the newest history entries since the last read.
        /// </summary>
        /// <param name="key">The key used while storing and retrieving the last updated date from the dataabase</param>
        /// <returns></returns>
        IEnumerable<HistoryEntry> GetLatestHistoryEntries(string key);

        /// <summary>
        /// Returns all history entries that match the supplied parameters.
        /// </summary>
        /// <param name="startTime">The earliest hisory entry we're interested in. Defaults to DateTime.MinValue</param>
        /// <param name="endTime">The last history entry we're interested in.  Defaults to DateTime.Now</param>
        /// <param name="category">Limits the results to HisotryEntries of this HistoryCategory.  Defaults to HistoryCategory.Item</param>
        /// <returns></returns>
        IEnumerable<HistoryEntry> ProcessHistory(DateTime? startTime = null, DateTime? endTime = null, HistoryCategory category = HistoryCategory.Item);

        /// <summary>
        /// Records the last read date for the given key.
        /// </summary>
        /// <param name="key"></param>
        /// <param name="startTime"></param>
        void SetLastReadDate(string key, DateTime? startTime = null);

        /// <summary>
        /// Retrieves the last read date for the given key
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        DateTime GetLastRecordedUpdateTime(string key);
    }

And finally we’ll create an implementation of this interface. This is the code where we’ll actually be reading from the database and returning results

public class DatabaseHistory : IDatabaseHistory
    {
        public Database Database { get; set; }

        public DatabaseHistory(Database database)
        {
            Assert.ArgumentNotNull(database, "Database");
            Database = database;
        }

        public IEnumerable<HistoryEntry> GetLatestHistoryEntries(string key)
        {
            Assert.ArgumentNotNull(key, "key");
            Assert.IsNotNull(Database, "Unable to process history from null database");
            DateTime fromDate = GetLastRecordedUpdateTime(key);
            DateTime toDate = DateTime.UtcNow;

            IEnumerable<HistoryEntry> results;
            try
            {
                results = ProcessHistory(fromDate, toDate);
            }
            catch (Exception ex)
            {
                Log.Error("Error reading database history", ex, this);
                return Enumerable.Empty<HistoryEntry>();
            }

            SetLastReadDate(key, toDate);

            return results;
        }

        public IEnumerable<HistoryEntry> ProcessHistory(DateTime? startTime = null, DateTime? endTime = null, HistoryCategory category = HistoryCategory.Item)
        {
            Assert.IsNotNull(Database, "Unable to process history from null database");

            DateTime fromDate = startTime.HasValue ? startTime.Value : DateTime.MinValue;
            DateTime toDate = endTime.HasValue ? endTime.Value : DateTime.UtcNow;

            var historyEntries = HistoryManager.GetHistory(Database, fromDate, toDate)
                                        .Where(x => x.Category == category);

            return historyEntries;
        }

        public void SetLastReadDate(string key, DateTime? startTime = null)
        {
            Assert.ArgumentNotNullOrEmpty(key, "key");
            Assert.IsNotNull(Database, "Unable to process history from null database");

            var utcNow = startTime.HasValue ? startTime.Value : DateTime.UtcNow;

            // writing back the date flag of our last operation
            Database.Properties[key] = DateUtil.ToIsoDate(utcNow, true);
        }

        public virtual DateTime GetLastRecordedUpdateTime(string lastUpdatePropertyName)
        {
            Assert.IsNotNull(Database, "Unable to process history from null database");
            DateTime lastUpdateTime;

            if (!DateTime.TryParse(Database.Properties[lastUpdatePropertyName], out lastUpdateTime))
            {
                lastUpdateTime = DateUtil.ParseDateTime(Database.Properties[lastUpdatePropertyName], DateTime.MinValue);
            }

            return lastUpdateTime;
        }
    }

From here you’re all set to go. An example use would be:

Sitecore.Context.Database.DatabaseHistory().GetLatestHistoryEntries("myKey")

One thing I’d like to mention before I go is to pick your key carefully. A common use case I’ve found for the history engine is to find all of the items that have been published since the last time a task was run. If the same key is used in a load balanced environment the first server to run the code will also update the last read data for that key, leaving the other servers with nothing. In this case I’ve found it helpful to append either a specific configuration setting, unique to each web server or something like Environment.MachineName.

Other reading:
http://blogs.clariusconsulting.net/kzu/making-extension-methods-amenable-to-mocking/
https://blog.horizontalintegration.com/2014/03/21/a-brief-look-at-the-sitecore-historymanager/

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: