Simmy Chaos Engine for .NET – Part 6, Configuring Policies Dynamically

Full source code here.

Want to learn more about Polly? Check out my Pluralsight course on it.

Simmy chaos policies have configurable options, via these options the polices can be turned on or off, have the rate at which they fire set, and in the case of the latency policy, the injected delay.

Here is an example of the fault policy -

1AsyncInjectOutcomePolicy<HttpResponseMessage> faultPolicy = MonkeyPolicy.InjectFaultAsync<HttpResponseMessage>(
2	new HttpRequestException("Simmy threw an exception, you should probably handle it in some way."),
3	injectionRate: .5, //can be set through configuration
4	enabled: () => true //can be set through configuration
5);

And here is the latency policy -

1var latencyPolicy = MonkeyPolicy.InjectLatencyAsync<HttpResponseMessage>(
2	TimeSpan.FromSeconds(3), //can be set through configuration
3	0.5, //can be set through configuration
4	enabled: () => true); //can be set through configuration

This post shows how to set the all these settings via the appsettings.json file, but you could just as easily use a key store such as Consul or those provided by AWS and Azure. When you are using the application, change the values in appsettings.json and you will see the policies behave differently, try turning then on and off, changing the injection rate, and increasing/decreasing the delay.

In the appsettings.json I have something like -

 1{
 2  "SimmySettings": {
 3    "FaultPolicySettings": {
 4      "Enabled": false,
 5      "InjectionRate": 0.1
 6    },
 7    "LatencyPolicySettings": {
 8      "Enabled": true,
 9      "Latency": 3,
10      "InjectionRate": 0.99
11    }
12  }
13}

A matching FaultOptions.cs class.

 1namespace SimmyConfigurePolicies
 2{
 3    public class FaultOptions
 4    {
 5        public LatencyPolicySettings LatencyPolicySettings { get; set; }
 6        public FaultPolicySettings FaultPolicySettings { get; set; }
 7    }
 8
 9    public class LatencyPolicySettings
10    {
11        public bool Enabled { get; set; }
12        public double Latency { get; set; }
13        public double InjectionRate { get; set; }
14
15    }
16    public class FaultPolicySettings
17    {
18        public bool Enabled { get; set; }
19        public double InjectionRate { get; set; }
20    }
21}

The ConfigureServices method looks like this -

 1public void ConfigureServices(IServiceCollection services)
 2{
 3	services.AddOptions();
 4
 5	services.Configure<FaultOptions>(Configuration.GetSection("SimmySettings"));
 6	services.AddPolicyRegistry();
 7
 8	// Add the HttpClientFactory
 9	services.AddHttpClient("OpenBreweryDb", client =>
10	{
11		client.BaseAddress = new Uri("https://api.openbrewerydb.org/breweries/");
12		client.DefaultRequestHeaders.Add("Accept", "application/json");
13	});
14
15	services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
16}

Note the services.AddOptions, Microsoft recommends adding this, but I have found it works fine without. Also notice that I have NOT added any policies to the registry, if you have been following along with this series of blog posts you might have noticed that I usually add the policies inside ConfigureServices.

Instead I have moved the policies to the Configure method because I need to get at the instance of FaultOptions that is inside the service collection. There are ways to do this from inside ConfigureServices, but Microsoft warns you not to do this. But accessing the FaultOptions from inside Configure is just fine. I define the policies and use the value inside the FaultOptions to configure the two policies.

For the fault policy I added a methods to make accessing the FaultOptions easier (you can see these in the attached source code), and for the latency policy I access the values directly.

Then add the policies to the registry.

 1public void Configure(IApplicationBuilder app, IHostingEnvironment env)
 2{
 3	_faultOptions = app.ApplicationServices.GetService<IOptionsMonitor<FaultOptions>>();
 4
 5	AsyncInjectOutcomePolicy<HttpResponseMessage> faultPolicy = MonkeyPolicy.InjectFaultAsync<HttpResponseMessage>(
 6		FaultToThrow, // 
 7		FaultPolicyInjectionRate,
 8		FaultPolicyEnabled
 9	 );
10
11	AsyncInjectLatencyPolicy<HttpResponseMessage> latencyPolicy = MonkeyPolicy.InjectLatencyAsync<HttpResponseMessage>(
12		 TimeSpan.FromSeconds(_faultOptions.CurrentValue.LatencyPolicySettings.Latency),
13		 _faultOptions.CurrentValue.LatencyPolicySettings.InjectionRate,
14		 () => _faultOptions.CurrentValue.LatencyPolicySettings.Enabled);
15
16	var registry = app.ApplicationServices.GetRequiredService<IPolicyRegistry<string>>();
17	registry.Add("FaultPolicy", faultPolicy);
18	registry.Add("LatencyPolicy", latencyPolicy);
19	if (env.IsDevelopment())
20	{
21		app.UseDeveloperExceptionPage();
22	}
23
24	app.UseMvc();
25}

The policies are used in the BreweryController, in the constructor I add the HttpClientFactory and the policy registry. In the action method grab the policy from the registry and use it to make the request to the remote service.

 1public class BreweryController : Controller
 2{
 3	private readonly IHttpClientFactory _httpClientFactory;
 4	private readonly IPolicyRegistry<string> _policyRegistry;
 5	public BreweryController(IHttpClientFactory httpClientFactory, IPolicyRegistry<string> policyRegistry)
 6	{
 7		_policyRegistry = policyRegistry;
 8		_httpClientFactory = httpClientFactory;
 9	}
10
11	public async Task<IActionResult> Get(string state = "Massachusetts", string name = "night shift brewing")
12	{
13		var simmyPolicy = _policyRegistry.Get<AsyncMonkeyPolicy<HttpResponseMessage>>("LatencyPolicy");
14		//var simmyPolicy = _policyRegistry.Get<AsyncMonkeyPolicy<HttpResponseMessage>>("FaultPolicy");
15		string requestEndpoint = $"?by_state={state}&by_name={name}";
16
17		var httpClient = _httpClientFactory.CreateClient("OpenBreweryDb");
18
19		var response =  await simmyPolicy.ExecuteAsync( async () => await httpClient.GetAsync(requestEndpoint));
20		if (response.IsSuccessStatusCode)
21		{
22			var breweries = await response.Content.ReadAsAsync<List<Brewery>>();
23			return Ok(breweries);
24		}
25
26		return StatusCode((int)response.StatusCode, await response.Content.ReadAsStringAsync());
27	}
28}

There are a few improvements that can be made to this and I’ll show them over the next few posts, one is to select a random chaos policy from the registry and the second is to apply the chaos policies to HttpClientFactory policy selector.

Full source code here.

comments powered by Disqus

Related