guibranco/CrispyWaffle

View on GitHub
Src/CrispyWaffle.Redis/Cache/RedisCacheRepository.cs

Summary

Maintainability
B
4 hrs
Test Coverage
using System;
using System.Collections.Generic;
using System.Globalization;
using CrispyWaffle.Cache;
using CrispyWaffle.Log;
using CrispyWaffle.Redis.Utils.Communications;
using StackExchange.Redis;
using StackExchange.Redis.Extensions.Core.Abstractions;

namespace CrispyWaffle.Redis.Cache
{
    /// <summary>
    /// Class RedisCacheRepository.
    /// Implements the <see cref="CrispyWaffle.Cache.ICacheRepository" />
    /// Implements the <see cref="System.IDisposable" />
    /// </summary>
    /// <seealso cref="CrispyWaffle.Cache.ICacheRepository" />
    /// <seealso cref="System.IDisposable" />
    public class RedisCacheRepository : ICacheRepository, IDisposable
    {
        /// <summary>
        /// The connector
        /// </summary>
        private readonly RedisConnector _connector;

        /// <summary>
        /// The cache client
        /// </summary>
        private readonly IRedisClient _cacheClient;

        /// <summary>
        /// Initializes a new instance of the <see cref="RedisCacheRepository" /> class.
        /// </summary>
        /// <param name="connector">The connector.</param>
        public RedisCacheRepository(RedisConnector connector)
        {
            _connector = connector;
            _cacheClient = connector.Cache;
        }

        /// <summary>
        /// Handles the exception.
        /// </summary>
        /// <param name="e">The e.</param>
        private static void HandleException(Exception e)
        {
            if (e.Message.IndexOf("timeout", StringComparison.InvariantCultureIgnoreCase) != -1)
            {
                LogConsumer.Trace(e);
            }
            else
            {
                LogConsumer.Handle(e);
            }
        }

        /// <summary>
        /// Sets to database.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="value">The value.</param>
        /// <param name="key">The key.</param>
        /// <param name="databaseNumber">The database number.</param>
        /// <param name="ttl">The TTL.</param>
        /// <param name="fireAndForget">if set to <c>true</c> [fire and forget].</param>
        public void SetToDatabase<T>(
            T value,
            string key,
            int databaseNumber,
            TimeSpan? ttl = null,
            bool fireAndForget = false
        )
        {
            var flags = CommandFlags.None;
            if (fireAndForget)
            {
                flags = CommandFlags.FireAndForget;
            }

            var inputBytes = _connector.Serializer.Serialize(value);
            _connector
                .GetDatabase(databaseNumber)
                .StringSet(key, inputBytes, ttl, When.Always, flags);
        }

        /// <summary>
        /// Gets from database.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key">The key.</param>
        /// <param name="databaseNumber">The database number.</param>
        /// <returns>T.</returns>
        /// <exception cref="System.InvalidOperationException"></exception>
        /// <exception cref="InvalidOperationException"></exception>
        public T GetFromDatabase<T>(string key, int databaseNumber)
        {
            var valueBytes = _connector
                .GetDatabase(databaseNumber)
                .StringGet(key, CommandFlags.PreferReplica);
            if (!valueBytes.HasValue)
            {
                throw new InvalidOperationException(
                    string.Format(
                        CultureInfo.CurrentCulture,
                        "Unable to get the item with key {0}",
                        key
                    )
                );
            }

            return _connector.Serializer.Deserialize<T>(valueBytes);
        }

        /// <summary>
        /// Tries the get from database.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key">The key.</param>
        /// <param name="databaseNumber">The database number.</param>
        /// <param name="value">The value.</param>
        /// <returns><c>true</c> if get from database, <c>false</c> otherwise.</returns>
        public bool TryGetFromDatabase<T>(string key, int databaseNumber, out T value)
        {
            value = default;
            var valueBytes = _connector
                .GetDatabase(databaseNumber)
                .StringGet(key, CommandFlags.PreferReplica);
            if (!valueBytes.HasValue)
            {
                return false;
            }

            value = _connector.Serializer.Deserialize<T>(valueBytes);
            return true;
        }

        /// <summary>
        /// Removes from database.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <param name="databaseNumber">The database number.</param>
        public void RemoveFromDatabase(string key, int databaseNumber)
        {
            _connector.GetDatabase(databaseNumber).KeyDelete(key);
        }

        /// <summary>
        /// Gets or sets a value indicating whether [should propagate exceptions].
        /// </summary>
        /// <value><c>true</c> if [should propagate exceptions]; otherwise, <c>false</c>.</value>
        public bool ShouldPropagateExceptions { get; set; }

        /// <summary>
        /// Sets the specified value.
        /// </summary>
        /// <typeparam name="T">The type of the value</typeparam>
        /// <param name="value">The value.</param>
        /// <param name="key">The key.</param>
        /// <param name="ttl">The TTL.</param>
        public void Set<T>(T value, string key, TimeSpan? ttl = null)
        {
            try
            {
                if (ttl.HasValue)
                {
                    _cacheClient.Db0.AddAsync(key, value, ttl.Value).Wait();
                }
                else
                {
                    _cacheClient.Db0.AddAsync(key, value).Wait();
                }
            }
            catch (Exception e)
            {
                if (ShouldPropagateExceptions)
                {
                    throw;
                }

                HandleException(e);
            }
        }

