Skip to content

Commit 7ade3ce

Browse files
committed
Improve core mutation DX
1 parent 1222929 commit 7ade3ce

22 files changed

Lines changed: 451 additions & 244 deletions

Examples/Core/BillingQuotas/Mutations/DecreaseQuotaMutation.cs

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,23 @@ namespace BillingQuotas.Mutations;
1010
/// <summary>
1111
/// Mutation that decreases the quota for specific user by specified amount.
1212
/// </summary>
13-
internal sealed record DecreaseQuotaMutation(
14-
string UserId,
15-
int Amount,
16-
MutationContext Context
17-
) : IMutation<QuotaState>
13+
internal sealed class DecreaseQuotaMutation(
14+
string userId,
15+
int amount,
16+
MutationContext context
17+
) : MutationBase<QuotaState>(
18+
CreateIntent(
19+
operationName: "DecreaseQuota",
20+
category: "Billing",
21+
description: "Decrease user quota by given amount",
22+
riskLevel: MutationRiskLevel.High),
23+
context)
1824
{
19-
public MutationIntent Intent { get; } = new()
20-
{
21-
OperationName = "DecreaseQuota",
22-
Category = "Billing",
23-
RiskLevel = MutationRiskLevel.High,
24-
Description = "Decrease user quota by given amount"
25-
};
25+
public string UserId { get; } = userId;
26+
27+
public int Amount { get; } = amount;
2628

27-
public ValidationResult Validate(QuotaState state)
29+
public override ValidationResult Validate(QuotaState state)
2830
{
2931
var result = new ValidationResult();
3032

@@ -40,19 +42,15 @@ public ValidationResult Validate(QuotaState state)
4042
return result;
4143
}
4244

43-
public MutationResult<QuotaState> Apply(QuotaState state)
45+
public override MutationResult<QuotaState> Apply(QuotaState state)
4446
{
4547
var quotas = state.UserQuotas.ToDictionary(kv => kv.Key, kv => kv.Value);
4648
quotas[UserId] = quotas.GetValueOrDefault(UserId) - Amount;
4749

4850
var newState = state with { UserQuotas = quotas };
4951

50-
var changes = ChangeSet.Single(
51-
StateChange.Modified($"UserQuotas.{UserId}", null, quotas[UserId])
52-
);
53-
54-
return MutationResult<QuotaState>.Success(newState, changes);
52+
return Success(
53+
newState,
54+
StateChange.Modified($"UserQuotas.{UserId}", null, quotas[UserId]));
5555
}
56-
57-
public MutationResult<QuotaState> Simulate(QuotaState state) => Apply(state);
58-
}
56+
}

Examples/Core/BillingQuotas/Mutations/IncreaseQuotaMutation.cs

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,23 @@ namespace BillingQuotas.Mutations;
1010
/// <summary>
1111
/// Mutation that increases the quota for specific user by given amount.
1212
/// </summary>
13-
internal sealed record IncreaseQuotaMutation(
14-
string UserId,
15-
int Amount,
16-
MutationContext Context
17-
) : IMutation<QuotaState>
13+
internal sealed class IncreaseQuotaMutation(
14+
string userId,
15+
int amount,
16+
MutationContext context
17+
) : MutationBase<QuotaState>(
18+
CreateIntent(
19+
operationName: "IncreaseQuota",
20+
category: "Billing",
21+
description: "Increase user quota by given amount",
22+
riskLevel: MutationRiskLevel.Medium),
23+
context)
1824
{
19-
public MutationIntent Intent { get; } = new()
20-
{
21-
OperationName = "IncreaseQuota",
22-
Category = "Billing",
23-
RiskLevel = MutationRiskLevel.Medium,
24-
Description = "Increase user quota by given amount"
25-
};
25+
public string UserId { get; } = userId;
26+
27+
public int Amount { get; } = amount;
2628

27-
public ValidationResult Validate(QuotaState state)
29+
public override ValidationResult Validate(QuotaState state)
2830
{
2931
var result = new ValidationResult();
3032

@@ -37,19 +39,15 @@ public ValidationResult Validate(QuotaState state)
3739
return result;
3840
}
3941

40-
public MutationResult<QuotaState> Apply(QuotaState state)
42+
public override MutationResult<QuotaState> Apply(QuotaState state)
4143
{
4244
var quotas = state.UserQuotas.ToDictionary(kv => kv.Key, kv => kv.Value);
4345
quotas[UserId] = quotas.GetValueOrDefault(UserId) + Amount;
4446

4547
var newState = state with { UserQuotas = quotas };
4648

47-
var changes = ChangeSet.Single(
48-
StateChange.Modified($"UserQuotas.{UserId}", null, quotas[UserId])
49-
);
50-
51-
return MutationResult<QuotaState>.Success(newState, changes);
49+
return Success(
50+
newState,
51+
StateChange.Modified($"UserQuotas.{UserId}", null, quotas[UserId]));
5252
}
53-
54-
public MutationResult<QuotaState> Simulate(QuotaState state) => Apply(state);
55-
}
53+
}

