-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
22 changed files
with
507 additions
and
19 deletions.
There are no files selected for viewing
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
13 changes: 13 additions & 0 deletions
13
examples/FeatureFlagger.Example.Console/FeatureFlagger.Examples.Console.fsproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 1,13 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
<PropertyGroup> | ||
<OutputType>Exe</OutputType> | ||
<TargetFramework>netcoreapp1.1</TargetFramework> | ||
</PropertyGroup> | ||
<ItemGroup> | ||
<ProjectReference Include="../../src/FeatureFlagger/FeatureFlagger.csproj"> | ||
<Name>FeatureFlagger.csproj</Name> | ||
</ProjectReference> | ||
</ItemGroup> | ||
<ItemGroup /> | ||
<Import Project="..\..\.paket\Paket.Restore.targets" /> | ||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 1,74 @@ | ||
namespace FeatureFlagger.Behaviours | ||
{ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
|
||
public class Bucket : IBucket | ||
{ | ||
// NOTE: this *really* shouldn't be in memory! | ||
// dictionary has key = bucketName, value = userName list. | ||
private readonly Dictionary<string, List<string>> buckets; | ||
|
||
public Bucket() | ||
{ | ||
buckets = new Dictionary<string, List<string>>(); | ||
} | ||
|
||
public bool Check(string bucketName, string userName) | ||
{ | ||
bucketName = bucketName.ToUpperInvariant(); | ||
userName = userName.ToUpperInvariant(); | ||
List<string> users; | ||
if (!buckets.ContainsKey(bucketName) | ||
|| !buckets.TryGetValue(bucketName, out users)) | ||
{ | ||
return false; | ||
} | ||
|
||
return users.Contains(userName); | ||
} | ||
|
||
public bool CheckVariants(string featureName, string userName) | ||
{ | ||
// get the base feature name. | ||
featureName = | ||
featureName.Remove( | ||
featureName.IndexOf( | ||
Constants.VariantSeparator, | ||
StringComparison.OrdinalIgnoreCase)); | ||
|
||
// find all the buckets for the group of variants. | ||
var featureBuckets = | ||
buckets.Keys.ToList() | ||
.FindAll( | ||
x => | ||
x.StartsWith(featureName, StringComparison.OrdinalIgnoreCase)); | ||
|
||
// check all the variant buckets for the given user. | ||
return featureBuckets.Any(b => Check(b, userName)); | ||
} | ||
|
||
public void Add(string bucketName, string userName) | ||
{ | ||
bucketName = bucketName.ToUpperInvariant(); | ||
userName = userName.ToUpperInvariant(); | ||
if (buckets.ContainsKey(bucketName)) | ||
{ | ||
List<string> users; | ||
// ReSharper disable once InvertIf | ||
if (buckets.TryGetValue(bucketName, out users)) | ||
{ | ||
if (!users.Contains(userName)) | ||
{ | ||
users.Add(userName); | ||
} | ||
} | ||
} | ||
else | ||
{ | ||
buckets.Add(bucketName, new List<string> { userName }); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 1,138 @@ | ||
namespace FeatureFlagger.Behaviours | ||
{ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.ComponentModel.Composition; | ||
using System.Security.Cryptography; | ||
|
||
[Export(typeof(IBehaviour))] | ||
public class DistributionBehaviour : IBehaviour | ||
{ | ||
private static readonly Random Random = new Random(); | ||
private static readonly object Synclock = new object(); | ||
private readonly IUser user; | ||
private readonly IBucket bucket; | ||
|
||
[ImportingConstructor] | ||
public DistributionBehaviour( | ||
[Import(AllowDefault = true)] IUser user, | ||
[Import(AllowDefault = true)] IBucket bucket) | ||
{ | ||
this.user = user ?? new User(); | ||
this.bucket = bucket ?? new Bucket(); | ||
} | ||
|
||
// <distribution [percentage="0.125"] /> | ||
public Func<Dictionary<string, string>, bool> Behaviour() | ||
{ | ||
const string ControlSubstring = Constants.VariantSeparator Constants.Control; | ||
|
||
return x => | ||
{ | ||
var featureName = x[Constants.Feature]; | ||
var userName = user.UserName; | ||
// Debug.WriteLine($"UserName: {userName}, FeatureName: {featureName}"); | ||
// check the bucket. | ||
var isInBucket = bucket.Check(featureName, userName); | ||
if (isInBucket) | ||
{ | ||
// this user is already using this variant. | ||
return true; | ||
} | ||
var percentage = GetPercentage(x); | ||
// is it an A/B test or an ABCD test? | ||
// we do this on the naming conventions where | ||
// an ABCD test contains a variant name. | ||
if (featureName.Contains(Constants.VariantSeparator)) | ||
{ | ||
// ABCD. | ||
if (bucket.CheckVariants(featureName, userName)) | ||
{ | ||
return false; | ||
} | ||
if (featureName.IndexOf(ControlSubstring, StringComparison.OrdinalIgnoreCase) >= 0) | ||
{ | ||
// _Control variant must be checked last in code flow | ||
// which places an onus on the ordering of variants. | ||
bucket.Add(featureName, userName); | ||
// this user is now in the split test's control group. | ||
return true; | ||
} | ||
// ReSharper disable once InvertIf : it's clearer this way. | ||
if (RollDice(percentage)) | ||
{ | ||
bucket.Add(featureName, userName); | ||
// this user is now using this variant. | ||
return true; | ||
} | ||
// so, not this variant. | ||
return false; | ||
} | ||
// A/B. | ||
var control = featureName ControlSubstring; | ||
isInBucket = bucket.Check(control, userName); | ||
if (isInBucket) | ||
{ | ||
// this user is already in the control group. | ||
return false; | ||
} | ||
if (RollDice(percentage)) | ||
{ | ||
bucket.Add(featureName, userName); | ||
// in an A/B test this user will get the functionality. | ||
return true; | ||
} | ||
// control. | ||
bucket.Add(control, userName); | ||
return false; | ||
}; | ||
} | ||
|
||
private static decimal GetPercentage(IReadOnlyDictionary<string, string> x) | ||
{ | ||
// percentage is optional: we don't use it for the control group. | ||
string percent = | ||
x.TryGetValue(Constants.Percentage, out percent) | ||
? percent | ||
: "0"; | ||
|
||
var percentage = Convert.ToDecimal(percent); | ||
|
||
// want percentages as decimals, i.e. 0.125 is 12.5%. | ||
if (percentage > 1) | ||
{ | ||
throw new ArgumentException("Distribution percentage must be expressed as a decimal."); | ||
} | ||
|
||
return percentage; | ||
} | ||
|
||
private static bool RollDice(decimal percentage) | ||
{ | ||
lock (Synclock) | ||
{ | ||
var randomNumber = new byte[1]; | ||
var crytoService = new RNGCryptoServiceProvider(); | ||
crytoService.GetBytes(randomNumber); | ||
var diceRoll = (randomNumber[0] % 100) 1; | ||
|
||
return diceRoll <= percentage * 100; | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 1,12 @@ | ||
namespace FeatureFlagger.Behaviours | ||
{ | ||
public interface IBucket | ||
{ | ||
bool Check(string bucketName, string userName); | ||
|
||
bool CheckVariants(string featureName, string userName); | ||
|
||
void Add(string bucketName, string userName); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 1 addition & 1 deletion
2
src/FeatureFlagger.ConfigurationReaders/IConfigurationSectionHandler.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
12 changes: 12 additions & 0 deletions
12
src/FeatureFlagger.ConfigurationWriters/FeatureFlagger.ConfigurationWriters.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 1,12 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
<PropertyGroup> | ||
<TargetFramework>netstandard2.0</TargetFramework> | ||
</PropertyGroup> | ||
<ItemGroup> | ||
<ProjectReference Include="..\FeatureFlagger.Domain\FeatureFlagger.Domain.csproj" /> | ||
</ItemGroup> | ||
<ItemGroup> | ||
<PackageReference Include="System.Data.SqlClient" Version="4.6.1" /> | ||
</ItemGroup> | ||
<Import Project="..\..\.paket\Paket.Restore.targets" /> | ||
</Project> |
Oops, something went wrong.