From 6386977ef54dd0389a9c99e88dd00dc544d5fe7d Mon Sep 17 00:00:00 2001 From: Rasmus Mikkelsen Date: Wed, 7 Oct 2015 19:45:59 +0200 Subject: [PATCH 01/16] Set logo as link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 89512e9c8..3381336f2 100644 --- a/README.md +++ b/README.md @@ -158,7 +158,7 @@ several areas that you could help out with. ## Thanks -![ReSharper](./Documentation/Images/logo_resharper.png) +[![ReSharper](./Documentation/Images/logo_resharper.png)](https://www.jetbrains.com/resharper/) ## License From 49fc0800077e4964c335fcc6ccb7a3dfd9f231ec Mon Sep 17 00:00:00 2001 From: Rasmus Mikkelsen Date: Wed, 7 Oct 2015 21:21:19 +0200 Subject: [PATCH 02/16] Version is now 0.19 --- RELEASE_NOTES.md | 6 +++++- appveyor.yml | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index d90ce2eca..8a80458e8 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,4 +1,8 @@ -### New in 0.18 (not released yet) +### New in 0.19 (not released yet) + +* _Nothing yet_ + +### New in 0.18.1181 (released 2015-10-07) * POTENTIAL DATA LOSS for the **files event store**: The EventFlow internal functionality regarding event stores has been refactored resulting diff --git a/appveyor.yml b/appveyor.yml index c99a6e6ac..fd466c61c 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,7 +1,7 @@ init: - git config --global core.autocrlf input -version: 0.18.{build} +version: 0.19.{build} skip_tags: true From 62ee76c2cab8f8edf3adc44f9e38622e58864ae3 Mon Sep 17 00:00:00 2001 From: Rasmus Mikkelsen Date: Sat, 10 Oct 2015 20:31:05 +0200 Subject: [PATCH 03/16] Added information on how to configure EventFlow --- Documentation/Configuration.md | 105 ++++++++++++++++++ RELEASE_NOTES.md | 3 +- .../EventFlowOptionsDefaultExtensions.cs | 1 + 3 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 Documentation/Configuration.md diff --git a/Documentation/Configuration.md b/Documentation/Configuration.md new file mode 100644 index 000000000..83a76b73b --- /dev/null +++ b/Documentation/Configuration.md @@ -0,0 +1,105 @@ +# Configure EventFlow + +To get EventFlow up and running, you need an instance of `IEventFlowOptions` +which is created using the `EventFlowOptions.New` static property. From here +you use extension methods and extension methods to setup EventFlow to your +needs. + +## Basic example + +Here's the very minimum that you will need to get EventFlow up and running +with a in-memory configuration. + +```csharp +using (var resolver = EventFlowOptions.New + .AddDefaults(typeof(YouApplication).Assembly) + .CreateResolver()) +{ + // Do stuff + + // Don't dispose the 'resolver' until application shutdown! +} +``` + +First the default types and services are registered from your application using +the `AddDefaults(...)` method. + +Versioned types, types that might exist in multiple versions as the application +evolves, are loaded into EventFlow. + +* [Event types](./ValueObjects.md) are loaded into the `IEventDefinitionService` +* [Command types](./Commands.md) are loaded into the `ICommandDefinitionService` +* [Job types](./Jobs.md) are loaded into the `IJobDefinitionService` + +Services with known interfaces are registered in the built-in IoC container. + +* Command handlers, i.e., `ICommandHandler<,,>` implementations +* Meta data providers, i.e., `IMetadataProvider` implementations +* Subscribers, i.e., `ISubscribeSynchronousTo<,,>` and `ISubscribeSynchronousToAll` implementations +* Event upgraders, i.e., `IEventUpgrader<,>` implementations +* Query handlers, i.e., `IQueryHandler<,>` implementations + +Note that you can use another IoC container, e.g. Autofac using the +`EventFlow.Autofac` NuGet package. If using this package, you can even skip +the `CreateResolver()` call, as EventFlow will automatically configure itself +when the container is ready. + +The final step of configuring EventFlow, is to call `CreateResolver()` which +configures the IoC container and its through this that you can access e.g. +the `ICommandBus` which allows you to publish commands. + +## Configuration methods + +Note that almost every single one of the configuration methods on +`IEventFlowOptions` is implemented as extension methods and you will need +to add the appropriate namespace, e.g. `EventFlow.Extensions`. + +### Versioned types + +* `AddEvents` +* `AddCommands` +* `AddJobs` + +### Services + +**General** +* `RegisterServices`, configure other services or override existing +* `UseServiceRegistration`, use an alternative IoC container +* `UseAutofacContainerBuilder` in NuGet package `EventFlow.Autofac` +* `RegisterModule`, register modules + +**Aggregates** +* `UseResolverAggregateRootFactory` +* `UseAutofacAggregateRootFactory` in the NuGet package `EventFlow.Autofac` + +* `AddAggregateRoots`, add aggregates if using the + `UseResolverAggregateRootFactory` or `UseAutofacAggregateRootFactory` +**Command handlers** +* `AddCommandHandlers` + +**Subscribers** +* `AddSubscribers` + +**Event upgraders** +* `AddEventUpgraders` + +**Metadata providers** +* `AddMetadataProviders` + +**Event stores** +* `UseEventStore` +* `UseFilesEventStore` +* `UseEventStoreEventStore` in NuGet package `EventFlow.EventStores.EventStore` +* `UseMssqlEventStore` in NuGet package `EventFlow.EventStores.MsSql` + +**Read models and stores** +* `UseReadStoreFor` +* `UseReadStoreFor>` +* `UseInMemoryReadStoreFor` +* `UseInMemoryReadStoreFor` + +**Jobs and schedulers** +* `UseHangfireJobScheduler` in the NuGet package `EventFlow.Hangfire` + +**Queries and handlers** +* `AddQueryHandlers` diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 8a80458e8..ec31909c1 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,6 +1,7 @@ ### New in 0.19 (not released yet) -* _Nothing yet_ +* Breaking: `AddDefaults` now also adds the job type definition to the + `IJobsDefinitonService` ### New in 0.18.1181 (released 2015-10-07) diff --git a/Source/EventFlow/Extensions/EventFlowOptionsDefaultExtensions.cs b/Source/EventFlow/Extensions/EventFlowOptionsDefaultExtensions.cs index 161f90895..1d940a24a 100644 --- a/Source/EventFlow/Extensions/EventFlowOptionsDefaultExtensions.cs +++ b/Source/EventFlow/Extensions/EventFlowOptionsDefaultExtensions.cs @@ -34,6 +34,7 @@ public static IEventFlowOptions AddDefaults( { return eventFlowOptions .AddEvents(fromAssembly, predicate) + .AddJobs(fromAssembly, predicate) .AddCommands(fromAssembly, predicate) .AddCommandHandlers(fromAssembly, predicate) .AddMetadataProviders(fromAssembly, predicate) From bab914420fde0d957f13ffd621847661bd76a80e Mon Sep 17 00:00:00 2001 From: Rasmus Mikkelsen Date: Sat, 10 Oct 2015 23:57:30 +0200 Subject: [PATCH 04/16] We should not throw exceptions when adding types we already know about --- .../EventStores/EventDefinitionServiceTests.cs | 10 ++++++++++ .../VersionedTypes/VersionedTypeDefinitionService.cs | 12 +++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/Source/EventFlow.Tests/UnitTests/EventStores/EventDefinitionServiceTests.cs b/Source/EventFlow.Tests/UnitTests/EventStores/EventDefinitionServiceTests.cs index 0b7401a49..64cf250c7 100644 --- a/Source/EventFlow.Tests/UnitTests/EventStores/EventDefinitionServiceTests.cs +++ b/Source/EventFlow.Tests/UnitTests/EventStores/EventDefinitionServiceTests.cs @@ -81,6 +81,16 @@ public void LoadEventsFollowedByGetEventDefinition_ReturnsCorrectAnswer(string e eventDefinition.Type.Should().Be(expectedEventType); } + [Test] + public void CanLoadSameEventMultipleTimes() + { + Assert.DoesNotThrow(() => + { + Sut.LoadEvents(new[] { typeof(TestEvent), typeof(TestEvent) }); + Sut.LoadEvents(new[] { typeof(TestEvent) }); + }); + } + [Test] public void CanLoadNull() { diff --git a/Source/EventFlow/Core/VersionedTypes/VersionedTypeDefinitionService.cs b/Source/EventFlow/Core/VersionedTypes/VersionedTypeDefinitionService.cs index 4de2372a9..e54b7817b 100644 --- a/Source/EventFlow/Core/VersionedTypes/VersionedTypeDefinitionService.cs +++ b/Source/EventFlow/Core/VersionedTypes/VersionedTypeDefinitionService.cs @@ -79,7 +79,17 @@ protected void Load(IEnumerable types) foreach (var definition in definitions) { var key = GetKey(definition.Name, definition.Version); - _definitionsByName.Add(key, definition); + if (!_definitionsByName.ContainsKey(key)) + { + _definitionsByName.Add(key, definition); + } + else + { + _log.Information( + "Alreadt loaded versioned type '{0}' v{1}, skipping it", + definition.Name, + definition.Version); + } } } From 7e9e92f18227a3fa1cb3287b9f48bee9c472e533 Mon Sep 17 00:00:00 2001 From: Rasmus Mikkelsen Date: Sat, 10 Oct 2015 23:57:44 +0200 Subject: [PATCH 05/16] Version type definition really should be a value type --- .../Core/VersionedTypes/VersionedTypeDefinition.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Source/EventFlow/Core/VersionedTypes/VersionedTypeDefinition.cs b/Source/EventFlow/Core/VersionedTypes/VersionedTypeDefinition.cs index 2e1dd3c09..49d4a80c6 100644 --- a/Source/EventFlow/Core/VersionedTypes/VersionedTypeDefinition.cs +++ b/Source/EventFlow/Core/VersionedTypes/VersionedTypeDefinition.cs @@ -21,10 +21,12 @@ // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. using System; +using System.Collections.Generic; +using EventFlow.ValueObjects; namespace EventFlow.Core.VersionedTypes { - public abstract class VersionedTypeDefinition + public abstract class VersionedTypeDefinition : ValueObject { public int Version { get; } public Type Type { get; } @@ -45,5 +47,12 @@ public override string ToString() var assemblyName = Type.Assembly.GetName(); return $"{Name} v{Version} ({assemblyName.Name} - {Type.Name})"; } + + protected override IEnumerable GetEqualityComponents() + { + yield return Version; + yield return Type; + yield return Name; + } } -} +} \ No newline at end of file From cd154e27eda08363b9960a5a2c7b226ffc7510f8 Mon Sep 17 00:00:00 2001 From: Rasmus Mikkelsen Date: Sun, 11 Oct 2015 00:00:32 +0200 Subject: [PATCH 06/16] Updated release notes --- RELEASE_NOTES.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index ec31909c1..eaac73884 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -2,6 +2,10 @@ * Breaking: `AddDefaults` now also adds the job type definition to the `IJobsDefinitonService` +* Fixed: `IEventDefinitionService`, `ICommandDefinitonService` and + `IJobsDefinitonService` now longer throw an exception if an existing + event is loaded, i.e., multiple calls to `AddEvents(...)`, `AddCommand(...)` + and `AddJobs(...)` no longer throws an exception ### New in 0.18.1181 (released 2015-10-07) From 664567c0f2d3be11348c8179bd5f5676817511b8 Mon Sep 17 00:00:00 2001 From: Rasmus Mikkelsen Date: Fri, 16 Oct 2015 06:47:47 +0200 Subject: [PATCH 07/16] Change poor argument name "e" to "domainEvent" --- .../ReadModels/MsSqlTestAggregateReadModel.cs | 4 ++-- .../Test/ReadModels/InMemoryTestAggregateReadModel.cs | 4 ++-- Source/EventFlow.Tests/IntegrationTests/DomainTests.cs | 4 ++-- .../ReadStores/ReadModelDomainEventApplierTests.cs | 6 +++--- .../UnitTests/ReadStores/ReadModelPopulatorTests.cs | 2 +- .../UnitTests/ReadStores/ReadStoreManagerTests.cs | 2 +- Source/EventFlow/ReadStores/IAmReadModelFor.cs | 2 +- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Source/EventFlow.MsSql.Tests/ReadModels/MsSqlTestAggregateReadModel.cs b/Source/EventFlow.MsSql.Tests/ReadModels/MsSqlTestAggregateReadModel.cs index b82092d42..8b1a50feb 100644 --- a/Source/EventFlow.MsSql.Tests/ReadModels/MsSqlTestAggregateReadModel.cs +++ b/Source/EventFlow.MsSql.Tests/ReadModels/MsSqlTestAggregateReadModel.cs @@ -36,12 +36,12 @@ public class MsSqlTestAggregateReadModel : MssqlReadModel, ITestAggregateReadMod public bool DomainErrorAfterFirstReceived { get; set; } public int PingsReceived { get; set; } - public void Apply(IReadModelContext context, IDomainEvent e) + public void Apply(IReadModelContext context, IDomainEvent domainEvent) { PingsReceived++; } - public void Apply(IReadModelContext context, IDomainEvent e) + public void Apply(IReadModelContext context, IDomainEvent domainEvent) { DomainErrorAfterFirstReceived = true; } diff --git a/Source/EventFlow.TestHelpers/Aggregates/Test/ReadModels/InMemoryTestAggregateReadModel.cs b/Source/EventFlow.TestHelpers/Aggregates/Test/ReadModels/InMemoryTestAggregateReadModel.cs index 9b55d46a8..453fca8d6 100644 --- a/Source/EventFlow.TestHelpers/Aggregates/Test/ReadModels/InMemoryTestAggregateReadModel.cs +++ b/Source/EventFlow.TestHelpers/Aggregates/Test/ReadModels/InMemoryTestAggregateReadModel.cs @@ -31,12 +31,12 @@ public class InMemoryTestAggregateReadModel : IReadModel, ITestAggregateReadMode public bool DomainErrorAfterFirstReceived { get; private set; } public int PingsReceived { get; private set; } - public void Apply(IReadModelContext context, IDomainEvent e) + public void Apply(IReadModelContext context, IDomainEvent domainEvent) { DomainErrorAfterFirstReceived = true; } - public void Apply(IReadModelContext context, IDomainEvent e) + public void Apply(IReadModelContext context, IDomainEvent domainEvent) { PingsReceived++; } diff --git a/Source/EventFlow.Tests/IntegrationTests/DomainTests.cs b/Source/EventFlow.Tests/IntegrationTests/DomainTests.cs index 6fe7cb53e..7fcaa8e78 100644 --- a/Source/EventFlow.Tests/IntegrationTests/DomainTests.cs +++ b/Source/EventFlow.Tests/IntegrationTests/DomainTests.cs @@ -61,9 +61,9 @@ public class PingReadModel : { public PingId Id { get; private set; } - public void Apply(IReadModelContext context, IDomainEvent e) + public void Apply(IReadModelContext context, IDomainEvent domainEvent) { - Id = e.AggregateEvent.PingId; + Id = domainEvent.AggregateEvent.PingId; } } diff --git a/Source/EventFlow.Tests/UnitTests/ReadStores/ReadModelDomainEventApplierTests.cs b/Source/EventFlow.Tests/UnitTests/ReadStores/ReadModelDomainEventApplierTests.cs index ae1f09acb..8065f3f3b 100644 --- a/Source/EventFlow.Tests/UnitTests/ReadStores/ReadModelDomainEventApplierTests.cs +++ b/Source/EventFlow.Tests/UnitTests/ReadStores/ReadModelDomainEventApplierTests.cs @@ -39,7 +39,7 @@ public class PingReadModel : IReadModel, { public bool PingEventsReceived { get; private set; } - public void Apply(IReadModelContext context, IDomainEvent e) + public void Apply(IReadModelContext context, IDomainEvent domainEvent) { PingEventsReceived = true; } @@ -50,7 +50,7 @@ public class TheOtherPingReadModel : IReadModel, { public bool PingEventsReceived { get; private set; } - public void Apply(IReadModelContext context, IDomainEvent e) + public void Apply(IReadModelContext context, IDomainEvent domainEvent) { PingEventsReceived = true; } @@ -61,7 +61,7 @@ public class DomainErrorAfterFirstReadModel : IReadModel, { public bool DomainErrorAfterFirstEventsReceived { get; private set; } - public void Apply(IReadModelContext context, IDomainEvent e) + public void Apply(IReadModelContext context, IDomainEvent domainEvent) { DomainErrorAfterFirstEventsReceived = true; } diff --git a/Source/EventFlow.Tests/UnitTests/ReadStores/ReadModelPopulatorTests.cs b/Source/EventFlow.Tests/UnitTests/ReadStores/ReadModelPopulatorTests.cs index 723ed5670..9e75e323e 100644 --- a/Source/EventFlow.Tests/UnitTests/ReadStores/ReadModelPopulatorTests.cs +++ b/Source/EventFlow.Tests/UnitTests/ReadStores/ReadModelPopulatorTests.cs @@ -43,7 +43,7 @@ public class ReadModelPopulatorTests : TestsFor public class TestReadModel : IReadModel, IAmReadModelFor { - public void Apply(IReadModelContext context, IDomainEvent e) + public void Apply(IReadModelContext context, IDomainEvent domainEvent) { } } diff --git a/Source/EventFlow.Tests/UnitTests/ReadStores/ReadStoreManagerTests.cs b/Source/EventFlow.Tests/UnitTests/ReadStores/ReadStoreManagerTests.cs index d021cb40c..b0e2cc73e 100644 --- a/Source/EventFlow.Tests/UnitTests/ReadStores/ReadStoreManagerTests.cs +++ b/Source/EventFlow.Tests/UnitTests/ReadStores/ReadStoreManagerTests.cs @@ -39,7 +39,7 @@ public class ReadStoreManagerTests : TestsFor { - public void Apply(IReadModelContext context, IDomainEvent e) + public void Apply(IReadModelContext context, IDomainEvent domainEvent) { } } diff --git a/Source/EventFlow/ReadStores/IAmReadModelFor.cs b/Source/EventFlow/ReadStores/IAmReadModelFor.cs index 2ae4fc103..aa6854d0c 100644 --- a/Source/EventFlow/ReadStores/IAmReadModelFor.cs +++ b/Source/EventFlow/ReadStores/IAmReadModelFor.cs @@ -30,6 +30,6 @@ public interface IAmReadModelFor where TIdentity : IIdentity where TEvent : IAggregateEvent { - void Apply(IReadModelContext context, IDomainEvent e); + void Apply(IReadModelContext context, IDomainEvent domainEvent); } } From 52ed0756dc83f0f0b0be6b690dd8eb57114a53fa Mon Sep 17 00:00:00 2001 From: Rasmus Mikkelsen Date: Sat, 17 Oct 2015 20:25:36 +0200 Subject: [PATCH 08/16] Implemented specifications --- Source/EventFlow.Tests/EventFlow.Tests.csproj | 1 + .../Specifications/SpecificationTests.cs | 136 ++++++++++++++++++ Source/EventFlow/Aggregates/AggregateRoot.cs | 5 + Source/EventFlow/Aggregates/IAggregateRoot.cs | 2 + Source/EventFlow/EventFlow.csproj | 7 + .../Extensions/SpeficicationExtensions.cs | 67 +++++++++ .../AggregateIsNewSpecification.cs | 39 +++++ .../Specifications/AndSpeficication.cs | 51 +++++++ .../Specifications/NotSpecification.cs | 50 +++++++ .../Specifications/OrSpecification.cs | 59 ++++++++ .../Specifications/ISpecification.cs | 33 +++++ .../EventFlow/Specifications/Specification.cs | 42 ++++++ 12 files changed, 492 insertions(+) create mode 100644 Source/EventFlow.Tests/UnitTests/Specifications/SpecificationTests.cs create mode 100644 Source/EventFlow/Extensions/SpeficicationExtensions.cs create mode 100644 Source/EventFlow/Provided/Specifications/AggregateIsNewSpecification.cs create mode 100644 Source/EventFlow/Provided/Specifications/AndSpeficication.cs create mode 100644 Source/EventFlow/Provided/Specifications/NotSpecification.cs create mode 100644 Source/EventFlow/Provided/Specifications/OrSpecification.cs create mode 100644 Source/EventFlow/Specifications/ISpecification.cs create mode 100644 Source/EventFlow/Specifications/Specification.cs diff --git a/Source/EventFlow.Tests/EventFlow.Tests.csproj b/Source/EventFlow.Tests/EventFlow.Tests.csproj index b52c1dc7e..7b829063f 100644 --- a/Source/EventFlow.Tests/EventFlow.Tests.csproj +++ b/Source/EventFlow.Tests/EventFlow.Tests.csproj @@ -106,6 +106,7 @@ + diff --git a/Source/EventFlow.Tests/UnitTests/Specifications/SpecificationTests.cs b/Source/EventFlow.Tests/UnitTests/Specifications/SpecificationTests.cs new file mode 100644 index 000000000..237bc5584 --- /dev/null +++ b/Source/EventFlow.Tests/UnitTests/Specifications/SpecificationTests.cs @@ -0,0 +1,136 @@ +// The MIT License (MIT) +// +// Copyright (c) 2015 Rasmus Mikkelsen +// https://github.com/rasmus/EventFlow +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +using System.Collections.Generic; +using EventFlow.Extensions; +using EventFlow.Specifications; +using FluentAssertions; +using NUnit.Framework; + +namespace EventFlow.Tests.UnitTests.Specifications +{ + public class SpecificationTests + { + [Test] + public void ShouldReturnTrueForSatisfiedValue() + { + // Arrange + var isTrue = new IsTrueSpecification(); + + // Act + var isSatisfiedBy = isTrue.IsSatisfiedBy(true); + + // Act + isSatisfiedBy.Should().BeTrue(); + } + + [Test] + public void ShouldReturnFalseForNotSatisfiedValue() + { + // Arrange + var isTrue = new IsTrueSpecification(); + + // Act + var isSatisfiedBy = isTrue.IsSatisfiedBy(false); + + // Act + isSatisfiedBy.Should().BeFalse(); + } + + [Test] + public void NotSpeficication_ReturnsTrue_ForNotSatisfied() + { + // Arrange + var isTrue = new IsTrueSpecification(); + + // Act + var isSatisfiedBy = isTrue.Not().IsSatisfiedBy(false); + + // Act + isSatisfiedBy.Should().BeTrue(); + } + + [Test] + public void NotSpeficication_ReturnsFalse_ForSatisfied() + { + // Arrange + var isTrue = new IsTrueSpecification(); + + // Act + var isSatisfiedBy = isTrue.Not().IsSatisfiedBy(true); + + // Act + isSatisfiedBy.Should().BeFalse(); + } + + [TestCase(true, true, false)] + [TestCase(true, false, true)] + [TestCase(false, true, true)] + [TestCase(false, false, true)] + public void OrSpeficication_ReturnsTrue_Correctly(bool notLeft, bool notRight, bool expectedResult) + { + // Arrange + var leftIsTrue = (ISpecification) new IsTrueSpecification(); + var rightIsTrue = (ISpecification)new IsTrueSpecification(); + if (notLeft) leftIsTrue = leftIsTrue.Not(); + if (notRight) rightIsTrue = rightIsTrue.Not(); + var orSpecification = leftIsTrue.Or(rightIsTrue); + + // Act + var isSatisfiedBy = orSpecification.IsSatisfiedBy(true); + + // Assert + isSatisfiedBy.Should().Be(expectedResult); + } + + [TestCase(true, true, false)] + [TestCase(true, false, false)] + [TestCase(false, true, false)] + [TestCase(false, false, true)] + public void AndSpeficication_ReturnsTrue_Correctly(bool notLeft, bool notRight, bool expectedResult) + { + // Arrange + var leftIsTrue = (ISpecification)new IsTrueSpecification(); + var rightIsTrue = (ISpecification)new IsTrueSpecification(); + if (notLeft) leftIsTrue = leftIsTrue.Not(); + if (notRight) rightIsTrue = rightIsTrue.Not(); + var andSpecification = leftIsTrue.And(rightIsTrue); + + // Act + var isSatisfiedBy = andSpecification.IsSatisfiedBy(true); + + // Assert + isSatisfiedBy.Should().Be(expectedResult); + } + + public class IsTrueSpecification : Specification + { + protected override IEnumerable IsNotStatisfiedBecause(bool obj) + { + if (!obj) + { + yield return "Its false!"; + } + } + } + } +} \ No newline at end of file diff --git a/Source/EventFlow/Aggregates/AggregateRoot.cs b/Source/EventFlow/Aggregates/AggregateRoot.cs index d3038b571..a7a4b9f68 100644 --- a/Source/EventFlow/Aggregates/AggregateRoot.cs +++ b/Source/EventFlow/Aggregates/AggregateRoot.cs @@ -153,6 +153,11 @@ public void ApplyEvents(IReadOnlyCollection domainEvents) Version = domainEvents.Max(e => e.AggregateSequenceNumber); } + public IIdentity GetIdentity() + { + return Id; + } + public void ApplyEvents(IEnumerable aggregateEvents) { if (Version > 0) diff --git a/Source/EventFlow/Aggregates/IAggregateRoot.cs b/Source/EventFlow/Aggregates/IAggregateRoot.cs index a37546125..03759ec04 100644 --- a/Source/EventFlow/Aggregates/IAggregateRoot.cs +++ b/Source/EventFlow/Aggregates/IAggregateRoot.cs @@ -45,6 +45,8 @@ Task> CommitAsync( void ApplyEvents(IEnumerable aggregateEvents); void ApplyEvents(IReadOnlyCollection domainEvents); + + IIdentity GetIdentity(); } public interface IAggregateRoot : IAggregateRoot diff --git a/Source/EventFlow/EventFlow.csproj b/Source/EventFlow/EventFlow.csproj index c178eea7b..ad54dee7a 100644 --- a/Source/EventFlow/EventFlow.csproj +++ b/Source/EventFlow/EventFlow.csproj @@ -169,6 +169,7 @@ + @@ -186,6 +187,10 @@ + + + + @@ -208,6 +213,8 @@ + + diff --git a/Source/EventFlow/Extensions/SpeficicationExtensions.cs b/Source/EventFlow/Extensions/SpeficicationExtensions.cs new file mode 100644 index 000000000..029cc6d87 --- /dev/null +++ b/Source/EventFlow/Extensions/SpeficicationExtensions.cs @@ -0,0 +1,67 @@ +// The MIT License (MIT) +// +// Copyright (c) 2015 Rasmus Mikkelsen +// https://github.com/rasmus/EventFlow +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +using System; +using System.Linq; +using EventFlow.Exceptions; +using EventFlow.Provided.Specifications; +using EventFlow.Specifications; + +namespace EventFlow.Extensions +{ + public static class SpeficicationExtensions + { + public static void ThrowDomainErrorIfNotStatisfied( + this ISpecification specification, + T obj) + { + if (specification == null) throw new ArgumentNullException(nameof(specification)); + + var whyIsNotStatisfiedBy = specification.WhyIsNotStatisfiedBy(obj).ToList(); + if (whyIsNotStatisfiedBy.Any()) + { + throw DomainError.With( + $"'{specification.GetType().PrettyPrint()}' is not satisfied becase of {string.Join(" and ", whyIsNotStatisfiedBy)}"); + } + } + + public static ISpecification And( + this ISpecification specification1, + ISpecification specification2) + { + return new AndSpeficication(specification1, specification2); + } + + public static ISpecification Or( + this ISpecification specification1, + ISpecification specification2) + { + return new OrSpecification(specification1, specification2); + } + + public static ISpecification Not( + this ISpecification specification) + { + return new NotSpecification(specification); + } + } +} \ No newline at end of file diff --git a/Source/EventFlow/Provided/Specifications/AggregateIsNewSpecification.cs b/Source/EventFlow/Provided/Specifications/AggregateIsNewSpecification.cs new file mode 100644 index 000000000..ddb29418b --- /dev/null +++ b/Source/EventFlow/Provided/Specifications/AggregateIsNewSpecification.cs @@ -0,0 +1,39 @@ +// The MIT License (MIT) +// +// Copyright (c) 2015 Rasmus Mikkelsen +// https://github.com/rasmus/EventFlow +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +using System.Collections.Generic; +using EventFlow.Aggregates; +using EventFlow.Specifications; + +namespace EventFlow.Provided.Specifications +{ + public class AggregateIsNewSpecification : Specification + { + protected override IEnumerable IsNotStatisfiedBecause(IAggregateRoot obj) + { + if (!obj.IsNew) + { + yield return $"'{obj.Name}' with ID '{obj.GetIdentity()}' is not new"; + } + } + } +} \ No newline at end of file diff --git a/Source/EventFlow/Provided/Specifications/AndSpeficication.cs b/Source/EventFlow/Provided/Specifications/AndSpeficication.cs new file mode 100644 index 000000000..94ade5951 --- /dev/null +++ b/Source/EventFlow/Provided/Specifications/AndSpeficication.cs @@ -0,0 +1,51 @@ +// The MIT License (MIT) +// +// Copyright (c) 2015 Rasmus Mikkelsen +// https://github.com/rasmus/EventFlow +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Linq; +using EventFlow.Specifications; + +namespace EventFlow.Provided.Specifications +{ + public class AndSpeficication : Specification + { + private readonly ISpecification _specification1; + private readonly ISpecification _specification2; + + public AndSpeficication( + ISpecification specification1, + ISpecification specification2) + { + if (specification1 == null) throw new ArgumentNullException(nameof(specification1)); + if (specification2 == null) throw new ArgumentNullException(nameof(specification2)); + + _specification1 = specification1; + _specification2 = specification2; + } + + protected override IEnumerable IsNotStatisfiedBecause(T obj) + { + return _specification1.WhyIsNotStatisfiedBy(obj).Concat(_specification2.WhyIsNotStatisfiedBy(obj)); + } + } +} \ No newline at end of file diff --git a/Source/EventFlow/Provided/Specifications/NotSpecification.cs b/Source/EventFlow/Provided/Specifications/NotSpecification.cs new file mode 100644 index 000000000..e66be66ba --- /dev/null +++ b/Source/EventFlow/Provided/Specifications/NotSpecification.cs @@ -0,0 +1,50 @@ +// The MIT License (MIT) +// +// Copyright (c) 2015 Rasmus Mikkelsen +// https://github.com/rasmus/EventFlow +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using EventFlow.Extensions; +using EventFlow.Specifications; + +namespace EventFlow.Provided.Specifications +{ + public class NotSpecification : Specification + { + private readonly ISpecification _specification; + + public NotSpecification( + ISpecification specification) + { + if (specification == null) throw new ArgumentNullException(nameof(specification)); + + _specification = specification; + } + + protected override IEnumerable IsNotStatisfiedBecause(T obj) + { + if (_specification.IsSatisfiedBy(obj)) + { + yield return $"Specification '{_specification.GetType().PrettyPrint()}' should not be satisfied"; + } + } + } +} \ No newline at end of file diff --git a/Source/EventFlow/Provided/Specifications/OrSpecification.cs b/Source/EventFlow/Provided/Specifications/OrSpecification.cs new file mode 100644 index 000000000..c88d50297 --- /dev/null +++ b/Source/EventFlow/Provided/Specifications/OrSpecification.cs @@ -0,0 +1,59 @@ +// The MIT License (MIT) +// +// Copyright (c) 2015 Rasmus Mikkelsen +// https://github.com/rasmus/EventFlow +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Linq; +using EventFlow.Specifications; + +namespace EventFlow.Provided.Specifications +{ + public class OrSpecification : Specification + { + private readonly ISpecification _specification1; + private readonly ISpecification _specification2; + + public OrSpecification( + ISpecification specification1, + ISpecification specification2) + { + if (specification1 == null) throw new ArgumentNullException(nameof(specification1)); + if (specification2 == null) throw new ArgumentNullException(nameof(specification2)); + + _specification1 = specification1; + _specification2 = specification2; + } + + protected override IEnumerable IsNotStatisfiedBecause(T obj) + { + var reasons1 = _specification1.WhyIsNotStatisfiedBy(obj).ToList(); + var reasons2 = _specification2.WhyIsNotStatisfiedBy(obj).ToList(); + + if (!reasons1.Any() || !reasons2.Any()) + { + return Enumerable.Empty(); + } + + return reasons1.Concat(reasons2); + } + } +} \ No newline at end of file diff --git a/Source/EventFlow/Specifications/ISpecification.cs b/Source/EventFlow/Specifications/ISpecification.cs new file mode 100644 index 000000000..5312bde92 --- /dev/null +++ b/Source/EventFlow/Specifications/ISpecification.cs @@ -0,0 +1,33 @@ +// The MIT License (MIT) +// +// Copyright (c) 2015 Rasmus Mikkelsen +// https://github.com/rasmus/EventFlow +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +using System.Collections.Generic; + +namespace EventFlow.Specifications +{ + public interface ISpecification + { + bool IsSatisfiedBy(T obj); + + IEnumerable WhyIsNotStatisfiedBy(T obj); + } +} \ No newline at end of file diff --git a/Source/EventFlow/Specifications/Specification.cs b/Source/EventFlow/Specifications/Specification.cs new file mode 100644 index 000000000..5798ed97a --- /dev/null +++ b/Source/EventFlow/Specifications/Specification.cs @@ -0,0 +1,42 @@ +// The MIT License (MIT) +// +// Copyright (c) 2015 Rasmus Mikkelsen +// https://github.com/rasmus/EventFlow +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +using System.Collections.Generic; +using System.Linq; + +namespace EventFlow.Specifications +{ + public abstract class Specification : ISpecification + { + public bool IsSatisfiedBy(T obj) + { + return !IsNotStatisfiedBecause(obj).Any(); + } + + public IEnumerable WhyIsNotStatisfiedBy(T obj) + { + return IsNotStatisfiedBecause(obj); + } + + protected abstract IEnumerable IsNotStatisfiedBecause(T obj); + } +} \ No newline at end of file From 7a9d2718c6dc9231766d662fb68b5f8922a59b2d Mon Sep 17 00:00:00 2001 From: Rasmus Mikkelsen Date: Sat, 17 Oct 2015 20:31:30 +0200 Subject: [PATCH 09/16] Minor fix for DomainError.With --- RELEASE_NOTES.md | 2 ++ Source/EventFlow/Exceptions/DomainError.cs | 20 +++++++++++++++----- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index eaac73884..fee25683c 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -6,6 +6,8 @@ `IJobsDefinitonService` now longer throw an exception if an existing event is loaded, i.e., multiple calls to `AddEvents(...)`, `AddCommand(...)` and `AddJobs(...)` no longer throws an exception +* Fixed: `DomainError.With(...)` no longer executes `string.format` if only + one argument is parsed ### New in 0.18.1181 (released 2015-10-07) diff --git a/Source/EventFlow/Exceptions/DomainError.cs b/Source/EventFlow/Exceptions/DomainError.cs index 96371c8b4..653a75bb4 100644 --- a/Source/EventFlow/Exceptions/DomainError.cs +++ b/Source/EventFlow/Exceptions/DomainError.cs @@ -28,21 +28,31 @@ namespace EventFlow.Exceptions public class DomainError : Exception { protected DomainError(string message) - : base(message) { } + : base(message) + { + } protected DomainError(string message, Exception innerException) - : base(message, innerException) { } + : base(message, innerException) + { + } [StringFormatMethod("format")] public static DomainError With(string format, params object[] args) { - return new DomainError(string.Format(format, args)); + var message = args.Length <= 0 + ? format + : string.Format(format, args); + return new DomainError(message); } [StringFormatMethod("format")] public static DomainError With(Exception innerException, string format, params object[] args) { - return new DomainError(string.Format(format, args), innerException); + var message = args.Length <= 0 + ? format + : string.Format(format, args); + return new DomainError(message, innerException); } } -} +} \ No newline at end of file From e74a7c360c6e5d1315a4744fc84f77e524803beb Mon Sep 17 00:00:00 2001 From: Rasmus Mikkelsen Date: Sat, 17 Oct 2015 20:39:43 +0200 Subject: [PATCH 10/16] Irrelevant for the overview of EventFlow --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 3381336f2..b0c0451fc 100644 --- a/README.md +++ b/README.md @@ -15,9 +15,7 @@ the [dos and don'ts](./Documentation/DoesAndDonts.md) and the ### Features * **CQRS+ES framework** -* **Async/await first:** Every part of EventFlow is written using async/await. In - some places EventFlow exposes sync methods like e.g. the `ICommandBus`, but these - merely _try_ to do the right thing using an async bridge +* **Async/await first:** Every part of EventFlow is written using async/await. * **Highly configurable and extendable** * **Easy to use** * **No use of threads or background workers making it "web friendly"** From b8e582b562190d3314a90fe8f6918a64cc7718f7 Mon Sep 17 00:00:00 2001 From: Rasmus Mikkelsen Date: Sat, 17 Oct 2015 20:42:08 +0200 Subject: [PATCH 11/16] Fixed spelling --- .../Core/VersionedTypes/VersionedTypeDefinitionService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/EventFlow/Core/VersionedTypes/VersionedTypeDefinitionService.cs b/Source/EventFlow/Core/VersionedTypes/VersionedTypeDefinitionService.cs index e54b7817b..661e57b0d 100644 --- a/Source/EventFlow/Core/VersionedTypes/VersionedTypeDefinitionService.cs +++ b/Source/EventFlow/Core/VersionedTypes/VersionedTypeDefinitionService.cs @@ -86,7 +86,7 @@ protected void Load(IEnumerable types) else { _log.Information( - "Alreadt loaded versioned type '{0}' v{1}, skipping it", + "Already loaded versioned type '{0}' v{1}, skipping it", definition.Name, definition.Version); } From 5258ad6c58059436bd9cef56ee4c0944905d2137 Mon Sep 17 00:00:00 2001 From: Rasmus Mikkelsen Date: Sun, 18 Oct 2015 10:56:47 +0200 Subject: [PATCH 12/16] Review comments and a few more specifications --- .../Specifications/SpecificationTests.cs | 67 +++++++++++++++++++ Source/EventFlow/EventFlow.csproj | 4 +- ...tensions.cs => SpecificationExtensions.cs} | 16 ++++- .../Specifications/AllSpecification.cs | 49 ++++++++++++++ .../Specifications/SomeSpecification.cs | 65 ++++++++++++++++++ 5 files changed, 199 insertions(+), 2 deletions(-) rename Source/EventFlow/Extensions/{SpeficicationExtensions.cs => SpecificationExtensions.cs} (82%) create mode 100644 Source/EventFlow/Provided/Specifications/AllSpecification.cs create mode 100644 Source/EventFlow/Provided/Specifications/SomeSpecification.cs diff --git a/Source/EventFlow.Tests/UnitTests/Specifications/SpecificationTests.cs b/Source/EventFlow.Tests/UnitTests/Specifications/SpecificationTests.cs index 237bc5584..7156800d0 100644 --- a/Source/EventFlow.Tests/UnitTests/Specifications/SpecificationTests.cs +++ b/Source/EventFlow.Tests/UnitTests/Specifications/SpecificationTests.cs @@ -122,6 +122,73 @@ public void AndSpeficication_ReturnsTrue_Correctly(bool notLeft, bool notRight, isSatisfiedBy.Should().Be(expectedResult); } + [TestCase(1, 1, false)] + [TestCase(1, 2, true)] + [TestCase(1, 3, true)] + [TestCase(1, 4, true)] + [TestCase(1, 5, true)] + [TestCase(3, 1, false)] + [TestCase(3, 2, false)] + [TestCase(3, 3, false)] + [TestCase(3, 4, true)] + [TestCase(3, 5, true)] + public void AtLeast_Returns_Correctly(int requiredSpecifications, int obj, bool expectedIsSatisfiedBy) + { + // Arrange + var isAbove1 = new IsAboveSpecification(1); + var isAbove2 = new IsAboveSpecification(2); + var isAbove3 = new IsAboveSpecification(3); + var isAbove4 = new IsAboveSpecification(4); + var atLeast = new[] + { + isAbove1, + isAbove2, + isAbove3, + isAbove4 + } + .AtLeast(requiredSpecifications); + + // Act + var isSatisfiedBy = atLeast.IsSatisfiedBy(obj); + + // Assert + isSatisfiedBy.Should().Be(expectedIsSatisfiedBy, string.Join(", ", atLeast.WhyIsNotStatisfiedBy(obj))); + } + + [TestCase(4, 3, false)] + [TestCase(4, 4, false)] + [TestCase(4, 5, true)] + public void IsAbove_Returns_Correct(int limit, int obj, bool expectedIsSatisfiedBy) + { + // Arrange + var isAbove = new IsAboveSpecification(limit); + + // Act + var isSatisfiedBy = isAbove.IsSatisfiedBy(obj); + + // Assert + isSatisfiedBy.Should().Be(expectedIsSatisfiedBy); + } + + public class IsAboveSpecification : Specification + { + private readonly int _limit; + + public IsAboveSpecification( + int limit) + { + _limit = limit; + } + + protected override IEnumerable IsNotStatisfiedBecause(int obj) + { + if (obj <= _limit) + { + yield return $"{obj} is less or equal than {_limit}"; + } + } + } + public class IsTrueSpecification : Specification { protected override IEnumerable IsNotStatisfiedBecause(bool obj) diff --git a/Source/EventFlow/EventFlow.csproj b/Source/EventFlow/EventFlow.csproj index ad54dee7a..df42bf2a5 100644 --- a/Source/EventFlow/EventFlow.csproj +++ b/Source/EventFlow/EventFlow.csproj @@ -169,7 +169,7 @@ - + @@ -191,6 +191,8 @@ + + diff --git a/Source/EventFlow/Extensions/SpeficicationExtensions.cs b/Source/EventFlow/Extensions/SpecificationExtensions.cs similarity index 82% rename from Source/EventFlow/Extensions/SpeficicationExtensions.cs rename to Source/EventFlow/Extensions/SpecificationExtensions.cs index 029cc6d87..ce8971705 100644 --- a/Source/EventFlow/Extensions/SpeficicationExtensions.cs +++ b/Source/EventFlow/Extensions/SpecificationExtensions.cs @@ -21,6 +21,7 @@ // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. using System; +using System.Collections.Generic; using System.Linq; using EventFlow.Exceptions; using EventFlow.Provided.Specifications; @@ -28,7 +29,7 @@ namespace EventFlow.Extensions { - public static class SpeficicationExtensions + public static class SpecificationExtensions { public static void ThrowDomainErrorIfNotStatisfied( this ISpecification specification, @@ -44,6 +45,19 @@ public static void ThrowDomainErrorIfNotStatisfied( } } + public static ISpecification All( + this IEnumerable> specifications) + { + return new AllSpecifications(specifications); + } + + public static ISpecification AtLeast( + this IEnumerable> specifications, + int requiredSpecifications) + { + return new AtLeastSpecification(requiredSpecifications, specifications); + } + public static ISpecification And( this ISpecification specification1, ISpecification specification2) diff --git a/Source/EventFlow/Provided/Specifications/AllSpecification.cs b/Source/EventFlow/Provided/Specifications/AllSpecification.cs new file mode 100644 index 000000000..27c263593 --- /dev/null +++ b/Source/EventFlow/Provided/Specifications/AllSpecification.cs @@ -0,0 +1,49 @@ +// The MIT License (MIT) +// +// Copyright (c) 2015 Rasmus Mikkelsen +// https://github.com/rasmus/EventFlow +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Linq; +using EventFlow.Specifications; + +namespace EventFlow.Provided.Specifications +{ + public class AllSpecifications : Specification + { + private readonly IReadOnlyList> _specifications; + + public AllSpecifications( + IEnumerable> specifications) + { + var specificationList = (specifications ?? Enumerable.Empty>()).ToList(); + + if (!specificationList.Any()) throw new ArgumentException("Please provide some specifications", nameof(specifications)); + + _specifications = specificationList; + } + + protected override IEnumerable IsNotStatisfiedBecause(T obj) + { + return _specifications.SelectMany(s => s.WhyIsNotStatisfiedBy(obj)); + } + } +} \ No newline at end of file diff --git a/Source/EventFlow/Provided/Specifications/SomeSpecification.cs b/Source/EventFlow/Provided/Specifications/SomeSpecification.cs new file mode 100644 index 000000000..1dc80ea99 --- /dev/null +++ b/Source/EventFlow/Provided/Specifications/SomeSpecification.cs @@ -0,0 +1,65 @@ +// The MIT License (MIT) +// +// Copyright (c) 2015 Rasmus Mikkelsen +// https://github.com/rasmus/EventFlow +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Linq; +using EventFlow.Extensions; +using EventFlow.Specifications; + +namespace EventFlow.Provided.Specifications +{ + public class AtLeastSpecification : Specification + { + private readonly int _requiredSpecifications; + private readonly IReadOnlyList> _specifications; + + public AtLeastSpecification( + int requiredSpecifications, + IEnumerable> specifications) + { + _requiredSpecifications = requiredSpecifications; + var specificationList = (specifications ?? Enumerable.Empty>()).ToList(); + + if (requiredSpecifications <= 0) throw new ArgumentOutOfRangeException(nameof(requiredSpecifications)); + if (!specificationList.Any()) throw new ArgumentException( + "Please provide some specifications", nameof(specifications)); + if (requiredSpecifications > specificationList.Count) throw new ArgumentOutOfRangeException( + $"You required '{requiredSpecifications}' to be met, but only '{specificationList.Count}' was supplied"); + + _specifications = specificationList; + } + + protected override IEnumerable IsNotStatisfiedBecause(T obj) + { + var notStatisfiedReasons = _specifications + .Select(s => new {Specification = s, WhyIsNotStatisfied = s.WhyIsNotStatisfiedBy(obj).ToList()}) + .Where(a => a.WhyIsNotStatisfied.Any()) + .Select(a => $"{a.Specification.GetType().PrettyPrint()}: {string.Join(", ", a.WhyIsNotStatisfied)}") + .ToList(); + + return (_specifications.Count - notStatisfiedReasons.Count) >= _requiredSpecifications + ? Enumerable.Empty() + : notStatisfiedReasons; + } + } +} \ No newline at end of file From 3127ed4dd389a3d8f17427e2eeb1bbf669727943 Mon Sep 17 00:00:00 2001 From: Rasmus Mikkelsen Date: Sun, 18 Oct 2015 19:03:32 +0200 Subject: [PATCH 13/16] Fix spelling --- .../UnitTests/Specifications/SpecificationTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/EventFlow.Tests/UnitTests/Specifications/SpecificationTests.cs b/Source/EventFlow.Tests/UnitTests/Specifications/SpecificationTests.cs index 7156800d0..60f3faa42 100644 --- a/Source/EventFlow.Tests/UnitTests/Specifications/SpecificationTests.cs +++ b/Source/EventFlow.Tests/UnitTests/Specifications/SpecificationTests.cs @@ -57,7 +57,7 @@ public void ShouldReturnFalseForNotSatisfiedValue() } [Test] - public void NotSpeficication_ReturnsTrue_ForNotSatisfied() + public void NotSpecification_ReturnsTrue_ForNotSatisfied() { // Arrange var isTrue = new IsTrueSpecification(); From 919bc379334c4633cc47a6453448774c4521a357 Mon Sep 17 00:00:00 2001 From: Rasmus Mikkelsen Date: Sun, 18 Oct 2015 19:09:32 +0200 Subject: [PATCH 14/16] Add a few more tests --- .../Specifications/SpecificationTests.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Source/EventFlow.Tests/UnitTests/Specifications/SpecificationTests.cs b/Source/EventFlow.Tests/UnitTests/Specifications/SpecificationTests.cs index 60f3faa42..2bcce228d 100644 --- a/Source/EventFlow.Tests/UnitTests/Specifications/SpecificationTests.cs +++ b/Source/EventFlow.Tests/UnitTests/Specifications/SpecificationTests.cs @@ -21,6 +21,7 @@ // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. using System.Collections.Generic; +using EventFlow.Exceptions; using EventFlow.Extensions; using EventFlow.Specifications; using FluentAssertions; @@ -170,6 +171,26 @@ public void IsAbove_Returns_Correct(int limit, int obj, bool expectedIsSatisfied isSatisfiedBy.Should().Be(expectedIsSatisfiedBy); } + [Test] + public void ThrowDomainErrorIfNotStatisfied_Throws_IfNotSatisfied() + { + // Arrange + var isTrue = new IsTrueSpecification(); + + // Act + Assert.Throws(() => isTrue.ThrowDomainErrorIfNotStatisfied(false)); + } + + [Test] + public void ThrowDomainErrorIfNotStatisfied_DoesNotThrow_IfStatisfied() + { + // Arrange + var isTrue = new IsTrueSpecification(); + + // Act + Assert.DoesNotThrow(() => isTrue.ThrowDomainErrorIfNotStatisfied(true)); + } + public class IsAboveSpecification : Specification { private readonly int _limit; From 1d0f54aabb0c903cc07b785a474c8d37d41c880c Mon Sep 17 00:00:00 2001 From: Rasmus Mikkelsen Date: Sun, 18 Oct 2015 19:19:35 +0200 Subject: [PATCH 15/16] Cleanup and move stuff around a bit --- Source/EventFlow.Tests/EventFlow.Tests.csproj | 3 + .../AtLeastSpecificationTests.cs | 58 +++++++++ .../Specifications/SpecificationTests.cs | 121 ++---------------- .../Specifications/TestSpecifications.cs | 60 +++++++++ .../Specifications/TestSpecificationsTests.cs | 71 ++++++++++ Source/EventFlow/EventFlow.csproj | 2 +- .../Extensions/SpecificationExtensions.cs | 2 +- ...ecification.cs => AtLeastSpecification.cs} | 19 ++- 8 files changed, 214 insertions(+), 122 deletions(-) create mode 100644 Source/EventFlow.Tests/UnitTests/Provided/Specifications/AtLeastSpecificationTests.cs create mode 100644 Source/EventFlow.Tests/UnitTests/Specifications/TestSpecifications.cs create mode 100644 Source/EventFlow.Tests/UnitTests/Specifications/TestSpecificationsTests.cs rename Source/EventFlow/Provided/Specifications/{SomeSpecification.cs => AtLeastSpecification.cs} (79%) diff --git a/Source/EventFlow.Tests/EventFlow.Tests.csproj b/Source/EventFlow.Tests/EventFlow.Tests.csproj index 7b829063f..8a66bb7af 100644 --- a/Source/EventFlow.Tests/EventFlow.Tests.csproj +++ b/Source/EventFlow.Tests/EventFlow.Tests.csproj @@ -91,6 +91,7 @@ + @@ -107,6 +108,8 @@ + + diff --git a/Source/EventFlow.Tests/UnitTests/Provided/Specifications/AtLeastSpecificationTests.cs b/Source/EventFlow.Tests/UnitTests/Provided/Specifications/AtLeastSpecificationTests.cs new file mode 100644 index 000000000..bc378ea6a --- /dev/null +++ b/Source/EventFlow.Tests/UnitTests/Provided/Specifications/AtLeastSpecificationTests.cs @@ -0,0 +1,58 @@ +// The MIT License (MIT) +// +// Copyright (c) 2015 Rasmus Mikkelsen +// https://github.com/rasmus/EventFlow +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +using EventFlow.Extensions; +using EventFlow.Tests.UnitTests.Specifications; +using FluentAssertions; +using NUnit.Framework; + +namespace EventFlow.Tests.UnitTests.Provided.Specifications +{ + public class AtLeastSpecificationTests + { + [TestCase(1, 1, false)] + [TestCase(1, 2, true)] + [TestCase(1, 3, true)] + [TestCase(1, 4, true)] + [TestCase(1, 5, true)] + [TestCase(3, 1, false)] + [TestCase(3, 2, false)] + [TestCase(3, 3, false)] + [TestCase(3, 4, true)] + [TestCase(3, 5, true)] + public void AtLeast_Returns_Correctly(int requiredSpecifications, int obj, bool expectedIsSatisfiedBy) + { + // Arrange + var isAbove1 = new TestSpecifications.IsAboveSpecification(1); + var isAbove2 = new TestSpecifications.IsAboveSpecification(2); + var isAbove3 = new TestSpecifications.IsAboveSpecification(3); + var isAbove4 = new TestSpecifications.IsAboveSpecification(4); + var atLeast = new[] { isAbove1, isAbove2, isAbove3, isAbove4 }.AtLeast(requiredSpecifications); + + // Act + var isSatisfiedBy = atLeast.IsSatisfiedBy(obj); + + // Assert + isSatisfiedBy.Should().Be(expectedIsSatisfiedBy, string.Join(", ", atLeast.WhyIsNotStatisfiedBy(obj))); + } + } +} \ No newline at end of file diff --git a/Source/EventFlow.Tests/UnitTests/Specifications/SpecificationTests.cs b/Source/EventFlow.Tests/UnitTests/Specifications/SpecificationTests.cs index 2bcce228d..0e23d650b 100644 --- a/Source/EventFlow.Tests/UnitTests/Specifications/SpecificationTests.cs +++ b/Source/EventFlow.Tests/UnitTests/Specifications/SpecificationTests.cs @@ -20,7 +20,6 @@ // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -using System.Collections.Generic; using EventFlow.Exceptions; using EventFlow.Extensions; using EventFlow.Specifications; @@ -31,37 +30,11 @@ namespace EventFlow.Tests.UnitTests.Specifications { public class SpecificationTests { - [Test] - public void ShouldReturnTrueForSatisfiedValue() - { - // Arrange - var isTrue = new IsTrueSpecification(); - - // Act - var isSatisfiedBy = isTrue.IsSatisfiedBy(true); - - // Act - isSatisfiedBy.Should().BeTrue(); - } - - [Test] - public void ShouldReturnFalseForNotSatisfiedValue() - { - // Arrange - var isTrue = new IsTrueSpecification(); - - // Act - var isSatisfiedBy = isTrue.IsSatisfiedBy(false); - - // Act - isSatisfiedBy.Should().BeFalse(); - } - [Test] public void NotSpecification_ReturnsTrue_ForNotSatisfied() { // Arrange - var isTrue = new IsTrueSpecification(); + var isTrue = new TestSpecifications.IsTrueSpecification(); // Act var isSatisfiedBy = isTrue.Not().IsSatisfiedBy(false); @@ -74,7 +47,7 @@ public void NotSpecification_ReturnsTrue_ForNotSatisfied() public void NotSpeficication_ReturnsFalse_ForSatisfied() { // Arrange - var isTrue = new IsTrueSpecification(); + var isTrue = new TestSpecifications.IsTrueSpecification(); // Act var isSatisfiedBy = isTrue.Not().IsSatisfiedBy(true); @@ -90,8 +63,8 @@ public void NotSpeficication_ReturnsFalse_ForSatisfied() public void OrSpeficication_ReturnsTrue_Correctly(bool notLeft, bool notRight, bool expectedResult) { // Arrange - var leftIsTrue = (ISpecification) new IsTrueSpecification(); - var rightIsTrue = (ISpecification)new IsTrueSpecification(); + var leftIsTrue = (ISpecification) new TestSpecifications.IsTrueSpecification(); + var rightIsTrue = (ISpecification)new TestSpecifications.IsTrueSpecification(); if (notLeft) leftIsTrue = leftIsTrue.Not(); if (notRight) rightIsTrue = rightIsTrue.Not(); var orSpecification = leftIsTrue.Or(rightIsTrue); @@ -110,8 +83,8 @@ public void OrSpeficication_ReturnsTrue_Correctly(bool notLeft, bool notRight, b public void AndSpeficication_ReturnsTrue_Correctly(bool notLeft, bool notRight, bool expectedResult) { // Arrange - var leftIsTrue = (ISpecification)new IsTrueSpecification(); - var rightIsTrue = (ISpecification)new IsTrueSpecification(); + var leftIsTrue = (ISpecification)new TestSpecifications.IsTrueSpecification(); + var rightIsTrue = (ISpecification)new TestSpecifications.IsTrueSpecification(); if (notLeft) leftIsTrue = leftIsTrue.Not(); if (notRight) rightIsTrue = rightIsTrue.Not(); var andSpecification = leftIsTrue.And(rightIsTrue); @@ -123,59 +96,11 @@ public void AndSpeficication_ReturnsTrue_Correctly(bool notLeft, bool notRight, isSatisfiedBy.Should().Be(expectedResult); } - [TestCase(1, 1, false)] - [TestCase(1, 2, true)] - [TestCase(1, 3, true)] - [TestCase(1, 4, true)] - [TestCase(1, 5, true)] - [TestCase(3, 1, false)] - [TestCase(3, 2, false)] - [TestCase(3, 3, false)] - [TestCase(3, 4, true)] - [TestCase(3, 5, true)] - public void AtLeast_Returns_Correctly(int requiredSpecifications, int obj, bool expectedIsSatisfiedBy) - { - // Arrange - var isAbove1 = new IsAboveSpecification(1); - var isAbove2 = new IsAboveSpecification(2); - var isAbove3 = new IsAboveSpecification(3); - var isAbove4 = new IsAboveSpecification(4); - var atLeast = new[] - { - isAbove1, - isAbove2, - isAbove3, - isAbove4 - } - .AtLeast(requiredSpecifications); - - // Act - var isSatisfiedBy = atLeast.IsSatisfiedBy(obj); - - // Assert - isSatisfiedBy.Should().Be(expectedIsSatisfiedBy, string.Join(", ", atLeast.WhyIsNotStatisfiedBy(obj))); - } - - [TestCase(4, 3, false)] - [TestCase(4, 4, false)] - [TestCase(4, 5, true)] - public void IsAbove_Returns_Correct(int limit, int obj, bool expectedIsSatisfiedBy) - { - // Arrange - var isAbove = new IsAboveSpecification(limit); - - // Act - var isSatisfiedBy = isAbove.IsSatisfiedBy(obj); - - // Assert - isSatisfiedBy.Should().Be(expectedIsSatisfiedBy); - } - [Test] public void ThrowDomainErrorIfNotStatisfied_Throws_IfNotSatisfied() { // Arrange - var isTrue = new IsTrueSpecification(); + var isTrue = new TestSpecifications.IsTrueSpecification(); // Act Assert.Throws(() => isTrue.ThrowDomainErrorIfNotStatisfied(false)); @@ -185,40 +110,10 @@ public void ThrowDomainErrorIfNotStatisfied_Throws_IfNotSatisfied() public void ThrowDomainErrorIfNotStatisfied_DoesNotThrow_IfStatisfied() { // Arrange - var isTrue = new IsTrueSpecification(); + var isTrue = new TestSpecifications.IsTrueSpecification(); // Act Assert.DoesNotThrow(() => isTrue.ThrowDomainErrorIfNotStatisfied(true)); } - - public class IsAboveSpecification : Specification - { - private readonly int _limit; - - public IsAboveSpecification( - int limit) - { - _limit = limit; - } - - protected override IEnumerable IsNotStatisfiedBecause(int obj) - { - if (obj <= _limit) - { - yield return $"{obj} is less or equal than {_limit}"; - } - } - } - - public class IsTrueSpecification : Specification - { - protected override IEnumerable IsNotStatisfiedBecause(bool obj) - { - if (!obj) - { - yield return "Its false!"; - } - } - } } } \ No newline at end of file diff --git a/Source/EventFlow.Tests/UnitTests/Specifications/TestSpecifications.cs b/Source/EventFlow.Tests/UnitTests/Specifications/TestSpecifications.cs new file mode 100644 index 000000000..ff5887623 --- /dev/null +++ b/Source/EventFlow.Tests/UnitTests/Specifications/TestSpecifications.cs @@ -0,0 +1,60 @@ +// The MIT License (MIT) +// +// Copyright (c) 2015 Rasmus Mikkelsen +// https://github.com/rasmus/EventFlow +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +using System.Collections.Generic; +using EventFlow.Specifications; + +namespace EventFlow.Tests.UnitTests.Specifications +{ + public static class TestSpecifications + { + public class IsAboveSpecification : Specification + { + private readonly int _limit; + + public IsAboveSpecification( + int limit) + { + _limit = limit; + } + + protected override IEnumerable IsNotStatisfiedBecause(int obj) + { + if (obj <= _limit) + { + yield return $"{obj} is less or equal than {_limit}"; + } + } + } + + public class IsTrueSpecification : Specification + { + protected override IEnumerable IsNotStatisfiedBecause(bool obj) + { + if (!obj) + { + yield return "Its false!"; + } + } + } + } +} \ No newline at end of file diff --git a/Source/EventFlow.Tests/UnitTests/Specifications/TestSpecificationsTests.cs b/Source/EventFlow.Tests/UnitTests/Specifications/TestSpecificationsTests.cs new file mode 100644 index 000000000..b7c036368 --- /dev/null +++ b/Source/EventFlow.Tests/UnitTests/Specifications/TestSpecificationsTests.cs @@ -0,0 +1,71 @@ +// The MIT License (MIT) +// +// Copyright (c) 2015 Rasmus Mikkelsen +// https://github.com/rasmus/EventFlow +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +using FluentAssertions; +using NUnit.Framework; + +namespace EventFlow.Tests.UnitTests.Specifications +{ + public class TestSpecificationsTests + { + [Test] + public void IsTrueSpecification_ReturnsTrue_ForTrue() + { + // Arrange + var isTrue = new TestSpecifications.IsTrueSpecification(); + + // Act + var isSatisfiedBy = isTrue.IsSatisfiedBy(true); + + // Act + isSatisfiedBy.Should().BeTrue(); + } + + [Test] + public void IsTrueSpecification_ReturnsFalse_ForFalse() + { + // Arrange + var isTrue = new TestSpecifications.IsTrueSpecification(); + + // Act + var isSatisfiedBy = isTrue.IsSatisfiedBy(false); + + // Act + isSatisfiedBy.Should().BeFalse(); + } + + [TestCase(4, 3, false)] + [TestCase(4, 4, false)] + [TestCase(4, 5, true)] + public void IsAboveSpecification_Returns_Correct(int limit, int obj, bool expectedIsSatisfiedBy) + { + // Arrange + var isAbove = new TestSpecifications.IsAboveSpecification(limit); + + // Act + var isSatisfiedBy = isAbove.IsSatisfiedBy(obj); + + // Assert + isSatisfiedBy.Should().Be(expectedIsSatisfiedBy); + } + } +} \ No newline at end of file diff --git a/Source/EventFlow/EventFlow.csproj b/Source/EventFlow/EventFlow.csproj index df42bf2a5..f50a6445e 100644 --- a/Source/EventFlow/EventFlow.csproj +++ b/Source/EventFlow/EventFlow.csproj @@ -192,7 +192,7 @@ - + diff --git a/Source/EventFlow/Extensions/SpecificationExtensions.cs b/Source/EventFlow/Extensions/SpecificationExtensions.cs index ce8971705..fb44d2486 100644 --- a/Source/EventFlow/Extensions/SpecificationExtensions.cs +++ b/Source/EventFlow/Extensions/SpecificationExtensions.cs @@ -56,7 +56,7 @@ public static ISpecification AtLeast( int requiredSpecifications) { return new AtLeastSpecification(requiredSpecifications, specifications); - } + } public static ISpecification And( this ISpecification specification1, diff --git a/Source/EventFlow/Provided/Specifications/SomeSpecification.cs b/Source/EventFlow/Provided/Specifications/AtLeastSpecification.cs similarity index 79% rename from Source/EventFlow/Provided/Specifications/SomeSpecification.cs rename to Source/EventFlow/Provided/Specifications/AtLeastSpecification.cs index 1dc80ea99..69addee44 100644 --- a/Source/EventFlow/Provided/Specifications/SomeSpecification.cs +++ b/Source/EventFlow/Provided/Specifications/AtLeastSpecification.cs @@ -37,22 +37,27 @@ public AtLeastSpecification( int requiredSpecifications, IEnumerable> specifications) { - _requiredSpecifications = requiredSpecifications; var specificationList = (specifications ?? Enumerable.Empty>()).ToList(); - if (requiredSpecifications <= 0) throw new ArgumentOutOfRangeException(nameof(requiredSpecifications)); - if (!specificationList.Any()) throw new ArgumentException( - "Please provide some specifications", nameof(specifications)); - if (requiredSpecifications > specificationList.Count) throw new ArgumentOutOfRangeException( - $"You required '{requiredSpecifications}' to be met, but only '{specificationList.Count}' was supplied"); + if (requiredSpecifications <= 0) + throw new ArgumentOutOfRangeException(nameof(requiredSpecifications)); + if (!specificationList.Any()) + throw new ArgumentException("Please provide some specifications", nameof(specifications)); + if (requiredSpecifications > specificationList.Count) + throw new ArgumentOutOfRangeException($"You required '{requiredSpecifications}' to be met, but only '{specificationList.Count}' was supplied"); + _requiredSpecifications = requiredSpecifications; _specifications = specificationList; } protected override IEnumerable IsNotStatisfiedBecause(T obj) { var notStatisfiedReasons = _specifications - .Select(s => new {Specification = s, WhyIsNotStatisfied = s.WhyIsNotStatisfiedBy(obj).ToList()}) + .Select(s => new + { + Specification = s, + WhyIsNotStatisfied = s.WhyIsNotStatisfiedBy(obj).ToList() + }) .Where(a => a.WhyIsNotStatisfied.Any()) .Select(a => $"{a.Specification.GetType().PrettyPrint()}: {string.Join(", ", a.WhyIsNotStatisfied)}") .ToList(); From abfae0ec740044d74ec917893a4d11e5f8c2e3b2 Mon Sep 17 00:00:00 2001 From: Rasmus Mikkelsen Date: Sun, 18 Oct 2015 19:27:16 +0200 Subject: [PATCH 16/16] Update release notes --- RELEASE_NOTES.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index fee25683c..212dbee50 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -2,6 +2,9 @@ * Breaking: `AddDefaults` now also adds the job type definition to the `IJobsDefinitonService` +* New: Implemented a basic specification pattern by providing + `ISpecification`, an easy-to-use `Specificaion` and a set of extension + methods. Look at the EventFlow specification tests to get started * Fixed: `IEventDefinitionService`, `ICommandDefinitonService` and `IJobsDefinitonService` now longer throw an exception if an existing event is loaded, i.e., multiple calls to `AddEvents(...)`, `AddCommand(...)`