Examples/Core/BillingQuotas/Mutations/ResetQuotaMutation.cs

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,26 @@
44
using ModularityKit.Mutator.Abstractions.Engine;
55
using ModularityKit.Mutator.Abstractions.Intent;
66
using ModularityKit.Mutator.Abstractions.Results;
7-
using ValidationResult = ModularityKit.Mutator.Abstractions.Results.ValidationResult;
87

98
namespace BillingQuotas.Mutations;
109

1110
/// <summary>
1211
/// Mutation that resets user's quota to zero.
1312
/// </summary>
14-
internal sealed record ResetQuotaMutation(
15-
string UserId,
16-
MutationContext Context
17-
) : IMutation<QuotaState>
13+
internal sealed class ResetQuotaMutation(
14+
string userId,
15+
MutationContext context
16+
) : MutationBase<QuotaState>(
17+
CreateIntent(
18+
operationName: "ResetQuota",
19+
category: "Billing",
20+
description: "Reset user quota to zero",
21+
riskLevel: MutationRiskLevel.High),
22+
context)
1823
{
19-
public MutationIntent Intent { get; } = new()
20-
{
21-
OperationName = "ResetQuota",
22-
Category = "Billing",
23-
RiskLevel = MutationRiskLevel.High,
24-
Description = "Reset user quota to zero"
25-
};
24+
public string UserId { get; } = userId;
2625

27-
public ValidationResult Validate(QuotaState state)
26+
public override ValidationResult Validate(QuotaState state)
2827
{
2928
var result = new ValidationResult();
3029

@@ -34,19 +33,15 @@ public ValidationResult Validate(QuotaState state)
3433
return result;
3534
}
3635

37-
public MutationResult<QuotaState> Apply(QuotaState state)
36+
public override MutationResult<QuotaState> Apply(QuotaState state)
3837
{
3938
var quotas = state.UserQuotas.ToDictionary(kv => kv.Key, kv => kv.Value);
4039
quotas[UserId] = 0;
4140

4241
var newState = state with { UserQuotas = quotas };
4342

44-
var changes = ChangeSet.Single(
45-
StateChange.Modified($"UserQuotas.{UserId}", null, 0)
46-
);
47-
48-
return MutationResult<QuotaState>.Success(newState, changes);
43+
return Success(
44+
newState,
45+
StateChange.Modified($"UserQuotas.{UserId}", null, 0));
4946
}
50-
51-
public MutationResult<QuotaState> Simulate(QuotaState state) => Apply(state);
52-
}
47+
}

Examples/Core/BillingQuotas/Scenarios/EmergencyIncreaseScenario.cs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,10 @@ internal static async Task Run(IMutationEngine engine)
5555
userName: "System Admin",
5656
reason: "Emergency quota increase");
5757

58-
var mutations = new IMutation<QuotaState>[]
59-
{
58+
var result = await engine.ExecuteBatchAsync(
59+
state,
6060
new IncreaseQuotaMutation("alice", 15, ctx),
61-
new IncreaseQuotaMutation("bob", 10, ctx)
62-
};
63-
64-
var result = await engine.ExecuteBatchAsync(mutations, state);
61+
new IncreaseQuotaMutation("bob", 10, ctx));
6562

6663
foreach (var res in result.Results)
6764
{
@@ -75,4 +72,4 @@ internal static async Task Run(IMutationEngine engine)
7572
}
7673
}
7774
}
78-
}
75+
}

Examples/Core/BillingQuotas/Scenarios/MonthlyResetScenario.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ internal static async Task Run(IMutationEngine engine)
6060
.Select(user => new ResetQuotaMutation(user, ctx))
6161
.ToArray();
6262

63-
var result = await engine.ExecuteBatchAsync(mutations, state);
63+
var result = await engine.ExecuteBatchAsync(state, mutations);
6464

6565
Console.WriteLine($"Executed: {result.Results.Count}");
6666
Console.WriteLine($"Success: {result.Results.Count(r => r.IsSuccess)}");
@@ -72,4 +72,4 @@ internal static async Task Run(IMutationEngine engine)
7272
foreach (var (user, quota) in state.UserQuotas)
7373
Console.WriteLine($" {user}: {quota}");
7474
}
75-
}
75+
}

Examples/Core/FeatureFlags/Mutations/DisableFeatureMutation.cs

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,28 +10,30 @@ namespace FeatureFlags.Mutations;
1010
/// <summary>
1111
/// Mutation that disables a feature flag in the current <see cref="FeatureFlagsState"/>.
1212
/// </summary>
13-
internal sealed record DisableFeatureMutation(string FeatureName, MutationContext Context) : IMutation<FeatureFlagsState>
13+
internal sealed class DisableFeatureMutation(string featureName, MutationContext context)
14+
: MutationBase<FeatureFlagsState>(
15+
CreateIntent(
16+
operationName: "DisableFeature",
17+
category: "Configuration",
18+
description: "Disables a feature flag.",
19+
riskLevel: MutationRiskLevel.High),
20+
context)
1421
{
15-
public MutationIntent Intent { get; } = new()
16-
{
17-
OperationName = "DisableFeature",
18-
Category = "Configuration",
19-
RiskLevel = MutationRiskLevel.High,
20-
Description = "Disables a feature flag."
21-
};
22+
public string FeatureName { get; } = featureName;
2223

23-
public MutationResult<FeatureFlagsState> Apply(FeatureFlagsState state)
24+
public override MutationResult<FeatureFlagsState> Apply(FeatureFlagsState state)
2425
{
2526
var newFlags = new Dictionary<string, bool>(state.Flags);
2627
if (newFlags.ContainsKey(FeatureName))
2728
newFlags[FeatureName] = false;
2829

2930
var newState = state with { Flags = newFlags };
30-
var changes = ChangeSet.Single(StateChange.Modified($"Flags.{FeatureName}", true, false));
31-
return MutationResult<FeatureFlagsState>.Success(newState, changes);
31+
return Success(
32+
newState,
33+
StateChange.Modified($"Flags.{FeatureName}", true, false));
3234
}
3335

34-
public ValidationResult Validate(FeatureFlagsState state)
36+
public override ValidationResult Validate(FeatureFlagsState state)
3537
{
3638
var result = new ValidationResult();
3739

@@ -41,6 +43,4 @@ public ValidationResult Validate(FeatureFlagsState state)
4143
result.AddError("FeatureName", $"Feature '{FeatureName}' does not exist");
4244
return result;
4345
}
44-
45-
public MutationResult<FeatureFlagsState> Simulate(FeatureFlagsState state) => Apply(state);
46-
}
46+
}

Examples/Core/FeatureFlags/Mutations/EnableFeatureMutation.cs

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,19 @@ namespace FeatureFlags.Mutations;
1010
/// <summary>
1111
/// Mutation that enables feature flag in the current <see cref="FeatureFlagsState"/>.
1212
/// </summary>
13-
internal sealed record EnableFeatureMutation(string FeatureName, MutationContext Context) : IMutation<FeatureFlagsState>
13+
internal sealed class EnableFeatureMutation(string featureName, MutationContext context)
14+
: MutationBase<FeatureFlagsState>(
15+
CreateIntent(
16+
operationName: "EnableFeature",
17+
category: "Security",
18+
description: "Enables a feature flag.",
19+
riskLevel: MutationRiskLevel.High,
20+
tags: new HashSet<string> { "auth" }),
21+
context)
1422
{
15-
public MutationIntent Intent { get; } = new()
16-
{
17-
OperationName = "EnableFeature",
18-
Category = "Security",
19-
Tags = new HashSet<string> { "auth" },
20-
RiskLevel = MutationRiskLevel.High,
21-
Description = "Enables a feature flag."
22-
};
23+
public string FeatureName { get; } = featureName;
2324

24-
public MutationResult<FeatureFlagsState> Apply(FeatureFlagsState state)
25+
public override MutationResult<FeatureFlagsState> Apply(FeatureFlagsState state)
2526
{
2627
if (state.Flags.TryGetValue(FeatureName, out var oldValue) && oldValue)
2728
return MutationResult<FeatureFlagsState>.Success(state, ChangeSet.Empty);
@@ -31,13 +32,12 @@ public MutationResult<FeatureFlagsState> Apply(FeatureFlagsState state)
3132
[FeatureName] = true
3233
};
3334
var newState = state with { Flags = newFlags };
34-
var changes = ChangeSet.Single(
35-
StateChange.Modified($"Flags.{FeatureName}", oldValue, true)
36-
);
37-
return MutationResult<FeatureFlagsState>.Success(newState, changes);
35+
return Success(
36+
newState,
37+
StateChange.Modified($"Flags.{FeatureName}", oldValue, true));
3838
}
3939

40-
public ValidationResult Validate(FeatureFlagsState state)
40+
public override ValidationResult Validate(FeatureFlagsState state)
4141
{
4242
var result = new ValidationResult();
4343
if (string.IsNullOrEmpty(FeatureName))
@@ -50,6 +50,4 @@ public ValidationResult Validate(FeatureFlagsState state)
5050
}
5151
return result;
5252
}
53-
54-
public MutationResult<FeatureFlagsState> Simulate(FeatureFlagsState state) => Apply(state);
55-
}
53+
}

Examples/Core/FeatureFlags/Scenarios/BatchFeatureToggleScenario.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ internal static async Task Run(IMutationEngine engine)
8484
),
8585
};
8686

87-
var batchResult = await engine.ExecuteBatchAsync(mutations, state);
87+
var batchResult = await engine.ExecuteBatchAsync(state, mutations);
8888
MutationResultLogger.LogBatch(batchResult.Results);
8989

9090
Console.WriteLine($"Success: {batchResult.SuccessCount}, Failed: {batchResult.FailureCount}");

0 commit comments

Comments
 (0)