        /// <summary>
        /// Sets the specified value.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="value">The value.</param>
        /// <param name="key">The key.</param>
        /// <param name="subKey">The sub key.</param>
        public void Set<T>(T value, string key, string subKey)
        {
            try
            {
                var allValues = _cacheClient.Db0.ExistsAsync(key).Result
                    ? _cacheClient.Db0.HashGetAllAsync<T>(key, CommandFlags.PreferReplica).Result
                    : new Dictionary<string, T>();
                allValues[subKey] = value;
                _cacheClient.Db0.HashSetAsync(key, allValues, CommandFlags.FireAndForget).Wait();
            }
            catch (Exception e)
            {
                if (ShouldPropagateExceptions)
                {
                    throw;
                }

                HandleException(e);
            }
        }

        /// <summary>
        /// Gets the specified key.
        /// </summary>
        /// <typeparam name="T">The type of object (the object will be cast to this type)</typeparam>
        /// <param name="key">The key.</param>
        /// <returns>T.</returns>
        /// <exception cref="System.InvalidOperationException"></exception>
        /// <exception cref="InvalidOperationException"></exception>
        public T Get<T>(string key)
        {
            try
            {
                if (_cacheClient.Db0.ExistsAsync(key).Result)
                {
                    return _cacheClient.Db0.GetAsync<T>(key, CommandFlags.PreferReplica).Result;
                }
            }
            catch (Exception e)
            {
                if (ShouldPropagateExceptions)
                {
                    throw;
                }

                HandleException(e);
            }

            throw new InvalidOperationException(
                string.Format(
                    CultureInfo.CurrentCulture,
                    "Unable to get the item with key {0}",
                    key
                )
            );
        }

        /// <summary>
        /// Gets the specified key.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key">The key.</param>
        /// <param name="subKey">The sub key.</param>
        /// <returns>T.</returns>
        /// <exception cref="System.InvalidOperationException"></exception>
        /// <exception cref="InvalidOperationException"></exception>
        public T Get<T>(string key, string subKey)
        {
            try
            {
                if (
                    _cacheClient.Db0.HashExistsAsync(key, subKey, CommandFlags.PreferReplica).Result
                )
                {
                    return _cacheClient
                        .Db0.HashGetAsync<T>(key, subKey, CommandFlags.PreferReplica)
                        .Result;
                }
            }
            catch (Exception e)
            {
                if (ShouldPropagateExceptions)
                {
                    throw;
                }

                HandleException(e);
            }

            throw new InvalidOperationException(
                string.Format(
                    CultureInfo.CurrentCulture,
                    "Unable to get the item with key {0} and sub key {1}",
                    key,
                    subKey
                )
            );
        }

        /// <summary>
        /// Tries to get a value based on its key, if exists return true, else false.
        /// The out parameter value is the object requested.
        /// </summary>
        /// <typeparam name="T">The type of object (the object will be cast to this type)</typeparam>
        /// <param name="key">The key.</param>
        /// <param name="value">The value.</param>
        /// <returns>Returns <b>True</b> if the object with the key exists, false otherwise</returns>
        public bool TryGet<T>(string key, out T value)
        {
            value = default;
            try
            {
                value = _cacheClient.Db0.GetAsync<T>(key, CommandFlags.PreferReplica).Result;
                return _cacheClient.Db0.ExistsAsync(key).Result;
            }
            catch (Exception e)
            {
                if (ShouldPropagateExceptions)
                {
                    throw;
                }

                HandleException(e);
            }

            return false;
        }

        /// <summary>
        /// Tries the get.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key">The key.</param>
        /// <param name="subKey">The sub key.</param>
        /// <param name="value">The value.</param>
        /// <returns><c>true</c> if get the key, <c>false</c> otherwise.</returns>
        public bool TryGet<T>(string key, string subKey, out T value)
        {
            value = default;
            try
            {
                value = _cacheClient
                    .Db0.HashGetAsync<T>(key, subKey, CommandFlags.PreferReplica)
                    .Result;
                return _cacheClient.Db0.HashExistsAsync(key, subKey).Result;
            }
            catch (Exception e)
            {
                if (ShouldPropagateExceptions)
                {
                    throw;
                }

                HandleException(e);
            }

            return false;
        }

        /// <summary>
        /// Removes the specified key from the cache.
        /// </summary>
        /// <param name="key">The key.</param>
        public void Remove(string key)
        {
            try
            {
                _cacheClient.Db0.RemoveAsync(key).Wait();
            }
            catch (Exception e)
            {
                if (ShouldPropagateExceptions)
                {
                    throw;
                }

                HandleException(e);
            }
        }

        /// <summary>
        /// Removes the specified key.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <param name="subKey">The sub key.</param>
        public void Remove(string key, string subKey)
        {
            try
            {
                _cacheClient.Db0.HashDeleteAsync(key, subKey, CommandFlags.FireAndForget).Wait();
            }
            catch (Exception e)
            {
                if (ShouldPropagateExceptions)
                {
                    throw;
                }

                HandleException(e);
            }
        }

        /// <summary>
        /// Returns the time to live of the specified key.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <returns>The timespan until this key is expired from the cache or 0 if it's already expired or doesn't exists.</returns>
        public TimeSpan TTL(string key)
        {
            try
            {
                return _cacheClient.Db0.Database.KeyTimeToLive(key, CommandFlags.PreferReplica)
                    ?? TimeSpan.Zero;
            }
            catch (Exception e)
            {
                if (ShouldPropagateExceptions)
                {
                    throw;
                }

                HandleException(e);
            }

            return TimeSpan.Zero;
        }

        /// <summary>
        /// Clears this instance.
        /// </summary>
        public void Clear()
        {
            try
            {
                _cacheClient.Db0.FlushDbAsync().Wait();
            }
            catch (Exception e)
            {
                if (ShouldPropagateExceptions)
                {
                    throw;
                }

                HandleException(e);
            }
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            _connector.Dispose();
        }
    }
}