Permalink
Browse files

CommonDomain brought in from joliver/CommonDomain - Initial stab at #261



This is an initial import of CommonDomain into NEventStore/CommonDomain.
The goal was to minimize breaking changes for existing CD users who aren't
quite ready to string all the things.

Outstanding issues / work / request for feedback:

- EventStoreRepository tracks opened snapshots and streams in a local
  dictionary. The key was a guid and came from the IAggregate.Id. It's now
  a string of bucketId and IAggregate.Id. This seems really naive. Since
  IAggregate.Id is still a Guid, this could stay as a Guid as well. Or,
  some sort of Value Object wrapper around bucketId and IAggregate.Id
  might be in order.

_ IRepository allows for loading / saving with or without a bucket

- The SagaEventStoreRepository is untouched. I haven't used CD for saga
  work so I'm unsure if adding bucketId as a saga parameter is something
  people want. I assume so, but I didn't want to add it without some
  validation

- I believe I applied the NES code style formatting to all files.
  • Loading branch information...
1 parent 05efa73 commit 9fdf647ac8a78069f3e479c6c020425ec136cfe8 @hyrmn hyrmn committed Sep 30, 2013
Showing with 1,531 additions and 0 deletions.
  1. +58 −0 src/NEventStore.Tests/CommonDomain/IAggregateTests.cs
  2. +18 −0 src/NEventStore.Tests/CommonDomain/Persistence/IAggregatePersistenceTestHelpers.cs
  3. +142 −0 src/NEventStore.Tests/CommonDomain/Persistence/IAggregatePersistenceTests.cs
  4. +54 −0 src/NEventStore.Tests/CommonDomain/TestAggregate.cs
  5. +4 −0 src/NEventStore.Tests/NEventStore.Tests.csproj
  6. +104 −0 src/NEventStore/CommonDomain/Core/AggregateBase.cs
  7. +58 −0 src/NEventStore/CommonDomain/Core/ConflictDetector.cs
  8. +88 −0 src/NEventStore/CommonDomain/Core/ConventionEventRouter.cs
  9. +21 −0 src/NEventStore/CommonDomain/Core/ExtensionMethods.cs
  10. +23 −0 src/NEventStore/CommonDomain/Core/HandlerForDomainEventNotFoundException.cs
  11. +39 −0 src/NEventStore/CommonDomain/Core/RegistrationEventRouter.cs
  12. +73 −0 src/NEventStore/CommonDomain/Core/SagaBase.cs
  13. +17 −0 src/NEventStore/CommonDomain/IAggregate.cs
  14. +13 −0 src/NEventStore/CommonDomain/IDetectConflicts.cs
  15. +11 −0 src/NEventStore/CommonDomain/IMemento.cs
  16. +13 −0 src/NEventStore/CommonDomain/IRouteEvents.cs
  17. +22 −0 src/NEventStore/CommonDomain/ISaga.cs
  18. +44 −0 src/NEventStore/CommonDomain/Persistence/ConflictingCommandException.cs
  19. +212 −0 src/NEventStore/CommonDomain/Persistence/EventStore/EventStoreRepository.cs
  20. +90 −0 src/NEventStore/CommonDomain/Persistence/EventStore/ExceptionMessages.Designer.cs
  21. +129 −0 src/NEventStore/CommonDomain/Persistence/EventStore/ExceptionMessages.resx
  22. +158 −0 src/NEventStore/CommonDomain/Persistence/EventStore/SagaEventStoreRepository.cs
  23. +9 −0 src/NEventStore/CommonDomain/Persistence/IConstructAggregates.cs
  24. +20 −0 src/NEventStore/CommonDomain/Persistence/IRepository.cs
  25. +12 −0 src/NEventStore/CommonDomain/Persistence/ISagaRepository.cs
  26. +44 −0 src/NEventStore/CommonDomain/Persistence/PersistenceException.cs
  27. +17 −0 src/NEventStore/CommonDomain/Persistence/RepositoryExtensions.cs
  28. +14 −0 src/NEventStore/CommonDomain/StringExtensions.cs
  29. +24 −0 src/NEventStore/NEventStore.csproj
@@ -0,0 +1,58 @@
+namespace CommonDomain
+{
+ using System;
+
+ using NEventStore.Persistence.AcceptanceTests.BDD;
+
+ using Xunit;
+ using Xunit.Should;
+
+ public class when_an_aggregate_is_created : SpecificationBase
+ {
+ private TestAggregate _testAggregate;
+
+ protected override void Because()
+ {
+ this._testAggregate = new TestAggregate(Guid.NewGuid(), "Test");
+ }
+
+ [Fact]
+ public void should_have_name()
+ {
+ this._testAggregate.Name.ShouldBe("Test");
+ }
+
+ [Fact]
+ public void aggregate_version_should_be_one()
+ {
+ this._testAggregate.Version.ShouldBe(1);
+ }
+ }
+
+ public class when_updating_an_aggregate : SpecificationBase
+ {
+ private TestAggregate _testAggregate;
+
+ protected override void Context()
+ {
+ this._testAggregate = new TestAggregate(Guid.NewGuid(), "Test");
+ }
+
+ protected override void Because()
+ {
+ _testAggregate.ChangeName("UpdatedTest");
+ }
+
+ [Fact]
+ public void name_change_should_be_applied()
+ {
+ this._testAggregate.Name.ShouldBe("UpdatedTest");
+ }
+
+ [Fact]
+ public void applying_events_automatically_increments_version()
+ {
+ this._testAggregate.Version.ShouldBe(2);
+ }
+ }
+}
@@ -0,0 +1,18 @@
+namespace CommonDomain
+{
+ using System;
+ using System.Reflection;
+
+ using CommonDomain.Persistence;
+
+ internal class AggregateFactory : IConstructAggregates
+ {
+ public IAggregate Build(Type type, Guid id, IMemento snapshot)
+ {
+ ConstructorInfo constructor = type.GetConstructor(
+ BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof(Guid) }, null);
+
+ return constructor.Invoke(new object[] { id }) as IAggregate;
+ }
+ }
+}
@@ -0,0 +1,142 @@
+namespace CommonDomain
+{
+ using System;
+
+ using NEventStore;
+ using NEventStore.Persistence.AcceptanceTests.BDD;
+
+ using Xunit;
+ using Xunit.Should;
+
+ using global::CommonDomain.Core;
+ using global::CommonDomain.Persistence;
+ using global::CommonDomain.Persistence.EventStore;
+
+ public class using_a_configured_repository : SpecificationBase
+ {
+ protected IRepository _repository;
+
+ protected IStoreEvents _storeEvents;
+
+ protected override void Context()
+ {
+ this._storeEvents = Wireup.Init().UsingInMemoryPersistence().Build();
+ this._repository = new EventStoreRepository(this._storeEvents, new AggregateFactory(), new ConflictDetector());
+ }
+ }
+
+ public class when_an_aggregate_is_persisted : using_a_configured_repository
+ {
+ private TestAggregate _testAggregate;
+
+ private Guid _id;
+
+ protected override void Context()
+ {
+ base.Context();
+ _id = Guid.NewGuid();
+ _testAggregate = new TestAggregate(_id, "Test");
+ }
+
+ protected override void Because()
+ {
+ _repository.Save(_testAggregate, Guid.NewGuid(), null);
+ }
+
+ [Fact]
+ public void should_be_returned_when_loaded_by_id()
+ {
+ _repository.GetById<TestAggregate>(_id).Name.ShouldBe(_testAggregate.Name);
+ }
+ }
+
+ public class when_a_persisted_aggregate_is_updated : using_a_configured_repository
+ {
+ private Guid _id;
+
+ private const string NewName = "UpdatedName";
+
+ protected override void Context()
+ {
+ base.Context();
+ _id = Guid.NewGuid();
+ _repository.Save(new TestAggregate(_id, "Test"), Guid.NewGuid(), null);
+ }
+
+ protected override void Because()
+ {
+ var aggregate = _repository.GetById<TestAggregate>(_id);
+ aggregate.ChangeName(NewName);
+ _repository.Save(aggregate,Guid.NewGuid(), null);
+ }
+
+ [Fact]
+ public void should_have_updated_name()
+ {
+ _repository.GetById<TestAggregate>(_id).Name.ShouldBe(NewName);
+ }
+
+ [Fact]
+ public void should_have_updated_version()
+ {
+ _repository.GetById<TestAggregate>(_id).Version.ShouldBe(2);
+ }
+ }
+
+ public class when_a_loading_a_specific_aggregate_version : using_a_configured_repository
+ {
+ private Guid _id;
+
+ private const string VersionOneName = "Test";
+ private const string NewName = "UpdatedName";
+
+ protected override void Context()
+ {
+ base.Context();
+ _id = Guid.NewGuid();
+ _repository.Save(new TestAggregate(_id, VersionOneName), Guid.NewGuid(), null);
+ }
+
+ protected override void Because()
+ {
+ var aggregate = _repository.GetById<TestAggregate>(_id);
+ aggregate.ChangeName(NewName);
+ _repository.Save(aggregate, Guid.NewGuid(), null);
+ _repository.Dispose();
+ }
+
+ [Fact]
+ public void should_be_able_to_load_initial_version()
+ {
+ _repository.GetById<TestAggregate>(_id, 1).Name.ShouldBe(VersionOneName);
+ }
+ }
+
+ public class when_an_aggregate_is_persisted_to_specific_bucket : using_a_configured_repository
+ {
+ private TestAggregate _testAggregate;
+
+ private Guid _id;
+
+ private string _bucket;
+
+ protected override void Context()
+ {
+ base.Context();
+ _id = Guid.NewGuid();
+ _bucket = "TenantB";
+ _testAggregate = new TestAggregate(_id, "Test");
+ }
+
+ protected override void Because()
+ {
+ _repository.Save(_bucket, _testAggregate, Guid.NewGuid(), null);
+ }
+
+ [Fact]
+ public void should_be_returned_when_loaded_by_id()
+ {
+ _repository.GetById<TestAggregate>(_bucket, _id).Name.ShouldBe(_testAggregate.Name);
+ }
+ }
+}
@@ -0,0 +1,54 @@
+namespace CommonDomain
+{
+ using System;
+
+ using global::CommonDomain.Core;
+
+ internal class TestAggregate : AggregateBase
+ {
+ private TestAggregate(Guid id)
+ {
+ this.Id = id;
+ }
+
+ public TestAggregate(Guid id, string name)
+ : this(id)
+ {
+ this.RaiseEvent(new TestAggregateCreatedEvent { Id = this.Id, Name = name });
+ }
+
+ public string Name { get; set; }
+
+ public void ChangeName(string newName)
+ {
+ this.RaiseEvent(new NameChangedEvent { Name = newName });
+ }
+
+ private void Apply(TestAggregateCreatedEvent @event)
+ {
+ this.Name = @event.Name;
+ }
+
+ private void Apply(NameChangedEvent @event)
+ {
+ this.Name = @event.Name;
+ }
+ }
+
+ public interface IDomainEvent
+ {}
+
+ [Serializable]
+ public class NameChangedEvent : IDomainEvent
+ {
+ public string Name { get; set; }
+ }
+
+ [Serializable]
+ public class TestAggregateCreatedEvent : IDomainEvent
+ {
+ public Guid Id { get; set; }
+
+ public string Name { get; set; }
+ }
+}
@@ -72,6 +72,10 @@
</Compile>
<Compile Include="Client\PollingClientTests.cs" />
<Compile Include="CommitHelper.cs" />
+ <Compile Include="CommonDomain\IAggregateTests.cs" />
+ <Compile Include="CommonDomain\Persistence\IAggregatePersistenceTestHelpers.cs" />
+ <Compile Include="CommonDomain\Persistence\IAggregatePersistenceTests.cs" />
+ <Compile Include="CommonDomain\TestAggregate.cs" />
<Compile Include="ConversionTests\EventUpconverterPipelineHookTests.cs" />
<Compile Include="DispatchCommitHookTests.cs" />
<Compile Include="DispatcherTests\AsynchronousDispatcherTests.cs" />
@@ -0,0 +1,104 @@
+namespace CommonDomain.Core
+{
+ using System;
+ using System.Collections;
+ using System.Collections.Generic;
+
+ public abstract class AggregateBase : IAggregate, IEquatable<IAggregate>
+ {
+ private readonly ICollection<object> uncommittedEvents = new LinkedList<object>();
+
+ private IRouteEvents registeredRoutes;
+
+ protected AggregateBase()
+ : this(null)
+ {}
+
+ protected AggregateBase(IRouteEvents handler)
+ {
+ if (handler == null)
+ {
+ return;
+ }
+
+ this.RegisteredRoutes = handler;
+ this.RegisteredRoutes.Register(this);
+ }
+
+ protected IRouteEvents RegisteredRoutes
+ {
+ get
+ {
+ return this.registeredRoutes ?? (this.registeredRoutes = new ConventionEventRouter(true, this));
+ }
+ set
+ {
+ if (value == null)
+ {
+ throw new InvalidOperationException("AggregateBase must have an event router to function");
+ }
+
+ this.registeredRoutes = value;
+ }
+ }
+
+ public Guid Id { get; protected set; }
+
+ public int Version { get; protected set; }
+
+ void IAggregate.ApplyEvent(object @event)
+ {
+ this.RegisteredRoutes.Dispatch(@event);
+ this.Version++;
+ }
+
+ ICollection IAggregate.GetUncommittedEvents()
+ {
+ return (ICollection)this.uncommittedEvents;
+ }
+
+ void IAggregate.ClearUncommittedEvents()
+ {
+ this.uncommittedEvents.Clear();
+ }
+
+ IMemento IAggregate.GetSnapshot()
+ {
+ IMemento snapshot = this.GetSnapshot();
+ snapshot.Id = this.Id;
+ snapshot.Version = this.Version;
+ return snapshot;
+ }
+
+ public virtual bool Equals(IAggregate other)
+ {
+ return null != other && other.Id == this.Id;
+ }
+
+ protected void Register<T>(Action<T> route)
+ {
+ this.RegisteredRoutes.Register(route);
+ }
+
+ protected void RaiseEvent(object @event)
+ {
+ ((IAggregate)this).ApplyEvent(@event);
+ this.uncommittedEvents.Add(@event);
+ }
+
+ protected virtual IMemento GetSnapshot()
+ {
+ return null;
+ }
+
+ public override int GetHashCode()
+ {
+ return this.Id.GetHashCode();
+ }
+
+ public override bool Equals(object obj)
+ {
+ return this.Equals(obj as IAggregate);
+ }
+ }
+}
Oops, something went wrong.

0 comments on commit 9fdf647

Please sign in to comment.