Skip to content

Commit

Permalink
Ensure that the new aggregate has an id. Proposed fix for #75
Browse files Browse the repository at this point in the history
  • Loading branch information
alexeyzimarev committed Apr 7, 2022
1 parent 93cbb3b commit 074fb9a
Show file tree
Hide file tree
Showing 5 changed files with 53 additions and 10 deletions.
2 changes: 1 addition & 1 deletion src/Core/src/Eventuous/Aggregate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,5 +114,5 @@ public abstract class Aggregate<T, TId> : Aggregate<T>
where T : AggregateState<T, TId>, new()
where TId : AggregateId {
/// <inheritdoc />
public override string GetId() => State.Id;
public override string GetId() => State.Id ?? throw new Exceptions.InvalidIdException<TId>();
}
10 changes: 5 additions & 5 deletions src/Core/src/Eventuous/AggregateId.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
namespace Eventuous;
namespace Eventuous;

[PublicAPI]
public abstract record AggregateId {
protected AggregateId(string value) {
if (string.IsNullOrWhiteSpace(value))
throw new Exceptions.InvalidIdException(this);
if (string.IsNullOrWhiteSpace(value)) throw new Exceptions.InvalidIdException(this);

Value = value;
}
Expand All @@ -13,7 +12,8 @@ protected AggregateId(string value) {

public sealed override string ToString() => Value;

public static implicit operator string(AggregateId id) => id.ToString();
public static implicit operator string(AggregateId? id)
=> id?.ToString() ?? throw new Exceptions.InvalidIdException(typeof(AggregateId));

public void Deconstruct(out string value) => value = Value;
}
}
3 changes: 3 additions & 0 deletions src/Core/src/Eventuous/AppService/ApplicationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,9 @@ public async Task<Result<TState, TId>> Handle(object command, CancellationToken
.Handler(aggregate!, command, cancellationToken)
.NoContext();

// Zero in the global position would mean nothing, so the receiver need to check the Changes.Length
if (result.Changes.Count == 0) return new OkResult<TState, TId>(result.State, Array.Empty<Change>(), 0);

var storeResult = await Store.Store(
streamName != default ? streamName : GetAggregateStreamName(result),
result,
Expand Down
12 changes: 8 additions & 4 deletions src/Core/src/Eventuous/Exceptions/Exceptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@ namespace Eventuous;

public static class Exceptions {
public class InvalidIdException : Exception {
public InvalidIdException(AggregateId id) : base(
$"Aggregate id {id.GetType().Name} cannot have an empty value"
) { }
public InvalidIdException(AggregateId id) : this(id.GetType()) { }

public InvalidIdException(Type idType) : base($"Aggregate id {idType.Name} cannot have an empty value") { }
}

public class InvalidIdException<T> : InvalidIdException where T : AggregateId {
public InvalidIdException() : base(typeof(T)) { }
}

public class CommandHandlerNotFound : Exception {
Expand All @@ -26,4 +30,4 @@ public CommandHandlerAlreadyRegistered() : base(
$"Command handler for ${typeof(T).Name} already registered"
) { }
}
}
}
36 changes: 36 additions & 0 deletions src/Core/test/Eventuous.Tests/ForgotToSetId.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using Eventuous.Tests.Fixtures;

namespace Eventuous.Tests;

public class ForgotToSetId : NaiveFixture {
public ForgotToSetId() => Service = new TestService(this.AggregateStore);

[Fact]
public async Task ShouldFailWithNoId() {
var cmd = new DoIt(Auto.Create<string>());
var result = await Service.Handle(cmd, default);
result.Success.Should().BeFalse();
(result as ErrorResult<TestState, TestId>)!.Exception.Should().BeOfType<Exceptions.InvalidIdException>();
}

TestService Service { get; }

class TestService : ApplicationService<TestAggregate, TestState, TestId> {
public TestService(IAggregateStore store) : base(store)
=> OnNew<DoIt>((test, cmd) => test.DoIt(new TestId(cmd.Id)));
}

record DoIt(string Id);

class TestAggregate : Aggregate<TestState, TestId> {
public void DoIt(TestId id) => Apply(new TestEvent(id));
}

record TestState : AggregateState<TestState, TestId>;

record TestId : AggregateId {
public TestId(string value) : base(value) { }
}

record TestEvent(string Id);
}

0 comments on commit 074fb9a

Please sign in to comment.