Create a WebJob to Ensure Cache is Valid

Caching is one of the operations that we always do when building software. We put an object that will be faster to be retrieved to improve the performance of our software. But likewise, because it is a Software Architecture. Choosing one side will always have risks. If the system cache the wrong data, of course, the output will be all wrong. Because of it, today we will learn how to avoid this problem by implementing WebJob to compare the cache vs the original value. 

Create Azure Redis Cache

To create the Azure Redis Cache, you can follow up on this article or you can follow this step:

Go to your portal.azure.com > search Azure Redis Cache > hit create button > Fill in Subscription, Resource group, DNS name, Location, and Cache type. For example, these are the settings that I applied:

Create redis cache

Once the resource is ready, go to your Redis Cache > Access Keys > copy the Primary/Secondary connection string for the next step.

PowerAutomate Flow (As WebApi)

For the demonstration purpose, I create a very simple API using PowerAutomate:

Power Automate as source data

Create Console App

Today we will create Console App. The first one is just to set the cache. You just need to create an exe with this value (the *.csproj with .NET Core 3.1 and all the packages necessary):

<Project Sdk="Microsoft.NET.Sdk">
	<PropertyGroup>
		<OutputType>Exe</OutputType>
		<TargetFramework>netcoreapp3.1</TargetFramework>
	</PropertyGroup>
	<ItemGroup>
		<PackageReference Include="StackExchange.Redis" Version="2.2.88" />
		<PackageReference Include="System.Configuration.ConfigurationManager" Version="6.0.0" />
	</ItemGroup>
</Project>

Below is the code for Program.cs:

using StackExchange.Redis;
using System.Threading.Tasks;
namespace RedisDemo
{
    internal class Program
    {
        static async Task Main()
        {
            string cacheConnection = "redis-connection-string";
            using (var connection = ConnectionMultiplexer.Connect(cacheConnection))
            {
                var db = connection.GetDatabase();
                db.StringSet("demo-cache", "Temmy Wahyu Raharjo");
            }
        }
    }
}

You can paste the Redis Connection string on line #10.

Create WebJob

In the next one, we will make a WebJob that will be triggered by the timer that we will set. Below is the *.csproj:

<Project Sdk="Microsoft.NET.Sdk;Microsoft.NET.Sdk.Publish">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.1</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.Azure.WebJobs.Extensions" Version="4.0.1" />
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.1" />
    <PackageReference Include="Microsoft.Extensions.Logging.ApplicationInsights" Version="2.20.0" />
    <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="6.0.0" />
    <PackageReference Include="RestSharp" Version="107.3.0" />
    <PackageReference Include="StackExchange.Redis" Version="2.2.88" />
    <PackageReference Include="System.Configuration.ConfigurationManager" Version="6.0.0" />
  </ItemGroup>
  <ItemGroup>
    <None Update="appSettings.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Settings.job">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </None>
  </ItemGroup>
</Project>

And here is the Program.cs of the WebJob:

using Microsoft.Azure.WebJobs;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using RestSharp;
using StackExchange.Redis;
using System;
namespace DemoWebJob
{
    internal class Program
    {
        static void Main(string[] args)
        {
            var builder = new HostBuilder()
                .ConfigureLogging(b =>
                {
                    b.AddConsole();
                    b.AddApplicationInsights();
                })
                .ConfigureWebJobs(b =>
                {
                    b.AddAzureStorageCoreServices();
                    b.AddTimers();
                });
            var host = builder.Build();
            host.Run();
        }
    }
    public class CacheManagerFunction
    {
        public IConfiguration Configuration { get; }
        public CacheManagerFunction(IConfiguration configuration)
        {
            Configuration = configuration;
        }
        [FunctionName("CacheManagerFunction")]
        public void Run([TimerTrigger("%scheduledTimer%")] TimerInfo _, ILogger logger)
        {
            logger.LogInformation("Begin executing CacheManagerFunction..");
            var redisConnectionString = Configuration.GetConnectionString("redisCacheConnection");
            using (var connection = ConnectionMultiplexer.Connect(redisConnectionString))
            {
                var db = connection.GetDatabase();
                var cache = db.StringGet("demo-cache");
                if (string.IsNullOrEmpty(cache)) return;
                var client = new RestClient();
                var request = new RestRequest("https://prod-06.southeastasia.logic.azure.com:443/workflows/255f73e3f2154ec9bf9d79d2bc8acfd3/triggers/manual/paths/invoke?api-version=2016-06-01&sp=%2Ftriggers%2Fmanual%2Frun&sv=1.0&sig=Md8xbCGz5qfdJh8UhZquufQWTfbQ288lU6Ske3tYvCU")
                {
                    Method = Method.Get
                };
                var result = client.ExecuteAsync<Response>(request)
                    .GetAwaiter().GetResult();
                var latestValue = result.Data ?? new Response();
                if (cache == latestValue.Data)
                {
                    logger.LogInformation("Cache still the same..");
                    return;
                }
                logger.LogInformation($"Cache is different. Cache: {cache}. Latest value: {latestValue.Data}.");
                db.StringSet("demo-cache", latestValue.Data);
            }
        }
    }
    public class Response
    {
        public string Data { get; set; }
    }
}

The Program class is the class that initiated the WebJob SDK. While the implementation is on CacheManagerFunction class. In CacheManagerFunction.Run method will be triggered automatically by the SDK. The first thing we need to do is just to connect with the Redis Database to get the value of the current cache. The next step is to get the value from the original source (PowerAutomate API). The last of-course to compare the value. If the value is not the same, then we need to change the Redis cache and we are done.

The next step is to deploy the WebJob. Right-click on the project, Publish > Create new profile > Select Azure WebJobs as the Specific Target.

Publish as Azure WebJobs

In the next dialog, you need to select/create App Service. Here is the setting of my Profile:

Publish Profile

Once the profile is ready (the resource needed is also created), you can publish it and wait until it is done.

If you have not yet created Storage Account (because our WebJob needs this Resource Group), you need to create one: Storage Account > create button > fill in Subscription, Resource Group, Storage account name, Region, Performance, and Redundancy. Once created, you can go to Access Key + save it for the next step.

Once done, you can go to App Service > Configuration blade and setup below red boxes (AzureWebJobStorage and scheduledTimer):

App Configuration

The other configurations is automatic + if you setup the Application Insights

Don’t forget to add below Connection String (redisCacheConnection):

App Connection String

Once you have done these changes, don’t forget to hit the Save button on the top.

If you want to debug it locally, here are my appSettings.json:

{
  "scheduledTimer": "0 */5 * * * *",
  "connectionStrings": {
    "azureWebJobsStorage": "Connection-String-Storage-Account",
    "redisCacheConnection": "Connection-String-Redis-Cache"
  }
}

Summary

We can summarize the step for the implementation like below diagram:

Diagram

You can run the first Exe to set/changed the cache. For the setting of the timer, you can changed base on your requirement. Then if you check the WebJobs log you will see the result (sample from my side):

Result

Happy learning!

Resources

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 )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.