Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add config change handler for Bulkhead. Simplify BulkheadConfig. #64

Merged

Conversation

damtur
Copy link
Contributor

@damtur damtur commented Oct 10, 2017

I've added IObservable into MjolnirConfiguration.
I've made it abstract, so someone using the library may wire-up their implementation of Config change handler.
I've added NonObservableMjolnirConfiguration with no-op implementation for default config.
I've added sample wire-up using Microsoft JSON configuration.
I've added a couple test which should test change handlers using sample configuration.

@damtur damtur requested a review from robhruska October 10, 2017 15:33
@@ -45,7 +46,7 @@ public BulkheadFactory(IMetricEvents metricEvents, IBulkheadConfig bulkheadConfi
if (_bulkheads.TryGetValue(key, out Lazy<SemaphoreBulkheadHolder> holder) && holder.IsValueCreated)
{
var bulkhead = holder.Value.Bulkhead;
_metricEvents.BulkheadGauge(bulkhead.Name, "semaphore", _bulkheadConfig.GetMaxConcurrent(key), bulkhead.CountAvailable);
_metricEvents.BulkheadGauge(bulkhead.Name, "semaphore", _config.GetBulkheadConfiguration(key.Name).MaxConcurrent, bulkhead.CountAvailable);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be better to pass in a GroupKey to GetBulkheadConfiguration?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had considered that but decided that it'll be easier to implement Configuration in any adapter if that will be just the string. So for simplicity, I've left it as a string.

Copy link
Member

@robhruska robhruska Oct 10, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More of an opinion, but I think it's clearer to the calling code if it just has to pass in a GroupKey, since then callers don't have to worry about translating a GroupKey to its appropriate configuration string - the configuration code can know what to do with the GroupKey and do it consistently for any call. GroupKey is the abstraction over bulkhead/breaker identifiers and I've always preferred to use that within Mjolnir if it's available instead of raw strings.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a strong argument, but sort of relevant: http://wiki.c2.com/?StringlyTyped

public void OnNext(BulkheadConfiguration value)
{
var newValue = _expression(value);
var hasChanged = !Equals(_currentValue, newValue);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this work, particularly for reference types? If T is a String, what does this evaluate to if they're the same value?

Given that this is Object.Equals and strings are reference types, I'm concerned this'll do an object reference comparison.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should work as string has "Equals" overriden. From Object code:

 public virtual bool Equals(object obj)
    {
      return RuntimeHelpers.Equals(this, obj);
    }

    public static bool Equals(object objA, object objB)
    {
      if (objA == objB)
        return true;
      if (objA == null || objB == null)
        return false;
      return objA.Equals(objB);
    }

I've also checked with my debugger and it seems to work on my machine.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah cool, thanks for checking.

namespace Hudl.Mjolnir.Config
{
/// <summary>
/// Used only for configs which will never change.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor, but is this true? If observables aren't used, configuration values can still change, correct? It's just that they won't fire change events. A re-read of the configuration property may yield a different value, though, right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In that case "Static" may be a bit misleading as the type name here. Could go with something like NonObservableBulkheadConfiguration?

Copy link
Contributor Author

@damtur damtur Oct 10, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the first comment: It will never change since this is only used for DefaultMjolnirConfig which is implemented inside the library already. If someone will provide different config they have to implement their own implementation of BulkheadConfiguration.
I'll make that class internal!
Also I'll change its name.


namespace Hudl.Mjolnir.Config
{
internal static class BulkheadConfigurationObserverCreationExtensions
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: Since these are extensions on BulkheadConfiguration, I'd expect this to be named BulkheadConfigurationExtensions.

@@ -0,0 +1,11 @@
{
"testconfig": {
"isenabled": true,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does case matter here? The case of these keys is inconsistent.


/// <summary>
/// Per-breaker configuration. breaker-key is the argument passed to the Command constructor.
/// </summary>
protected Dictionary<string, BreakerConfiguration> BreakerConfigurations { get; set; }
public Dictionary<string, BreakerConfiguration> BreakerConfigurations { get; set; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the keys for these Dictionaries be GroupKeys now that it's the argument to access config?

{
if (key == null) return DefaultBulkheadConfiguration;
if (string.IsNullOrEmpty(key.Name)) return DefaultBulkheadConfiguration;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IsNullOrWhiteSpace

@@ -100,11 +102,11 @@ public CommandConfiguration GetCommandConfiguration(string groupKey = null)
/// <param name="key">Bulkhead configuration for a given key. Default value returned if non-existent or
/// null.</param>
/// <returns></returns>
public BulkheadConfiguration GetBulkheadConfiguration(string key = null)
public BulkheadConfiguration GetBulkheadConfiguration(GroupKey key)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are the other methods (GetCommandConfiguration, GetBreakerConfiguration) switching over to GroupKey instead of string as well?


public void OnError(Exception error)
{
throw new NotImplementedException();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the best approach here? i.e. is it impossible for OnError to happen, or should we have better handling here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good find. I'll change to no-op.
FWIW With my implementation, it's not possible for OnError or OnCompleted to be fired.

}
catch (Exception)
{
//Purely to make sure that other change events still fire
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be clearer about why we're ignoring the exception? Should we at least log here?

/// </summary>
/// <param name="observer">Mjolnir configuration observer which is acting upon configuration change</param>
/// <returns></returns>
public abstract IDisposable Subscribe(IObserver<MjolnirConfiguration> observer);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to have this class (MjolnirConfiguration) handle all of the observer logic and make this internal instead of public abstract? It looks like most of the implementations of this are the same - do inheriting classes need their own control over how the subscriptions work?

BulkheadConfiguration bulkheadConfiguration;
return BulkheadConfigurations.TryGetValue(key, out bulkheadConfiguration) ?
return BulkheadConfigurations.TryGetValue(key.Name, out bulkheadConfiguration) ?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible for the Dictionary to have a null value for any key? It might be good to protect against returning null values from this method to prevent NullReferenceExceptions when callers access properties on the returned value, since the calling code assumes non-null objects returned.



/// <summary>
/// Allows subscribtions for configuration change. Whenever any property change in MjolnirConfig all
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"subscribtions"

@damtur damtur merged commit 96c9906 into Version3-ConfigMigration Oct 18, 2017
@damtur damtur deleted the Version3-ConfigMigration-ChangeHandler branch October 18, 2017 20:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants