Skip to content

Commit

Permalink
Slimmed specifications setup (#10)
Browse files Browse the repository at this point in the history
Merged When and Then builders, as Then was redundant. Renamed builders accordingly

Updated docs.
  • Loading branch information
oskardudycz committed Oct 7, 2022
1 parent a646c42 commit 7d8bfa4
Show file tree
Hide file tree
Showing 10 changed files with 934 additions and 113 deletions.
418 changes: 418 additions & 0 deletions README.md

Large diffs are not rendered by default.

418 changes: 418 additions & 0 deletions mdsource/README.source.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,30 @@ private ShoppingCart(
Apply(@event);
}

public static ShoppingCart Evolve(ShoppingCart cart, object @event)
{
switch (@event)
{
case ShoppingCartOpened opened:
cart.Apply(opened);
break;
case ProductAdded productAdded:
cart.Apply(productAdded);
break;
case ProductRemoved productRemoved:
cart.Apply(productRemoved);
break;
case ShoppingCartConfirmed confirmed:
cart.Apply(confirmed);
break;
case ShoppingCartCanceled canceled:
cart.Apply(canceled);
break;
}

return cart;
}

public void Apply(ShoppingCartOpened @event)
{
Id = @event.CartId;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,32 +14,8 @@ public class ShoppingCartTests
{
private readonly Random random = new();

private static readonly Func<ShoppingCart, object, ShoppingCart> evolve =
(cart, @event) =>
{
switch (@event)
{
case ShoppingCartOpened opened:
cart.Apply(opened);
break;
case ProductAdded productAdded:
cart.Apply(productAdded);
break;
case ProductRemoved productRemoved:
cart.Apply(productRemoved);
break;
case ShoppingCartConfirmed confirmed:
cart.Apply(confirmed);
break;
case ShoppingCartCanceled canceled:
cart.Apply(canceled);
break;
}
return cart;
};

private readonly HandlerSpecification<ShoppingCart> Spec = Specification.For(Handle, evolve);
private readonly HandlerSpecification<ShoppingCart> Spec =
Specification.For<ShoppingCart>(Handle, ShoppingCart.Evolve);

private class DummyProductPriceCalculator: IProductPriceCalculator
{
Expand Down Expand Up @@ -100,7 +76,7 @@ public static class AggregateTestExtensions<TAggregate> where TAggregate : Aggre
public static DecideResult<object, TAggregate> Handle(Handler<object, TAggregate> handle, TAggregate aggregate)
{
var result = handle(aggregate);
var updatedAggregate = result.CurrentState ?? aggregate;
var updatedAggregate = result.NewState ?? aggregate;
return DecideResult.For(updatedAggregate, updatedAggregate.DequeueUncommittedEvents());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ string Reason

public static class BankAccountDecider
{

public static object Handle(
Func<DateTimeOffset> now,
object command,
Expand Down
6 changes: 3 additions & 3 deletions src/Ogooreck/BusinessLogic/Decider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public static Decider<TCommand, TEvent, TState> For<TCommand, TEvent, TState>(
) =>
new(
decide,
(state, @event) => evolve != null ? evolve(state, @event): state,
(state, @event) => evolve != null ? evolve(state, @event) : state,
getInitialState ?? ObjectFactory<TState>.GetDefaultOrUninitialized
);

Expand All @@ -29,7 +29,7 @@ public static Decider<TCommand, TEvent, TState> For<TCommand, TEvent, TState>(
Func<TState>? getInitialState = null
) =>
For<TCommand, TEvent, TState>(
(command, currentState) => new [] {decide(command, currentState)},
(command, currentState) => new[] { decide(command, currentState) },
evolve,
getInitialState
);
Expand All @@ -48,7 +48,7 @@ public static Decider<TCommand, TEvent, TState> For<TCommand, TEvent, TState>(

public record DecideResult<TEvent, TState>(
TEvent[] NewEvents,
TState? CurrentState = default
TState? NewState = default
);

public static class DecideResult
Expand Down
77 changes: 20 additions & 57 deletions src/Ogooreck/BusinessLogic/DeciderSpecification.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,94 +17,57 @@ public class DeciderSpecification<TCommand, TEvent, TState>
public DeciderSpecification(Decider<TCommand, TEvent, TState> decider) =>
this.decider = decider;

public GivenDeciderSpecificationBuilder<TCommand, TEvent, TState> Given(params TEvent[] events) =>
public WhenDeciderSpecificationBuilder<TCommand, TEvent, TState> Given(params TEvent[] events) =>
Given(() =>
{
var currentState = decider.GetInitialState();
return events.Aggregate(currentState, decider.Evolve);
});

public GivenDeciderSpecificationBuilder<TCommand, TEvent, TState> Given(TState currentState) =>
public WhenDeciderSpecificationBuilder<TCommand, TEvent, TState> Given(TState currentState) =>
Given(() => currentState);

public GivenDeciderSpecificationBuilder<TCommand, TEvent, TState> Given() =>
public WhenDeciderSpecificationBuilder<TCommand, TEvent, TState> Given() =>
new(decider);

public GivenDeciderSpecificationBuilder<TCommand, TEvent, TState> Given(Func<TState> getCurrentState) =>
public WhenDeciderSpecificationBuilder<TCommand, TEvent, TState> Given(Func<TState> getCurrentState) =>
new(decider, getCurrentState);
}

public class GivenDeciderSpecificationBuilder<TCommand, TEvent, TState>
{
private readonly Decider<TCommand, TEvent, TState> decider;
private readonly Func<TState>? getCurrentState;

public GivenDeciderSpecificationBuilder(
Decider<TCommand, TEvent, TState> decider,
Func<TState>? getCurrentState = null
)
{
this.decider = decider;
this.getCurrentState = getCurrentState;
}

public WhenDeciderSpecificationBuilder<TCommand, TEvent, TState> When(params TCommand[] commands) =>
new(decider, getCurrentState, commands);
}

public class WhenDeciderSpecificationBuilder<TCommand, TEvent, TState>
{
private readonly Decider<TCommand, TEvent, TState> decider;
private readonly TCommand[] commands;
private readonly Func<TState>? getCurrentState;
private readonly Lazy<TestResult<TState, TEvent>> getResult;

public WhenDeciderSpecificationBuilder(
Decider<TCommand, TEvent, TState> decider,
Func<TState>? getCurrentState,
TCommand[] commands
Func<TState>? getCurrentState = null
)
{
this.decider = decider;
this.commands = commands;
this.getCurrentState = getCurrentState;
getResult = new Lazy<TestResult<TState, TEvent>>(Perform);
}

public ThenDeciderSpecificationBuilder<TEvent, TState> Then(params TEvent[] expectedEvents) =>
new ThenDeciderSpecificationBuilder<TEvent, TState>(getResult).Then(expectedEvents);

public ThenDeciderSpecificationBuilder<TEvent, TState> Then(TState expectedState) =>
new ThenDeciderSpecificationBuilder<TEvent, TState>(getResult).Then(expectedState);

public ThenDeciderSpecificationBuilder<TEvent, TState> Then(params Action<TEvent[]>[] eventsAssertions) =>
new ThenDeciderSpecificationBuilder<TEvent, TState>(getResult).Then(eventsAssertions);

public ThenDeciderSpecificationBuilder<TEvent, TState> Then(params Action<TState>[] stateAssertions) =>
new ThenDeciderSpecificationBuilder<TEvent, TState>(getResult).Then(stateAssertions);

public ThenDeciderSpecificationBuilder<TEvent, TState> Then(params Action<TState, TEvent[]>[] assertions) =>
new ThenDeciderSpecificationBuilder<TEvent, TState>(getResult).Then(assertions);

public void ThenThrows<TException>(Action<TException>? assert = null) where TException : Exception =>
new ThenDeciderSpecificationBuilder<TEvent, TState>(getResult).ThenThrows(assert);
public ThenDeciderSpecificationBuilder<TEvent, TState> When(params TCommand[] commands) =>
new(RunTest(commands));

private TestResult<TState, TEvent> Perform()
{
var currentState = (getCurrentState ?? decider.GetInitialState)();
var resultEvents = new List<TEvent>();

foreach (var command in commands)
private Lazy<TestResult<TState, TEvent>> RunTest(TCommand[] commands) =>
new(() =>
{
var (newEvents, state) = decider.Decide(command, currentState);
resultEvents.AddRange(newEvents);
var currentState = (getCurrentState ?? decider.GetInitialState)();
var resultEvents = new List<TEvent>();
currentState = state ?? newEvents.Aggregate(currentState, decider.Evolve);
}
foreach (var command in commands)
{
var (newEvents, state) = decider.Decide(command, currentState);
resultEvents.AddRange(newEvents);
return new TestResult<TState, TEvent>(currentState, resultEvents.ToArray());
}
currentState = state ?? newEvents.Aggregate(currentState, decider.Evolve);
}
return new TestResult<TState, TEvent>(currentState, resultEvents.ToArray());
});
}

public class ThenDeciderSpecificationBuilder<TEvent, TState>
Expand Down
46 changes: 22 additions & 24 deletions src/Ogooreck/BusinessLogic/HandlerSpecification.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using Ogooreck.Factories;

namespace Ogooreck.BusinessLogic;
namespace Ogooreck.BusinessLogic;
#pragma warning disable CS1591
public delegate DecideResult<TEvent, TState> Handler<TEvent, TState>(TState state);

Expand All @@ -24,55 +22,55 @@ public HandlerSpecification(Decider<Handler<TEvent, TState>, TEvent, TState> dec

public static class HandlerSpecificationExtensions
{
public static WhenDeciderSpecificationBuilder<Handler<TEvent, TState>, TEvent, TState>
public static ThenDeciderSpecificationBuilder<TEvent, TState>
When<TEvent, TState>(
this GivenDeciderSpecificationBuilder<Handler<TEvent, TState>, TEvent, TState> given,
this WhenDeciderSpecificationBuilder<Handler<TEvent, TState>, TEvent, TState> when,
params Func<TState, TEvent>[] whens
) =>
given.When(whens.Select(WhenMapping<TEvent, TState>.ToHandler).ToArray());
when.When(whens.Select(WhenMapping<TEvent, TState>.ToHandler).ToArray());

public static WhenDeciderSpecificationBuilder<Handler<TEvent, TState>, TEvent, TState>
public static ThenDeciderSpecificationBuilder<TEvent, TState>
When<TEvent, TState>(
this GivenDeciderSpecificationBuilder<Handler<TEvent, TState>, TEvent, TState> given,
this WhenDeciderSpecificationBuilder<Handler<TEvent, TState>, TEvent, TState> when,
params Func<TState, TEvent[]>[] whens
) =>
given.When(whens.Select(WhenMapping<TEvent, TState>.ToHandler).ToArray());
when.When(whens.Select(WhenMapping<TEvent, TState>.ToHandler).ToArray());

public static WhenDeciderSpecificationBuilder<Handler<TEvent, TState>, TEvent, TState>
public static ThenDeciderSpecificationBuilder<TEvent, TState>
When<TEvent, TState>(
this GivenDeciderSpecificationBuilder<Handler<TEvent, TState>, TEvent, TState> given,
this WhenDeciderSpecificationBuilder<Handler<TEvent, TState>, TEvent, TState> when,
params Action<TState>[] whens
) =>
given.When(whens.Select(WhenMapping<TEvent, TState>.ToHandler).ToArray());
when.When(whens.Select(WhenMapping<TEvent, TState>.ToHandler).ToArray());

public static WhenDeciderSpecificationBuilder<Handler<TEvent, TState>, TEvent, TState>
public static ThenDeciderSpecificationBuilder<TEvent, TState>
When<TEvent, TState>(
this GivenDeciderSpecificationBuilder<Handler<TEvent, TState>, TEvent, TState> given,
this WhenDeciderSpecificationBuilder<Handler<TEvent, TState>, TEvent, TState> when,
params Func<TState>[] whens
) =>
given.When(whens.Select(WhenMapping<TEvent, TState>.ToHandler).ToArray());
when.When(whens.Select(WhenMapping<TEvent, TState>.ToHandler).ToArray());

public static WhenDeciderSpecificationBuilder<Handler<TEvent, TState>, TEvent, TState>
public static ThenDeciderSpecificationBuilder<TEvent, TState>
When<TEvent, TState>(
this GivenDeciderSpecificationBuilder<Handler<TEvent, TState>, TEvent, TState> given,
this WhenDeciderSpecificationBuilder<Handler<TEvent, TState>, TEvent, TState> when,
params Func<TState, TState>[] whens
) =>
given.When(whens.Select(WhenMapping<TEvent, TState>.ToHandler).ToArray());
when.When(whens.Select(WhenMapping<TEvent, TState>.ToHandler).ToArray());


public static WhenDeciderSpecificationBuilder<Handler<TEvent, TState>, TEvent, TState>
public static ThenDeciderSpecificationBuilder<TEvent, TState>
When<TEvent, TState>(
this GivenDeciderSpecificationBuilder<Handler<TEvent, TState>, TEvent, TState> given,
this WhenDeciderSpecificationBuilder<Handler<TEvent, TState>, TEvent, TState> when,
params Func<TEvent[]>[] whens
) =>
given.When(whens.Select(WhenMapping<TEvent, TState>.ToHandler).ToArray());
when.When(whens.Select(WhenMapping<TEvent, TState>.ToHandler).ToArray());

public static WhenDeciderSpecificationBuilder<Handler<TEvent, TState>, TEvent, TState>
public static ThenDeciderSpecificationBuilder<TEvent, TState>
When<TEvent, TState>(
this GivenDeciderSpecificationBuilder<Handler<TEvent, TState>, TEvent, TState> given,
this WhenDeciderSpecificationBuilder<Handler<TEvent, TState>, TEvent, TState> when,
params Func<TEvent>[] whens
) =>
given.When(whens.Select(WhenMapping<TEvent, TState>.ToHandler).ToArray());
when.When(whens.Select(WhenMapping<TEvent, TState>.ToHandler).ToArray());
}

public static class WhenMapping<TEvent, TState>
Expand Down
25 changes: 25 additions & 0 deletions src/Ogooreck/BusinessLogic/Specification.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,31 @@ public static DeciderSpecification<TState> For<TState>(
) =>
new(Decider.For(decide, evolve, getInitialState));

public static HandlerSpecification<TEvent, TState> For<TEvent, TState>(
Func<Handler<TEvent, TState>, TState, DecideResult<TEvent, TState>> decide,
Func<TState, TEvent, TState> evolve,
Func<TState>? getInitialState = null
) =>
new(
Decider.For(
decide,
evolve,
getInitialState
)
);

public static HandlerSpecification<TEvent, TState> For<TEvent, TState>(
Func<TState, TEvent, TState>? evolve = null,
Func<TState>? getInitialState = null
) =>
new(
Decider.For<Handler<TEvent, TState>, TEvent, TState>(
decide: (handler, currentState) => handler(currentState),
evolve,
getInitialState
)
);

public static HandlerSpecification<TState> For<TState>(
Func<Handler<object, TState>, TState, DecideResult<object, TState>> decide,
Func<TState, object, TState> evolve,
Expand Down
2 changes: 1 addition & 1 deletion src/Ogooreck/Ogooreck.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<VersionPrefix>0.4.0</VersionPrefix>
<VersionPrefix>0.5.0</VersionPrefix>
<TargetFramework>net6.0</TargetFramework>
<GenerateAssemblyTitleAttribute>true</GenerateAssemblyTitleAttribute>
<GenerateAssemblyDescriptionAttribute>true</GenerateAssemblyDescriptionAttribute>
Expand Down

0 comments on commit 7d8bfa4

Please sign in to comment.