From eae567435c69cedb172b056a7d11211999d71b71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=B8rge=20Nordli?= Date: Tue, 23 Feb 2016 13:09:58 +0100 Subject: [PATCH 1/3] Provide an IExceptionSubscriber interface for listening to exceptions #686 Exceptions from the following calls will be forwarded to anything implementing IExceptionSubscriber - queries - commands - sagas - calls to rest handlers Original issue: ProCoSys/Bifrost#9 --- .../given/a_command_coordinator.cs | 11 ++- ..._handling_a_command_that_fails_security.cs | 5 +- .../when_handling_an_invalid_command.cs | 46 +++++------ ...n_exception_occurs_during_authorization.cs | 12 +-- ...and_an_exception_occurs_during_handling.cs | 21 ++--- ...d_an_exception_occurs_during_validation.cs | 12 +-- .../when_handling_command_with_success.cs | 20 ++--- .../given/a_query_coordinator.cs | 3 +- ...a_query_coordinator_with_known_provider.cs | 3 +- ...dinator_with_non_generic_known_provider.cs | 3 +- ...or_known_query_and_one_for_derived_type.cs | 3 +- .../given/all_dependencies.cs | 5 +- ...cuting_and_provider_throws_an_exception.cs | 2 + .../for_SagaNarrator/given/a_saga_narrator.cs | 32 ++++---- ...cluding_a_saga_that_causes_an_exception.cs | 37 ++++----- ...luding_a_saga_that_is_already_concluded.cs | 17 +++-- .../when_concluding_a_saga_that_is_new.cs | 13 ++-- Source/Bifrost.Testing/CommandScenario.cs | 4 +- .../Services/RestServiceRouteHttpHandler.cs | 51 ++++++------- Source/Bifrost/Bifrost.csproj | 3 + Source/Bifrost/Commands/CommandCoordinator.cs | 76 ++++++++++--------- .../Bifrost/Exceptions/ExceptionPublisher.cs | 38 ++++++++++ .../Bifrost/Exceptions/IExceptionPublisher.cs | 20 +++++ .../Exceptions/IExceptionSubscriber.cs | 24 ++++++ Source/Bifrost/Read/QueryCoordinator.cs | 22 ++++-- Source/Bifrost/Sagas/SagaNarrator.cs | 11 ++- 26 files changed, 306 insertions(+), 188 deletions(-) create mode 100644 Source/Bifrost/Exceptions/ExceptionPublisher.cs create mode 100644 Source/Bifrost/Exceptions/IExceptionPublisher.cs create mode 100644 Source/Bifrost/Exceptions/IExceptionSubscriber.cs diff --git a/Source/Bifrost.Specs/Commands/for_CommandCoordinator/given/a_command_coordinator.cs b/Source/Bifrost.Specs/Commands/for_CommandCoordinator/given/a_command_coordinator.cs index fc097d230..23b294ae0 100644 --- a/Source/Bifrost.Specs/Commands/for_CommandCoordinator/given/a_command_coordinator.cs +++ b/Source/Bifrost.Specs/Commands/for_CommandCoordinator/given/a_command_coordinator.cs @@ -1,6 +1,6 @@ using Bifrost.Commands; +using Bifrost.Exceptions; using Bifrost.Security; -using Bifrost.Validation; using Machine.Specifications; using Moq; @@ -13,15 +13,17 @@ public abstract class a_command_coordinator : Globalization.given.a_localizer_mo protected static Mock command_context_manager_mock; protected static Mock command_security_manager_mock; protected static Mock command_validators_mock; - protected static Mock command_mock; protected static Mock command_context_mock; + protected static Mock exception_publisher_mock; + protected static ICommand command; Establish context = () => { - command_mock = new Mock(); + command = Mock.Of(); command_handler_manager_mock = new Mock(); command_context_manager_mock = new Mock(); command_validators_mock = new Mock(); + exception_publisher_mock = new Mock(); command_context_mock = new Mock(); command_context_manager_mock.Setup(c => c.EstablishForCommand(Moq.It.IsAny())). @@ -36,7 +38,8 @@ public abstract class a_command_coordinator : Globalization.given.a_localizer_mo command_context_manager_mock.Object, command_security_manager_mock.Object, command_validators_mock.Object, - localizer_mock.Object + localizer_mock.Object, + exception_publisher_mock.Object ); }; } diff --git a/Source/Bifrost.Specs/Commands/for_CommandCoordinator/when_handling_a_command_that_fails_security.cs b/Source/Bifrost.Specs/Commands/for_CommandCoordinator/when_handling_a_command_that_fails_security.cs index 51bd89192..59701207c 100644 --- a/Source/Bifrost.Specs/Commands/for_CommandCoordinator/when_handling_a_command_that_fails_security.cs +++ b/Source/Bifrost.Specs/Commands/for_CommandCoordinator/when_handling_a_command_that_fails_security.cs @@ -1,7 +1,6 @@ using Bifrost.Commands; using Bifrost.Security; using Machine.Specifications; -using Bifrost.Testing.Fakes.Commands; using Moq; using It = Machine.Specifications.It; @@ -10,7 +9,6 @@ namespace Bifrost.Specs.Commands.for_CommandCoordinator [Subject(typeof(CommandCoordinator))] public class when_handling_a_command_that_fails_security : given.a_command_coordinator { - static ICommand command; static CommandResult result; static Mock authorization_result; @@ -19,8 +17,7 @@ public class when_handling_a_command_that_fails_security : given.a_command_coord authorization_result = new Mock(); authorization_result.Setup(r => r.IsAuthorized).Returns(false); authorization_result.Setup(r => r.BuildFailedAuthorizationMessages()).Returns(new[] { "Something went wrong" }); - command = new SimpleCommand(); - command_security_manager_mock.Setup(c => c.Authorize(Moq.It.IsAny())).Returns(authorization_result.Object); + command_security_manager_mock.Setup(c => c.Authorize(command)).Returns(authorization_result.Object); }; Because of = () => result = coordinator.Handle(command); diff --git a/Source/Bifrost.Specs/Commands/for_CommandCoordinator/when_handling_an_invalid_command.cs b/Source/Bifrost.Specs/Commands/for_CommandCoordinator/when_handling_an_invalid_command.cs index 3bdea18cc..740f1b895 100644 --- a/Source/Bifrost.Specs/Commands/for_CommandCoordinator/when_handling_an_invalid_command.cs +++ b/Source/Bifrost.Specs/Commands/for_CommandCoordinator/when_handling_an_invalid_command.cs @@ -1,38 +1,40 @@ using Bifrost.Commands; -using Machine.Specifications; using Bifrost.Validation; +using Machine.Specifications; +using Moq; +using It = Machine.Specifications.It; namespace Bifrost.Specs.Commands.for_CommandCoordinator { [Subject(typeof(CommandCoordinator))] public class when_handling_an_invalid_command : given.a_command_coordinator { - static CommandResult Result; + static CommandResult result; static CommandValidationResult validation_errors; - Establish context = () => - { - validation_errors = new CommandValidationResult - { - ValidationResults = new ValidationResult[] - { - new ValidationResult("First validation failure"), - new ValidationResult("Second validation failure") - } - }; + Establish context = () => + { + validation_errors = new CommandValidationResult + { + ValidationResults = new[] + { + new ValidationResult("First validation failure"), + new ValidationResult("Second validation failure") + } + }; - command_validators_mock.Setup(cvs => cvs.Validate(command_mock.Object)).Returns( - validation_errors); - }; + command_validators_mock + .Setup(cvs => cvs.Validate(command)) + .Returns(validation_errors); + }; - Because of = () => Result = coordinator.Handle(command_mock.Object); - + Because of = () => result = coordinator.Handle(command); It should_have_validated_the_command = () => command_validators_mock.VerifyAll(); - It should_have_a_result = () => Result.ShouldNotBeNull(); - It should_have_success_set_to_false = () => Result.Success.ShouldBeFalse(); - It should_have_a_record_of_each_validation_failure = () => Result.ValidationResults.ShouldContainOnly(validation_errors.ValidationResults); - It should_not_handle_the_command = () => command_handler_manager_mock.Verify(chm => chm.Handle(command_mock.Object), Moq.Times.Never()); - It should_rollback_the_command_context = () => command_context_mock.Verify(c => c.Rollback(), Moq.Times.Once()); + It should_have_a_result = () => result.ShouldNotBeNull(); + It should_have_success_set_to_false = () => result.Success.ShouldBeFalse(); + It should_have_a_record_of_each_validation_failure = () => result.ValidationResults.ShouldContainOnly(validation_errors.ValidationResults); + It should_not_handle_the_command = () => command_handler_manager_mock.Verify(chm => chm.Handle(command), Times.Never()); + It should_rollback_the_command_context = () => command_context_mock.Verify(c => c.Rollback(), Times.Once()); } } \ No newline at end of file diff --git a/Source/Bifrost.Specs/Commands/for_CommandCoordinator/when_handling_command_and_an_exception_occurs_during_authorization.cs b/Source/Bifrost.Specs/Commands/for_CommandCoordinator/when_handling_command_and_an_exception_occurs_during_authorization.cs index 26ac148b3..27f0a48db 100644 --- a/Source/Bifrost.Specs/Commands/for_CommandCoordinator/when_handling_command_and_an_exception_occurs_during_authorization.cs +++ b/Source/Bifrost.Specs/Commands/for_CommandCoordinator/when_handling_command_and_an_exception_occurs_during_authorization.cs @@ -1,5 +1,6 @@ using System; using Bifrost.Commands; +using Bifrost.Exceptions; using Machine.Specifications; namespace Bifrost.Specs.Commands.for_CommandCoordinator @@ -11,14 +12,15 @@ public class when_handling_command_and_an_exception_occurs_during_authorization static Exception exception; Establish context = () => - { - exception = new Exception(); - command_security_manager_mock.Setup(cvs => cvs.Authorize(command_mock.Object)).Throws(exception); - }; + { + exception = new Exception(); + command_security_manager_mock.Setup(cvs => cvs.Authorize(command)).Throws(exception); + }; - Because of = () => result = coordinator.Handle(command_mock.Object); + Because of = () => result = coordinator.Handle(command); It should_have_exception_in_result = () => result.Exception.ShouldEqual(exception); It should_have_success_set_to_false = () => result.Success.ShouldBeFalse(); + It should_have_published_the_exception = () => exception_publisher_mock.Verify(m => m.Publish(exception)); } } \ No newline at end of file diff --git a/Source/Bifrost.Specs/Commands/for_CommandCoordinator/when_handling_command_and_an_exception_occurs_during_handling.cs b/Source/Bifrost.Specs/Commands/for_CommandCoordinator/when_handling_command_and_an_exception_occurs_during_handling.cs index 940a75abf..9aa5cd78a 100644 --- a/Source/Bifrost.Specs/Commands/for_CommandCoordinator/when_handling_command_and_an_exception_occurs_during_handling.cs +++ b/Source/Bifrost.Specs/Commands/for_CommandCoordinator/when_handling_command_and_an_exception_occurs_during_handling.cs @@ -1,8 +1,8 @@ using System; using Bifrost.Commands; -using Machine.Specifications; -using It = Machine.Specifications.It; +using Bifrost.Exceptions; using Bifrost.Validation; +using Machine.Specifications; namespace Bifrost.Specs.Commands.for_CommandCoordinator { @@ -13,18 +13,19 @@ public class when_handling_command_and_an_exception_occurs_during_handling : giv static Exception exception; Establish context = () => - { - exception = new Exception(); - var validation_results = new CommandValidationResult { ValidationResults = new ValidationResult[0] }; - command_validators_mock.Setup(cvs => cvs.Validate(command_mock.Object)).Returns(validation_results); - command_handler_manager_mock.Setup(c => c.Handle(Moq.It.IsAny())).Throws(exception); - }; + { + exception = new Exception(); + var validationResults = new CommandValidationResult {ValidationResults = new ValidationResult[0]}; + command_validators_mock.Setup(cvs => cvs.Validate(command)).Returns(validationResults); + command_handler_manager_mock.Setup(c => c.Handle(Moq.It.IsAny())).Throws(exception); + }; - Because of = () => result = coordinator.Handle(command_mock.Object); + Because of = () => result = coordinator.Handle(command); It should_have_validated_the_command = () => command_validators_mock.VerifyAll(); It should_have_authenticated_the_command = () => command_security_manager_mock.VerifyAll(); It should_have_exception_in_result = () => result.Exception.ShouldEqual(exception); It should_have_success_set_to_false = () => result.Success.ShouldBeFalse(); + It should_have_published_the_exception = () => exception_publisher_mock.Verify(m => m.Publish(exception)); } -} +} \ No newline at end of file diff --git a/Source/Bifrost.Specs/Commands/for_CommandCoordinator/when_handling_command_and_an_exception_occurs_during_validation.cs b/Source/Bifrost.Specs/Commands/for_CommandCoordinator/when_handling_command_and_an_exception_occurs_during_validation.cs index b373404ec..a92f365b3 100644 --- a/Source/Bifrost.Specs/Commands/for_CommandCoordinator/when_handling_command_and_an_exception_occurs_during_validation.cs +++ b/Source/Bifrost.Specs/Commands/for_CommandCoordinator/when_handling_command_and_an_exception_occurs_during_validation.cs @@ -1,5 +1,6 @@ using System; using Bifrost.Commands; +using Bifrost.Exceptions; using Machine.Specifications; namespace Bifrost.Specs.Commands.for_CommandCoordinator @@ -11,15 +12,16 @@ public class when_handling_command_and_an_exception_occurs_during_validation : g static Exception exception; Establish context = () => - { - exception = new Exception(); - command_validators_mock.Setup(cvs => cvs.Validate(command_mock.Object)).Throws(exception); - }; + { + exception = new Exception(); + command_validators_mock.Setup(cvs => cvs.Validate(command)).Throws(exception); + }; - Because of = () => result = coordinator.Handle(command_mock.Object); + Because of = () => result = coordinator.Handle(command); It should_have_authorized_the_command = () => command_security_manager_mock.VerifyAll(); It should_have_exception_in_result = () => result.Exception.ShouldEqual(exception); It should_have_success_set_to_false = () => result.Success.ShouldBeFalse(); + It should_have_published_the_exception = () => exception_publisher_mock.Verify(m => m.Publish(exception)); } } \ No newline at end of file diff --git a/Source/Bifrost.Specs/Commands/for_CommandCoordinator/when_handling_command_with_success.cs b/Source/Bifrost.Specs/Commands/for_CommandCoordinator/when_handling_command_with_success.cs index e9e045ed1..d35de9f5c 100644 --- a/Source/Bifrost.Specs/Commands/for_CommandCoordinator/when_handling_command_with_success.cs +++ b/Source/Bifrost.Specs/Commands/for_CommandCoordinator/when_handling_command_with_success.cs @@ -1,28 +1,20 @@ using Bifrost.Commands; using Machine.Specifications; -using It = Machine.Specifications.It; -using Bifrost.Validation; namespace Bifrost.Specs.Commands.for_CommandCoordinator { [Subject(typeof(CommandCoordinator))] public class when_handling_command_with_success : given.a_command_coordinator { - static CommandResult Result; - + static CommandResult result; + Establish context = () => - { - var validation_results = new CommandValidationResult(); - command_validators_mock.Setup(cvs => cvs.Validate(command_mock.Object)).Returns(validation_results); - }; + command_validators_mock.Setup(cvs => cvs.Validate(command)).Returns(new CommandValidationResult()); - Because of = () => - { - Result = coordinator.Handle(command_mock.Object); - }; + Because of = () => result = coordinator.Handle(command); It should_have_validated_the_command = () => command_validators_mock.VerifyAll(); - It should_have_a_result = () => Result.ShouldNotBeNull(); - It should_have_success_set_to_true = () => Result.Success.ShouldBeTrue(); + It should_have_a_result = () => result.ShouldNotBeNull(); + It should_have_success_set_to_true = () => result.Success.ShouldBeTrue(); } } \ No newline at end of file diff --git a/Source/Bifrost.Specs/Read/for_QueryCoordinator/given/a_query_coordinator.cs b/Source/Bifrost.Specs/Read/for_QueryCoordinator/given/a_query_coordinator.cs index 0a8cbe4cd..1aa5eda3e 100644 --- a/Source/Bifrost.Specs/Read/for_QueryCoordinator/given/a_query_coordinator.cs +++ b/Source/Bifrost.Specs/Read/for_QueryCoordinator/given/a_query_coordinator.cs @@ -22,7 +22,8 @@ public class a_query_coordinator : all_dependencies container_mock.Object, fetching_security_manager_mock.Object, query_validator_mock.Object, - read_model_filters_mock.Object); + read_model_filters_mock.Object, + exception_publisher_mock.Object); }; } } diff --git a/Source/Bifrost.Specs/Read/for_QueryCoordinator/given/a_query_coordinator_with_known_provider.cs b/Source/Bifrost.Specs/Read/for_QueryCoordinator/given/a_query_coordinator_with_known_provider.cs index e367d1bb1..692f6cac2 100644 --- a/Source/Bifrost.Specs/Read/for_QueryCoordinator/given/a_query_coordinator_with_known_provider.cs +++ b/Source/Bifrost.Specs/Read/for_QueryCoordinator/given/a_query_coordinator_with_known_provider.cs @@ -26,7 +26,8 @@ public class a_query_coordinator_with_known_provider : a_query_coordinator container_mock.Object, fetching_security_manager_mock.Object, query_validator_mock.Object, - read_model_filters_mock.Object); + read_model_filters_mock.Object, + exception_publisher_mock.Object); }; } } diff --git a/Source/Bifrost.Specs/Read/for_QueryCoordinator/given/a_query_coordinator_with_non_generic_known_provider.cs b/Source/Bifrost.Specs/Read/for_QueryCoordinator/given/a_query_coordinator_with_non_generic_known_provider.cs index c14e091cf..d32c3986c 100644 --- a/Source/Bifrost.Specs/Read/for_QueryCoordinator/given/a_query_coordinator_with_non_generic_known_provider.cs +++ b/Source/Bifrost.Specs/Read/for_QueryCoordinator/given/a_query_coordinator_with_non_generic_known_provider.cs @@ -26,7 +26,8 @@ public class a_query_coordinator_with_non_generic_known_provider : a_query_coord container_mock.Object, fetching_security_manager_mock.Object, query_validator_mock.Object, - read_model_filters_mock.Object); + read_model_filters_mock.Object, + exception_publisher_mock.Object); }; } } diff --git a/Source/Bifrost.Specs/Read/for_QueryCoordinator/given/a_query_coordinator_with_provider_for_known_query_and_one_for_derived_type.cs b/Source/Bifrost.Specs/Read/for_QueryCoordinator/given/a_query_coordinator_with_provider_for_known_query_and_one_for_derived_type.cs index 39a9a9f2e..420fd0873 100644 --- a/Source/Bifrost.Specs/Read/for_QueryCoordinator/given/a_query_coordinator_with_provider_for_known_query_and_one_for_derived_type.cs +++ b/Source/Bifrost.Specs/Read/for_QueryCoordinator/given/a_query_coordinator_with_provider_for_known_query_and_one_for_derived_type.cs @@ -30,7 +30,8 @@ public class a_query_coordinator_with_provider_for_known_query_and_one_for_deriv container_mock.Object, fetching_security_manager_mock.Object, query_validator_mock.Object, - read_model_filters_mock.Object); + read_model_filters_mock.Object, + exception_publisher_mock.Object); }; } } diff --git a/Source/Bifrost.Specs/Read/for_QueryCoordinator/given/all_dependencies.cs b/Source/Bifrost.Specs/Read/for_QueryCoordinator/given/all_dependencies.cs index dc575cfac..7807cc874 100644 --- a/Source/Bifrost.Specs/Read/for_QueryCoordinator/given/all_dependencies.cs +++ b/Source/Bifrost.Specs/Read/for_QueryCoordinator/given/all_dependencies.cs @@ -1,4 +1,5 @@ -using Bifrost.Execution; +using Bifrost.Exceptions; +using Bifrost.Execution; using Bifrost.Read; using Bifrost.Read.Validation; using Machine.Specifications; @@ -13,6 +14,7 @@ public class all_dependencies protected static Mock fetching_security_manager_mock; protected static Mock read_model_filters_mock; protected static Mock query_validator_mock; + protected static Mock exception_publisher_mock; Establish context = () => { @@ -21,6 +23,7 @@ public class all_dependencies fetching_security_manager_mock = new Mock(); read_model_filters_mock = new Mock(); query_validator_mock = new Mock(); + exception_publisher_mock = new Mock(); }; } } diff --git a/Source/Bifrost.Specs/Read/for_QueryCoordinator/when_executing_and_provider_throws_an_exception.cs b/Source/Bifrost.Specs/Read/for_QueryCoordinator/when_executing_and_provider_throws_an_exception.cs index 99e90cd86..de31930d8 100644 --- a/Source/Bifrost.Specs/Read/for_QueryCoordinator/when_executing_and_provider_throws_an_exception.cs +++ b/Source/Bifrost.Specs/Read/for_QueryCoordinator/when_executing_and_provider_throws_an_exception.cs @@ -29,5 +29,7 @@ public class when_executing_and_provider_throws_an_exception : given.a_query_coo Because of = () => result = coordinator.Execute(query, paging); It should_set_the_exception_on_the_result = () => result.Exception.ShouldEqual(exception_thrown); + + It should_publish_the_exception = () => exception_publisher_mock.Verify(m => m.Publish(exception_thrown)); } } diff --git a/Source/Bifrost.Specs/Sagas/for_SagaNarrator/given/a_saga_narrator.cs b/Source/Bifrost.Specs/Sagas/for_SagaNarrator/given/a_saga_narrator.cs index a49b2d894..8185507a5 100644 --- a/Source/Bifrost.Specs/Sagas/for_SagaNarrator/given/a_saga_narrator.cs +++ b/Source/Bifrost.Specs/Sagas/for_SagaNarrator/given/a_saga_narrator.cs @@ -1,7 +1,7 @@ using Bifrost.Events; +using Bifrost.Exceptions; using Bifrost.Execution; using Bifrost.Sagas; -using Bifrost.Validation; using Machine.Specifications; using Moq; @@ -13,22 +13,24 @@ public class a_saga_narrator protected static Mock librarian_mock; protected static Mock container_mock; protected static Mock chapter_validation_service_mock; - protected static Mock event_store_mock; + protected static Mock event_store_mock; + protected static Mock exception_publisher_mock; Establish context = () => - { - librarian_mock = new Mock(); - container_mock = new Mock(); - chapter_validation_service_mock = new Mock(); - event_store_mock = new Mock(); + { + librarian_mock = new Mock(); + container_mock = new Mock(); + chapter_validation_service_mock = new Mock(); + event_store_mock = new Mock(); + exception_publisher_mock = new Mock(); - narrator = new SagaNarrator( - librarian_mock.Object, - container_mock.Object, - chapter_validation_service_mock.Object, - event_store_mock.Object - ); - - }; + narrator = new SagaNarrator( + librarian_mock.Object, + container_mock.Object, + chapter_validation_service_mock.Object, + event_store_mock.Object, + exception_publisher_mock.Object + ); + }; } } diff --git a/Source/Bifrost.Specs/Sagas/for_SagaNarrator/when_concluding_a_saga_that_causes_an_exception.cs b/Source/Bifrost.Specs/Sagas/for_SagaNarrator/when_concluding_a_saga_that_causes_an_exception.cs index e09060535..749905559 100644 --- a/Source/Bifrost.Specs/Sagas/for_SagaNarrator/when_concluding_a_saga_that_causes_an_exception.cs +++ b/Source/Bifrost.Specs/Sagas/for_SagaNarrator/when_concluding_a_saga_that_causes_an_exception.cs @@ -1,27 +1,30 @@ using System; -using Bifrost.Testing.Fakes.Sagas; using Bifrost.Sagas; +using Bifrost.Testing.Fakes.Sagas; using Machine.Specifications; namespace Bifrost.Specs.Sagas.for_SagaNarrator { [Subject(typeof(SagaNarrator))] - public class when_concluding_a_saga_that_has_begun_that_causes_an_exception : given.a_saga_narrator - { + public class when_concluding_a_saga_that_has_begun_that_causes_an_exception : given.a_saga_narrator + { static SagaWithOneChapterProperty saga; - static IChapter chapter; - static SagaConclusion conclusion; - - Establish context = () => - { - chapter = new SimpleChapter(); - saga = new SagaWithOneChapterProperty(chapter); - saga.Begin(); - librarian_mock.Setup(l => l.Close(saga)).Throws(new ArgumentException()); - }; + static IChapter chapter; + static SagaConclusion conclusion; + static Exception exception; + + Establish context = () => + { + chapter = new SimpleChapter(); + saga = new SagaWithOneChapterProperty(chapter); + saga.Begin(); + exception = new ArgumentException(); + librarian_mock.Setup(l => l.Close(saga)).Throws(exception); + }; - Because of = () => conclusion = narrator.Conclude(saga); + Because of = () => conclusion = narrator.Conclude(saga); - It should_have_a_non_successful_conclusion = () => conclusion.Success.ShouldBeFalse(); - } -} + It should_have_a_non_successful_conclusion = () => conclusion.Success.ShouldBeFalse(); + It should_publish_the_exception = () => exception_publisher_mock.Verify(m => m.Publish(exception)); + } +} \ No newline at end of file diff --git a/Source/Bifrost.Specs/Sagas/for_SagaNarrator/when_concluding_a_saga_that_is_already_concluded.cs b/Source/Bifrost.Specs/Sagas/for_SagaNarrator/when_concluding_a_saga_that_is_already_concluded.cs index e1936ff59..2981b3648 100644 --- a/Source/Bifrost.Specs/Sagas/for_SagaNarrator/when_concluding_a_saga_that_is_already_concluded.cs +++ b/Source/Bifrost.Specs/Sagas/for_SagaNarrator/when_concluding_a_saga_that_is_already_concluded.cs @@ -1,7 +1,6 @@ -using System; -using Bifrost.Testing.Fakes.Sagas; using Bifrost.Sagas; using Bifrost.Sagas.Exceptions; +using Bifrost.Testing.Fakes.Sagas; using Machine.Specifications; namespace Bifrost.Specs.Sagas.for_SagaNarrator @@ -14,17 +13,19 @@ public class when_concluding_a_saga_that_is_already_concluded : given.a_saga_nar static SagaConclusion conclusion; Establish context = () => - { - chapter = new SimpleChapter(); - saga = new SagaWithOneChapterProperty(chapter); - saga.Begin(); - saga.Conclude(); - }; + { + chapter = new SimpleChapter(); + saga = new SagaWithOneChapterProperty(chapter); + saga.Begin(); + saga.Conclude(); + }; Because of = () => conclusion = narrator.Conclude(saga); It should_have_a_non_successful_conclusion = () => conclusion.Success.ShouldBeFalse(); It should_have_an_invalid_saga_state_transition_exception = () => conclusion.Exception.ShouldBeOfExactType(); It should_not_have_called_the_on_conclude_method_again = () => saga.OnConcludeCalled.ShouldEqual(1); + It should_publish_the_exception = () => + exception_publisher_mock.Verify(m => m.Publish(Moq.It.IsAny())); } } \ No newline at end of file diff --git a/Source/Bifrost.Specs/Sagas/for_SagaNarrator/when_concluding_a_saga_that_is_new.cs b/Source/Bifrost.Specs/Sagas/for_SagaNarrator/when_concluding_a_saga_that_is_new.cs index e1981983d..e68482231 100644 --- a/Source/Bifrost.Specs/Sagas/for_SagaNarrator/when_concluding_a_saga_that_is_new.cs +++ b/Source/Bifrost.Specs/Sagas/for_SagaNarrator/when_concluding_a_saga_that_is_new.cs @@ -1,7 +1,6 @@ -using System; -using Bifrost.Testing.Fakes.Sagas; using Bifrost.Sagas; using Bifrost.Sagas.Exceptions; +using Bifrost.Testing.Fakes.Sagas; using Machine.Specifications; namespace Bifrost.Specs.Sagas.for_SagaNarrator @@ -13,15 +12,17 @@ public class when_concluding_a_saga_that_is_new : given.a_saga_narrator static IChapter chapter; static SagaConclusion conclusion; Establish context = () => - { - chapter = new SimpleChapter(); - saga = new SagaWithOneChapterProperty(chapter); - }; + { + chapter = new SimpleChapter(); + saga = new SagaWithOneChapterProperty(chapter); + }; Because of = () => conclusion = narrator.Conclude(saga); It should_have_a_non_successful_conclusion = () => conclusion.Success.ShouldBeFalse(); It should_have_an_invalid_saga_state_transition_exception = () => conclusion.Exception.ShouldBeOfExactType(); It should_not_have_called_the_on_conclude_method = () => saga.OnConcludeCalled.ShouldEqual(0); + It should_publish_the_exception = () => + exception_publisher_mock.Verify(m => m.Publish(Moq.It.IsAny())); } } \ No newline at end of file diff --git a/Source/Bifrost.Testing/CommandScenario.cs b/Source/Bifrost.Testing/CommandScenario.cs index ff5888d3d..bbaaae06f 100644 --- a/Source/Bifrost.Testing/CommandScenario.cs +++ b/Source/Bifrost.Testing/CommandScenario.cs @@ -7,6 +7,7 @@ using Bifrost.Commands; using Bifrost.Domain; using Bifrost.Events; +using Bifrost.Exceptions; using Bifrost.Execution; using Bifrost.Globalization; using Bifrost.Principal; @@ -79,7 +80,8 @@ public CommandScenario() command_context_manager, command_security_manager_mock.Object, command_validators_mock.Object, - localizer.Object); + localizer.Object, + Mock.Of()); null_validator_mock = new Mock>(); null_validator = null_validator_mock.Object; diff --git a/Source/Bifrost.Web/Services/RestServiceRouteHttpHandler.cs b/Source/Bifrost.Web/Services/RestServiceRouteHttpHandler.cs index 4e4c2c569..81d47187d 100644 --- a/Source/Bifrost.Web/Services/RestServiceRouteHttpHandler.cs +++ b/Source/Bifrost.Web/Services/RestServiceRouteHttpHandler.cs @@ -3,15 +3,12 @@ * Licensed under the MIT License. See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ using System; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.Security; using System.Web; using System.Web.SessionState; using Bifrost.Configuration; +using Bifrost.Exceptions; using Bifrost.Execution; using Bifrost.Security; -using Bifrost.Serialization; using Bifrost.Services; namespace Bifrost.Web.Services @@ -19,12 +16,13 @@ namespace Bifrost.Web.Services // Todo : add async support - performance gain! public class RestServiceRouteHttpHandler : IHttpHandler, IRequiresSessionState // IHttpAsyncHandler { - Type _type; - string _url; - IRequestParamsFactory _factory; - IRestServiceMethodInvoker _invoker; - IContainer _container; - private readonly ISecurityManager _securityManager; + readonly Type _type; + readonly string _url; + readonly IRequestParamsFactory _factory; + readonly IRestServiceMethodInvoker _invoker; + readonly IContainer _container; + readonly ISecurityManager _securityManager; + readonly IExceptionPublisher _exceptionPublisher; public RestServiceRouteHttpHandler(Type type, string url) : this( @@ -33,10 +31,18 @@ public RestServiceRouteHttpHandler(Type type, string url) Configure.Instance.Container.Get(), Configure.Instance.Container.Get(), Configure.Instance.Container, - Configure.Instance.Container.Get()) + Configure.Instance.Container.Get(), + Configure.Instance.Container.Get()) {} - public RestServiceRouteHttpHandler(Type type, string url, IRequestParamsFactory factory, IRestServiceMethodInvoker invoker, IContainer container, ISecurityManager securityManager) + public RestServiceRouteHttpHandler( + Type type, + string url, + IRequestParamsFactory factory, + IRestServiceMethodInvoker invoker, + IContainer container, + ISecurityManager securityManager, + IExceptionPublisher exceptionPublisher) { _type = type; _url = url; @@ -44,9 +50,10 @@ public RestServiceRouteHttpHandler(Type type, string url, IRequestParamsFactory _invoker = invoker; _container = container; _securityManager = securityManager; + _exceptionPublisher = exceptionPublisher; } - public bool IsReusable { get { return true; } } + public bool IsReusable => true; public void ProcessRequest(HttpContext context) { @@ -65,9 +72,10 @@ public void ProcessRequest(HttpContext context) var result = _invoker.Invoke(_url, serviceInstance, context.Request.Url, form); context.Response.Write(result); } - catch( Exception e) + catch (Exception e) { - if (e.InnerException != null && e.InnerException is HttpStatus.HttpStatusException) + _exceptionPublisher.Publish(e); + if (e.InnerException is HttpStatus.HttpStatusException) { var ex = e.InnerException as HttpStatus.HttpStatusException; context.Response.StatusCode = ex.Code; @@ -80,18 +88,5 @@ public void ProcessRequest(HttpContext context) } } } - - /* - public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData) - { - ProcessRequest(context); - - throw new System.NotImplementedException(); - } - - public void EndProcessRequest(IAsyncResult result) - { - throw new System.NotImplementedException(); - }*/ } } diff --git a/Source/Bifrost/Bifrost.csproj b/Source/Bifrost/Bifrost.csproj index c86de2217..59ea142ea 100644 --- a/Source/Bifrost/Bifrost.csproj +++ b/Source/Bifrost/Bifrost.csproj @@ -132,6 +132,9 @@ + + + diff --git a/Source/Bifrost/Commands/CommandCoordinator.cs b/Source/Bifrost/Commands/CommandCoordinator.cs index 52839c33d..9f0248bb4 100644 --- a/Source/Bifrost/Commands/CommandCoordinator.cs +++ b/Source/Bifrost/Commands/CommandCoordinator.cs @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ using System; using System.Reflection; +using Bifrost.Exceptions; using Bifrost.Globalization; using Bifrost.Lifecycle; using Bifrost.Sagas; @@ -14,46 +15,49 @@ namespace Bifrost.Commands /// Represents a ICommandCoordinator /// public class CommandCoordinator : ICommandCoordinator - { - readonly ICommandHandlerManager _commandHandlerManager; - readonly ICommandContextManager _commandContextManager; - readonly ICommandValidators _commandValidationService; + { + readonly ICommandHandlerManager _commandHandlerManager; + readonly ICommandContextManager _commandContextManager; + readonly ICommandValidators _commandValidationService; readonly ICommandSecurityManager _commandSecurityManager; - readonly ILocalizer _localizer; + readonly ILocalizer _localizer; + readonly IExceptionPublisher _exceptionPublisher; - - /// - /// Initializes a new instance of the CommandCoordinator - /// - /// A for handling commands - /// A for establishing a + /// + /// Initializes a new instance of the CommandCoordinator + /// + /// A for handling commands + /// A for establishing a /// A for dealing with security and commands - /// A for validating a before handling - /// A to use for controlling localization of current thread when handling commands - public CommandCoordinator( - ICommandHandlerManager commandHandlerManager, - ICommandContextManager commandContextManager, + /// A for validating a before handling + /// A to use for controlling localization of current thread when handling commands + /// An to send exceptions to + public CommandCoordinator( + ICommandHandlerManager commandHandlerManager, + ICommandContextManager commandContextManager, ICommandSecurityManager commandSecurityManager, ICommandValidators commandValidators, - ILocalizer localizer) - { - _commandHandlerManager = commandHandlerManager; - _commandContextManager = commandContextManager; + ILocalizer localizer, + IExceptionPublisher exceptionPublisher) + { + _commandHandlerManager = commandHandlerManager; + _commandContextManager = commandContextManager; _commandSecurityManager = commandSecurityManager; - _commandValidationService = commandValidators; - _localizer = localizer; - } + _commandValidationService = commandValidators; + _localizer = localizer; + _exceptionPublisher = exceptionPublisher; + } #pragma warning disable 1591 // Xml Comments - public CommandResult Handle(ISaga saga, ICommand command) - { - return Handle(_commandContextManager.EstablishForSaga(saga,command), command); - } + public CommandResult Handle(ISaga saga, ICommand command) + { + return Handle(_commandContextManager.EstablishForSaga(saga, command), command); + } - public CommandResult Handle(ICommand command) - { - return Handle( _commandContextManager.EstablishForCommand(command),command); - } + public CommandResult Handle(ICommand command) + { + return Handle(_commandContextManager.EstablishForCommand(command), command); + } CommandResult Handle(ITransaction transaction, ICommand command) { @@ -85,12 +89,14 @@ CommandResult Handle(ITransaction transaction, ICommand command) } catch (TargetInvocationException ex) { + _exceptionPublisher.Publish(ex); commandResult.Exception = ex.InnerException; transaction.Rollback(); } - catch (Exception exception) + catch (Exception ex) { - commandResult.Exception = exception; + _exceptionPublisher.Publish(ex); + commandResult.Exception = ex; transaction.Rollback(); } } @@ -102,14 +108,16 @@ CommandResult Handle(ITransaction transaction, ICommand command) } catch (TargetInvocationException ex) { + _exceptionPublisher.Publish(ex); commandResult.Exception = ex.InnerException; } catch (Exception ex) { + _exceptionPublisher.Publish(ex); commandResult.Exception = ex; } - return commandResult; + return commandResult; } #pragma warning restore 1591 // Xml Comments } diff --git a/Source/Bifrost/Exceptions/ExceptionPublisher.cs b/Source/Bifrost/Exceptions/ExceptionPublisher.cs new file mode 100644 index 000000000..3c7634abf --- /dev/null +++ b/Source/Bifrost/Exceptions/ExceptionPublisher.cs @@ -0,0 +1,38 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2008-2017 Dolittle. All rights reserved. + * Licensed under the MIT License. See LICENSE in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +using System; +using Bifrost.Execution; + +namespace Bifrost.Exceptions +{ + /// + /// Publishes exceptions to all s. + /// + public class ExceptionPublisher : IExceptionPublisher + { + readonly IInstancesOf _subscribers; + + /// + /// Initializes a new instance of . + /// + /// All known subscribers. + public ExceptionPublisher(IInstancesOf subscribers) + { + _subscribers = subscribers; + } + + /// + /// Publishes the exception to all . + /// + /// + public void Publish(Exception exception) + { + foreach (var subscriber in _subscribers) + { + subscriber.Handle(exception); + } + } + } +} \ No newline at end of file diff --git a/Source/Bifrost/Exceptions/IExceptionPublisher.cs b/Source/Bifrost/Exceptions/IExceptionPublisher.cs new file mode 100644 index 000000000..2497eb70d --- /dev/null +++ b/Source/Bifrost/Exceptions/IExceptionPublisher.cs @@ -0,0 +1,20 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2008-2017 Dolittle. All rights reserved. + * Licensed under the MIT License. See LICENSE in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +using System; + +namespace Bifrost.Exceptions +{ + /// + /// Publishes exceptions to all s. + /// + public interface IExceptionPublisher + { + /// + /// Publishes the exception to all . + /// + /// + void Publish(Exception exception); + } +} \ No newline at end of file diff --git a/Source/Bifrost/Exceptions/IExceptionSubscriber.cs b/Source/Bifrost/Exceptions/IExceptionSubscriber.cs new file mode 100644 index 000000000..582ef3a47 --- /dev/null +++ b/Source/Bifrost/Exceptions/IExceptionSubscriber.cs @@ -0,0 +1,24 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2008-2017 Dolittle. All rights reserved. + * Licensed under the MIT License. See LICENSE in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +using System; +using Bifrost.Conventions; + +namespace Bifrost.Exceptions +{ + /// + /// Implement this interface to subscribe to exceptions. + /// + /// + /// Types inheriting from this interface will be automatically registered. + /// An application can implement any number of these conventions. + /// + public interface IExceptionSubscriber : IConvention + { + /// + /// Handle the exception. + /// + void Handle(Exception exception); + } +} \ No newline at end of file diff --git a/Source/Bifrost/Read/QueryCoordinator.cs b/Source/Bifrost/Read/QueryCoordinator.cs index d9f074fe6..074473fee 100644 --- a/Source/Bifrost/Read/QueryCoordinator.cs +++ b/Source/Bifrost/Read/QueryCoordinator.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; +using Bifrost.Exceptions; using Bifrost.Execution; using Bifrost.Read.Validation; @@ -20,11 +21,12 @@ public class QueryCoordinator : IQueryCoordinator const string ExecuteMethodName = "Execute"; Dictionary _queryProviderTypesPerTargetType; - ITypeDiscoverer _typeDiscoverer; - IContainer _container; - IReadModelFilters _filters; - IQueryValidator _validator; - IFetchingSecurityManager _fetchingSecurityManager; + readonly ITypeDiscoverer _typeDiscoverer; + readonly IContainer _container; + readonly IFetchingSecurityManager _fetchingSecurityManager; + readonly IQueryValidator _validator; + readonly IReadModelFilters _filters; + readonly IExceptionPublisher _exceptionPublisher; /// /// Initializes a new instance of @@ -34,18 +36,21 @@ public class QueryCoordinator : IQueryCoordinator /// to use for securing queries /// to use for validating queries /// Filters used to filter any of the read models coming back after a query + /// An to send exceptions to public QueryCoordinator( ITypeDiscoverer typeDiscoverer, IContainer container, IFetchingSecurityManager fetchingSecurityManager, IQueryValidator validator, - IReadModelFilters filters) + IReadModelFilters filters, + IExceptionPublisher exceptionPublisher) { _typeDiscoverer = typeDiscoverer; _container = container; + _fetchingSecurityManager = fetchingSecurityManager; _validator = validator; _filters = filters; - _fetchingSecurityManager = fetchingSecurityManager; + _exceptionPublisher = exceptionPublisher; DiscoverQueryTypesPerTargetType(); } @@ -72,7 +77,6 @@ public QueryResult Execute(IQuery query, PagingInfo paging) result.Items = new object[0]; return result; } - var property = GetQueryPropertyFromQuery(query); var actualQuery = property.GetValue(query, null); @@ -84,10 +88,12 @@ public QueryResult Execute(IQuery query, PagingInfo paging) } catch (TargetInvocationException ex) { + _exceptionPublisher.Publish(ex.InnerException); result.Exception = ex.InnerException; } catch (Exception ex) { + _exceptionPublisher.Publish(ex); result.Exception = ex; } diff --git a/Source/Bifrost/Sagas/SagaNarrator.cs b/Source/Bifrost/Sagas/SagaNarrator.cs index d47f2bf4a..474813499 100644 --- a/Source/Bifrost/Sagas/SagaNarrator.cs +++ b/Source/Bifrost/Sagas/SagaNarrator.cs @@ -5,6 +5,7 @@ using System; using System.Linq; using Bifrost.Events; +using Bifrost.Exceptions; using Bifrost.Execution; namespace Bifrost.Sagas @@ -18,6 +19,7 @@ public class SagaNarrator : ISagaNarrator readonly IContainer _container; readonly IChapterValidationService _chapterValidationService; readonly IEventStore _eventStore; + readonly IExceptionPublisher _exceptionPublisher; /// /// Initializes a new instance of @@ -26,16 +28,19 @@ public class SagaNarrator : ISagaNarrator /// for creating instances /// for validating chapters /// + /// An to send exceptions to public SagaNarrator( ISagaLibrarian librarian, IContainer container, IChapterValidationService chapterValidationService, - IEventStore eventStore) + IEventStore eventStore, + IExceptionPublisher exceptionPublisher) { _librarian = librarian; _container = container; _chapterValidationService = chapterValidationService; _eventStore = eventStore; + _exceptionPublisher = exceptionPublisher; } #pragma warning disable 1591 // Xml Comments @@ -62,8 +67,10 @@ public SagaConclusion Conclude(ISaga saga) saga.Conclude(); saga.SaveUncommittedEventsToEventStore(_eventStore); _librarian.Close(saga); - } catch( Exception ex) + } + catch (Exception ex) { + _exceptionPublisher.Publish(ex); conclusion.Exception = ex; } From ffeaf7b3d660800736f8feb95e3928450dda95c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=B8rge=20Nordli?= Date: Tue, 23 Feb 2016 13:09:58 +0100 Subject: [PATCH 2/3] Implement OrderedInstancesOf #686 From the ProCoSys fork. Instead of depending on IInstancesOf, any implementation can depend on IOrderedInstancesOf instead. By doing this, the ordering of Ts are guaranteed: Implementations of T can specify their ordering, either explicitly by and Order attribute, or implicitly via an After attribute. --- Source/Bifrost.Specs/Bifrost.Specs.csproj | 10 ++ .../given/an_ordered_instances_of.cs | 39 ++++++++ .../when_instances_are_ordered.cs | 28 ++++++ .../when_instances_are_ordered_incorrectly.cs | 29 ++++++ ...en_instances_have_circular_dependencies.cs | 33 +++++++ .../when_instances_have_dependencies.cs | 29 ++++++ .../when_there_are_no_instances.cs | 16 +++ .../for_TypeExtensions/Custom1Attribute.cs | 8 ++ .../for_TypeExtensions/Custom2Attribute.cs | 9 ++ .../when_checking_if_types_have_attribute.cs | 30 ++++++ .../when_getting_attributes.cs | 33 +++++++ .../Fakes/Configuration/TestInstancesOf.cs | 4 +- Source/Bifrost/Bifrost.csproj | 5 + Source/Bifrost/Execution/AfterAttribute.cs | 31 ++++++ .../Execution/CyclicDependencyException.cs | 30 ++++++ Source/Bifrost/Execution/IInstancesOf.cs | 2 +- .../Bifrost/Execution/IOrderedInstancesOf.cs | 18 ++++ Source/Bifrost/Execution/InstancesOf.cs | 2 +- Source/Bifrost/Execution/OrderAttribute.cs | 31 ++++++ .../Bifrost/Execution/OrderedInstancesOf.cs | 75 ++++++++++++++ Source/Bifrost/Extensions/TypeExtensions.cs | 97 +++++++++++++++---- 21 files changed, 534 insertions(+), 25 deletions(-) create mode 100644 Source/Bifrost.Specs/Execution/for_OrderedInstancesOf/given/an_ordered_instances_of.cs create mode 100644 Source/Bifrost.Specs/Execution/for_OrderedInstancesOf/when_instances_are_ordered.cs create mode 100644 Source/Bifrost.Specs/Execution/for_OrderedInstancesOf/when_instances_are_ordered_incorrectly.cs create mode 100644 Source/Bifrost.Specs/Execution/for_OrderedInstancesOf/when_instances_have_circular_dependencies.cs create mode 100644 Source/Bifrost.Specs/Execution/for_OrderedInstancesOf/when_instances_have_dependencies.cs create mode 100644 Source/Bifrost.Specs/Execution/for_OrderedInstancesOf/when_there_are_no_instances.cs create mode 100644 Source/Bifrost.Specs/Extensions/for_TypeExtensions/Custom1Attribute.cs create mode 100644 Source/Bifrost.Specs/Extensions/for_TypeExtensions/Custom2Attribute.cs create mode 100644 Source/Bifrost.Specs/Extensions/for_TypeExtensions/when_checking_if_types_have_attribute.cs create mode 100644 Source/Bifrost.Specs/Extensions/for_TypeExtensions/when_getting_attributes.cs create mode 100644 Source/Bifrost/Execution/AfterAttribute.cs create mode 100644 Source/Bifrost/Execution/CyclicDependencyException.cs create mode 100644 Source/Bifrost/Execution/IOrderedInstancesOf.cs create mode 100644 Source/Bifrost/Execution/OrderAttribute.cs create mode 100644 Source/Bifrost/Execution/OrderedInstancesOf.cs diff --git a/Source/Bifrost.Specs/Bifrost.Specs.csproj b/Source/Bifrost.Specs/Bifrost.Specs.csproj index daa7374e2..840d96528 100644 --- a/Source/Bifrost.Specs/Bifrost.Specs.csproj +++ b/Source/Bifrost.Specs/Bifrost.Specs.csproj @@ -326,6 +326,12 @@ + + + + + + @@ -374,6 +380,10 @@ + + + + diff --git a/Source/Bifrost.Specs/Execution/for_OrderedInstancesOf/given/an_ordered_instances_of.cs b/Source/Bifrost.Specs/Execution/for_OrderedInstancesOf/given/an_ordered_instances_of.cs new file mode 100644 index 000000000..9a6cad021 --- /dev/null +++ b/Source/Bifrost.Specs/Execution/for_OrderedInstancesOf/given/an_ordered_instances_of.cs @@ -0,0 +1,39 @@ +using System.Linq; +using Bifrost.Execution; +using Machine.Specifications; +using Moq; + +namespace Bifrost.Specs.Execution.for_OrderedInstancesOf.given +{ + public class an_ordered_instances_of + { + public interface IDummy { } + + protected static Mock type_discoverer_mock; + protected static Mock container_mock; + protected static OrderedInstancesOf ordered_instances_of; + + protected static IDummy[] result; + + Establish context = () => + { + type_discoverer_mock = new Mock(); + container_mock = new Mock(); + }; + + protected static void Register(params IDummy[] instances) + { + type_discoverer_mock + .Setup(m => m.FindMultiple()) + .Returns(instances.Select(i => i.GetType())); + foreach (var instance in instances) + { + container_mock + .Setup(m => m.Get(instance.GetType())) + .Returns(instance); + } + + ordered_instances_of = new OrderedInstancesOf(type_discoverer_mock.Object, container_mock.Object); + } + } +} \ No newline at end of file diff --git a/Source/Bifrost.Specs/Execution/for_OrderedInstancesOf/when_instances_are_ordered.cs b/Source/Bifrost.Specs/Execution/for_OrderedInstancesOf/when_instances_are_ordered.cs new file mode 100644 index 000000000..7169b2ed9 --- /dev/null +++ b/Source/Bifrost.Specs/Execution/for_OrderedInstancesOf/when_instances_are_ordered.cs @@ -0,0 +1,28 @@ +using System.Linq; +using Bifrost.Execution; +using Machine.Specifications; + +namespace Bifrost.Specs.Execution.for_OrderedInstancesOf +{ + [Subject(typeof(OrderedInstancesOf<>))] + public sealed class when_instances_have_dependencies : given.an_ordered_instances_of + { + class Dummy1 : IDummy { } + + [After(typeof(Dummy1))] + class Dummy2 : IDummy { } + + [After(typeof(Dummy2))] + class Dummy3 : IDummy { } + + static readonly IDummy dummy1 = new Dummy1(); + static readonly IDummy dummy2 = new Dummy2(); + static readonly IDummy dummy3 = new Dummy3(); + + Establish context = () => Register(dummy3, dummy2, dummy1); + + Because of = () => result = ordered_instances_of.ToArray(); + + It should_return_correct_order = () => result.SequenceEqual(new[] {dummy1, dummy2, dummy3 }).ShouldBeTrue(); + } +} \ No newline at end of file diff --git a/Source/Bifrost.Specs/Execution/for_OrderedInstancesOf/when_instances_are_ordered_incorrectly.cs b/Source/Bifrost.Specs/Execution/for_OrderedInstancesOf/when_instances_are_ordered_incorrectly.cs new file mode 100644 index 000000000..7cbe8ee10 --- /dev/null +++ b/Source/Bifrost.Specs/Execution/for_OrderedInstancesOf/when_instances_are_ordered_incorrectly.cs @@ -0,0 +1,29 @@ +using System; +using System.Linq; +using Bifrost.Execution; +using Machine.Specifications; + +namespace Bifrost.Specs.Execution.for_OrderedInstancesOf +{ + [Subject(typeof(OrderedInstancesOf<>))] + public sealed class when_instances_are_ordered_incorrectly : given.an_ordered_instances_of + { + static Exception exception; + + [Order(1000)] + class Dummy1 : IDummy { } + + [Order(3)] + [After(typeof(Dummy1))] + class Dummy2 : IDummy { } + + static readonly IDummy dummy1 = new Dummy1(); + static readonly IDummy dummy2 = new Dummy2(); + + Establish context = () => Register(dummy2, dummy1); + + Because of = () => result = ordered_instances_of.ToArray(); + + It should_return_correct_order = () => result.SequenceEqual(new[] { dummy1, dummy2 }).ShouldBeTrue(); + } +} \ No newline at end of file diff --git a/Source/Bifrost.Specs/Execution/for_OrderedInstancesOf/when_instances_have_circular_dependencies.cs b/Source/Bifrost.Specs/Execution/for_OrderedInstancesOf/when_instances_have_circular_dependencies.cs new file mode 100644 index 000000000..bb7bf2e85 --- /dev/null +++ b/Source/Bifrost.Specs/Execution/for_OrderedInstancesOf/when_instances_have_circular_dependencies.cs @@ -0,0 +1,33 @@ +using System; +using System.Linq; +using Bifrost.Execution; +using Machine.Specifications; +using Moq; +using It = Machine.Specifications.It; + +namespace Bifrost.Specs.Execution.for_OrderedInstancesOf +{ + [Subject(typeof(OrderedInstancesOf<>))] + public sealed class when_instances_have_circular_dependencies : given.an_ordered_instances_of + { + static Exception exception; + + [After(typeof(Dummy2))] + class Dummy1 : IDummy { } + + [After(typeof(Dummy1))] + class Dummy2 : IDummy { } + + static readonly IDummy dummy1 = new Dummy1(); + static readonly IDummy dummy2 = new Dummy2(); + + Establish context = () => Register(dummy1, dummy2); + + Because of = () => exception = Catch.Only(() => ordered_instances_of.ToArray()); + + It should_throw_exception = () => exception.Message.ShouldContain("circular"); + + It should_not_have_created_the_instances = () => + container_mock.Verify(m => m.Get(Moq.It.IsAny()), Times.Never); + } +} \ No newline at end of file diff --git a/Source/Bifrost.Specs/Execution/for_OrderedInstancesOf/when_instances_have_dependencies.cs b/Source/Bifrost.Specs/Execution/for_OrderedInstancesOf/when_instances_have_dependencies.cs new file mode 100644 index 000000000..24e888c5b --- /dev/null +++ b/Source/Bifrost.Specs/Execution/for_OrderedInstancesOf/when_instances_have_dependencies.cs @@ -0,0 +1,29 @@ +using System.Linq; +using Bifrost.Execution; +using Machine.Specifications; + +namespace Bifrost.Specs.Execution.for_OrderedInstancesOf +{ + [Subject(typeof(OrderedInstancesOf<>))] + public sealed class when_instances_are_ordered : given.an_ordered_instances_of + { + [Order(1)] + class Dummy1 : IDummy { } + + [Order(2)] + class Dummy2 : IDummy { } + + [Order(3)] + class Dummy3 : IDummy { } + + static readonly IDummy dummy1 = new Dummy1(); + static readonly IDummy dummy2 = new Dummy2(); + static readonly IDummy dummy3 = new Dummy3(); + + Establish context = () => Register(dummy3, dummy2, dummy1); + + Because of = () => result = ordered_instances_of.ToArray(); + + It should_return_correct_order = () => result.SequenceEqual(new[] {dummy1, dummy2, dummy3 }).ShouldBeTrue(); + } +} \ No newline at end of file diff --git a/Source/Bifrost.Specs/Execution/for_OrderedInstancesOf/when_there_are_no_instances.cs b/Source/Bifrost.Specs/Execution/for_OrderedInstancesOf/when_there_are_no_instances.cs new file mode 100644 index 000000000..58728fe31 --- /dev/null +++ b/Source/Bifrost.Specs/Execution/for_OrderedInstancesOf/when_there_are_no_instances.cs @@ -0,0 +1,16 @@ +using System.Linq; +using Bifrost.Execution; +using Machine.Specifications; + +namespace Bifrost.Specs.Execution.for_OrderedInstancesOf +{ + [Subject(typeof(OrderedInstancesOf<>))] + public sealed class when_there_are_no_instances : given.an_ordered_instances_of + { + Establish context = () => Register(); + + Because of = () => result = ordered_instances_of.ToArray(); + + It should_not_return_anything = () => result.ShouldBeEmpty(); + } +} \ No newline at end of file diff --git a/Source/Bifrost.Specs/Extensions/for_TypeExtensions/Custom1Attribute.cs b/Source/Bifrost.Specs/Extensions/for_TypeExtensions/Custom1Attribute.cs new file mode 100644 index 000000000..1cd55590e --- /dev/null +++ b/Source/Bifrost.Specs/Extensions/for_TypeExtensions/Custom1Attribute.cs @@ -0,0 +1,8 @@ +using System; + +namespace Bifrost.Specs.Extensions.for_TypeExtensions +{ + public class Custom1Attribute : Attribute + { + } +} \ No newline at end of file diff --git a/Source/Bifrost.Specs/Extensions/for_TypeExtensions/Custom2Attribute.cs b/Source/Bifrost.Specs/Extensions/for_TypeExtensions/Custom2Attribute.cs new file mode 100644 index 000000000..f9314eaf3 --- /dev/null +++ b/Source/Bifrost.Specs/Extensions/for_TypeExtensions/Custom2Attribute.cs @@ -0,0 +1,9 @@ +using System; + +namespace Bifrost.Specs.Extensions.for_TypeExtensions +{ + public class Custom2Attribute : Attribute + { + public string Data { get; set; } + } +} \ No newline at end of file diff --git a/Source/Bifrost.Specs/Extensions/for_TypeExtensions/when_checking_if_types_have_attribute.cs b/Source/Bifrost.Specs/Extensions/for_TypeExtensions/when_checking_if_types_have_attribute.cs new file mode 100644 index 000000000..8689b8e51 --- /dev/null +++ b/Source/Bifrost.Specs/Extensions/for_TypeExtensions/when_checking_if_types_have_attribute.cs @@ -0,0 +1,30 @@ +using System; +using Bifrost.Extensions; +using Machine.Specifications; + +namespace Bifrost.Specs.Extensions.for_TypeExtensions +{ + public class when_checking_if_types_have_attributes + { + [Custom1] + interface ITest { } + + [Custom2] + class Type1 : ITest { } + + class Type2 : Type1 { } + + static Type type_1 = new Type1().GetType(); + static Type type_2 = new Type2().GetType(); + + It should_find_attribute_directly_on_class = () => type_1.HasAttribute().ShouldBeTrue(); + + It should_not_search_interfaces_by_default = () => type_2.HasAttribute().ShouldBeFalse(); + + It should_not_search_supertype_by_default = () => type_2.HasAttribute().ShouldBeFalse(); + + It should_search_interfaces_if_asked = () => type_2.HasAttribute(true).ShouldBeTrue(); + + It should_search_supertypes_if_asked = () => type_2.HasAttribute(true).ShouldBeTrue(); + } +} \ No newline at end of file diff --git a/Source/Bifrost.Specs/Extensions/for_TypeExtensions/when_getting_attributes.cs b/Source/Bifrost.Specs/Extensions/for_TypeExtensions/when_getting_attributes.cs new file mode 100644 index 000000000..c9c06ff89 --- /dev/null +++ b/Source/Bifrost.Specs/Extensions/for_TypeExtensions/when_getting_attributes.cs @@ -0,0 +1,33 @@ +using System; +using System.Linq; +using Bifrost.Extensions; +using Machine.Specifications; + +namespace Bifrost.Specs.Extensions.for_TypeExtensions +{ + public class when_getting_attributes + { + [Custom1] + interface ITest { } + + [Custom1, Custom2(Data = "Hello")] + class Type1 : ITest { } + + class Type2 : Type1 { } + + static Type type_1 = new Type1().GetType(); + static Type type_2 = new Type2().GetType(); + + It should_find_one_attribute_directly_on_class = () => type_1.GetAttributes().Count().ShouldEqual(1); + + It should_get_attribute_data = () => type_1.GetAttributes().First().Data.ShouldEqual("Hello"); + + It should_not_search_interfaces_by_default = () => type_2.GetAttributes().ShouldBeEmpty(); + + It should_not_search_supertype_by_default = () => type_2.GetAttributes().ShouldBeEmpty(); + + It should_search_interfaces_if_asked = () => type_2.GetAttributes(true).Count().ShouldEqual(2); + + It should_search_supertypes_if_asked = () => type_2.GetAttributes(true).Count().ShouldEqual(1); + } +} \ No newline at end of file diff --git a/Source/Bifrost.Testing/Fakes/Configuration/TestInstancesOf.cs b/Source/Bifrost.Testing/Fakes/Configuration/TestInstancesOf.cs index 7e5749773..0077073bc 100644 --- a/Source/Bifrost.Testing/Fakes/Configuration/TestInstancesOf.cs +++ b/Source/Bifrost.Testing/Fakes/Configuration/TestInstancesOf.cs @@ -4,9 +4,9 @@ namespace Bifrost.Testing.Fakes.Configuration { - public class TestInstancesOf : IInstancesOf where T : class + public class TestInstancesOf : IInstancesOf, IOrderedInstancesOf where T : class { - IEnumerable _instances; + readonly IEnumerable _instances; public TestInstancesOf(params T[] instances) { diff --git a/Source/Bifrost/Bifrost.csproj b/Source/Bifrost/Bifrost.csproj index 59ea142ea..f175b38bf 100644 --- a/Source/Bifrost/Bifrost.csproj +++ b/Source/Bifrost/Bifrost.csproj @@ -135,7 +135,10 @@ + + + @@ -175,6 +178,8 @@ + + diff --git a/Source/Bifrost/Execution/AfterAttribute.cs b/Source/Bifrost/Execution/AfterAttribute.cs new file mode 100644 index 000000000..c3b408167 --- /dev/null +++ b/Source/Bifrost/Execution/AfterAttribute.cs @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2008-2017 Dolittle. All rights reserved. + * Licensed under the MIT License. See LICENSE in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +using System; + +namespace Bifrost.Execution +{ + /// + /// Indicates that a type used for injecting into an must be ordered after + /// another type injected into the same . + /// + [AttributeUsage(AttributeTargets.Class)] + public class AfterAttribute : Attribute + { + /// + /// Initializes a new instance of . + /// + /// The types the decorated type is dependant upon, and must come after. + public AfterAttribute(params Type[] dependantTypes) + { + DependantTypes = dependantTypes; + } + + /// + /// List of types that must be injected before this decorated type is injected. + /// + /// This will take precedence over any attributes. + public Type[] DependantTypes { get; set; } + } +} \ No newline at end of file diff --git a/Source/Bifrost/Execution/CyclicDependencyException.cs b/Source/Bifrost/Execution/CyclicDependencyException.cs new file mode 100644 index 000000000..398981a6b --- /dev/null +++ b/Source/Bifrost/Execution/CyclicDependencyException.cs @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2008-2017 Dolittle. All rights reserved. + * Licensed under the MIT License. See LICENSE in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +using System; + +namespace Bifrost.Execution +{ + /// + /// The exception that is thrown if classes injected into have cyclic + /// dependencies. + /// + public class CyclicDependencyException : Exception + { + /// + /// Initializes a new instance of . + /// + public CyclicDependencyException() + { + } + + /// + /// Initializes a new instance of . + /// + /// The exception message. + public CyclicDependencyException(string message) : base(message) + { + } + } +} \ No newline at end of file diff --git a/Source/Bifrost/Execution/IInstancesOf.cs b/Source/Bifrost/Execution/IInstancesOf.cs index 63012caba..14ffa976d 100644 --- a/Source/Bifrost/Execution/IInstancesOf.cs +++ b/Source/Bifrost/Execution/IInstancesOf.cs @@ -11,7 +11,7 @@ namespace Bifrost.Execution /// when enumerated over /// /// Base type to discover for - must be an abstract class or an interface - public interface IInstancesOf : IEnumerable + public interface IInstancesOf : IEnumerable where T : class { } diff --git a/Source/Bifrost/Execution/IOrderedInstancesOf.cs b/Source/Bifrost/Execution/IOrderedInstancesOf.cs new file mode 100644 index 000000000..548f0ea01 --- /dev/null +++ b/Source/Bifrost/Execution/IOrderedInstancesOf.cs @@ -0,0 +1,18 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2008-2017 Dolittle. All rights reserved. + * Licensed under the MIT License. See LICENSE in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +using System.Collections.Generic; + +namespace Bifrost.Execution +{ + /// + /// Defines something that can discover types and give instance of these types + /// in a predefined order when enumerated over. + /// + /// Base type to discover for - must be an abstract class or an interface. + /// Enumeration will throw a if cycles are detected. + public interface IOrderedInstancesOf : IEnumerable where T : class + { + } +} diff --git a/Source/Bifrost/Execution/InstancesOf.cs b/Source/Bifrost/Execution/InstancesOf.cs index cfbacf8e8..9c6ab6dd8 100644 --- a/Source/Bifrost/Execution/InstancesOf.cs +++ b/Source/Bifrost/Execution/InstancesOf.cs @@ -20,7 +20,7 @@ public class InstancesOf : IInstancesOf IContainer _container; /// - /// Initalizes an instance of + /// Initalizes an instance of /// /// used for discovering types /// used for managing instances of the types when needed diff --git a/Source/Bifrost/Execution/OrderAttribute.cs b/Source/Bifrost/Execution/OrderAttribute.cs new file mode 100644 index 000000000..3a5a596c7 --- /dev/null +++ b/Source/Bifrost/Execution/OrderAttribute.cs @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2008-2017 Dolittle. All rights reserved. + * Licensed under the MIT License. See LICENSE in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +using System; + +namespace Bifrost.Execution +{ + /// + /// Indicates that a type used for injecting into an has a specific + /// ordering relative to other types decorated with this attribute. + /// + [AttributeUsage(AttributeTargets.Class)] + public class OrderAttribute : Attribute + { + /// + /// Initializes a new instance of . + /// + /// The order of the decorated type. + public OrderAttribute(int order) + { + Order = order; + } + + /// + /// The order of the decorated type. + /// + /// Types without this attribute have order 0. + public int Order { get; private set; } + } +} \ No newline at end of file diff --git a/Source/Bifrost/Execution/OrderedInstancesOf.cs b/Source/Bifrost/Execution/OrderedInstancesOf.cs new file mode 100644 index 000000000..1903d2b64 --- /dev/null +++ b/Source/Bifrost/Execution/OrderedInstancesOf.cs @@ -0,0 +1,75 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2008-2017 Dolittle. All rights reserved. + * Licensed under the MIT License. See LICENSE in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using Bifrost.Extensions; + +namespace Bifrost.Execution +{ + /// + /// Represents an implementation of . + /// + /// Base type to discover for - must be an abstract class or an interface. + public class OrderedInstancesOf : IOrderedInstancesOf where T : class + { + readonly IEnumerable _types; + readonly IContainer _container; + + /// + /// Initializes an instance of . + /// + /// used for discovering types. + /// used for managing instances of the types when needed. + public OrderedInstancesOf(ITypeDiscoverer typeDiscoverer, IContainer container) + { + _types = typeDiscoverer.FindMultiple(); + _container = container; + } + +#pragma warning disable 1591 // Xml Comments + public IEnumerator GetEnumerator() + { + IList queue = _types.OrderBy(Order).ToList(); + ISet initialized = new HashSet(); + + while (queue.Count > 0) + { + var progress = false; + var ready = queue + .Where(s => s + .GetAttributes() + .SelectMany(a => a.DependantTypes) + .All(initialized.Contains)) + .ToList(); + foreach (var type in ready) + { + yield return _container.Get(type) as T; + initialized.Add(type); + queue.Remove(type); + progress = true; + } + + if (!progress) + { + throw new CyclicDependencyException( + $"Circular dependency detected when ordering instances of {typeof(T)} between these types: {string.Join(", ", queue)}"); + } + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } +#pragma warning restore 1591 // Xml Comments + + static int Order(Type type) + { + return type.GetAttributes().Select(a => a.Order).FirstOrDefault(); + } + } +} diff --git a/Source/Bifrost/Extensions/TypeExtensions.cs b/Source/Bifrost/Extensions/TypeExtensions.cs index 29753f402..ddc9339cc 100644 --- a/Source/Bifrost/Extensions/TypeExtensions.cs +++ b/Source/Bifrost/Extensions/TypeExtensions.cs @@ -9,23 +9,36 @@ namespace Bifrost.Extensions { - /// - /// Provides a set of methods for working with types - /// - public static class TypeExtensions - { - static HashSet AdditionalPrimitiveTypes = new HashSet - { - typeof(decimal),typeof(string),typeof(Guid),typeof(DateTime),typeof(DateTimeOffset),typeof(TimeSpan) - }; + /// + /// Provides a set of methods for working with types + /// + public static class TypeExtensions + { + static readonly HashSet AdditionalPrimitiveTypes = new HashSet + { + typeof (decimal), + typeof (string), + typeof (Guid), + typeof (DateTime), + typeof (DateTimeOffset), + typeof (TimeSpan) + }; - static HashSet NumericTypes = new HashSet + static readonly HashSet NumericTypes = new HashSet { - typeof(byte), typeof(sbyte), - typeof(short), typeof(int), typeof(long), - typeof(ushort), typeof(uint), typeof(ulong), - typeof(double), typeof(decimal), typeof(Single) + typeof (byte), + typeof (sbyte), + typeof (short), + typeof (int), + typeof (long), + typeof (ushort), + typeof (uint), + typeof (ulong), + typeof (double), + typeof (decimal), + typeof (Single) }; + #pragma warning disable 1591 // Xml Comments static ITypeInfo GetTypeInfo(Type type) { @@ -37,14 +50,56 @@ static ITypeInfo GetTypeInfo(Type type) #pragma warning restore 1591 // Xml Comments /// - /// Check if a type has an attribute associated with it + /// Check if a type has an attribute associated with it. The inheritance chain is not used to find the attribute. /// - /// Type to check - /// True if there is an attribute, false if not + /// The type which is searched for the attributes. + /// Type to check. + /// True if there is an attribute, false if not. public static bool HasAttribute(this Type type) where T : Attribute { - var attributes = type.GetTypeInfo().GetCustomAttributes(typeof(T), false).ToArray(); - return attributes.Length == 1; + return type.HasAttribute(false); + } + + /// + /// Check if a type has an attribute associated with it. + /// + /// The type which is searched for the attributes. + /// Specifies whether to search this member's inheritance chain to find the attributes. + /// Interfaces will be searched, too. + /// Type to check. + /// True if there is an attribute, false if not. + public static bool HasAttribute(this Type type, bool inherit) where T : Attribute + { + return type.GetAttributes(inherit).Any(); + } + + /// Searches and returns attributes. The inheritance chain is not used to find the attributes. + /// The type of attribute to search for. + /// The type which is searched for the attributes. + /// Returns all attributes of the given type. + public static IEnumerable GetAttributes(this Type type) where T : Attribute + { + return type.GetAttributes(false); + } + + /// Searches and returns attributes. + /// The type of attribute to search for. + /// The type which is searched for the attributes. + /// Specifies whether to search this member's inheritance chain to find the attributes. + /// Interfaces will be searched, too. + /// Returns all attributes. + public static IEnumerable GetAttributes(this Type type, bool inherit) where T : Attribute + { + var attributes = type.GetTypeInfo().GetCustomAttributes(inherit); + if (inherit) + { + attributes = attributes.Concat( + type + .GetInterfaces() + .SelectMany(i => i.GetCustomAttributes(false))); + } + + return attributes; } /// @@ -85,7 +140,7 @@ public static bool IsDate(this Type type) /// True if type is a boolean, false if not public static bool IsBoolean(this Type type) { - return type == typeof (Boolean) || Nullable.GetUnderlyingType(type) == typeof (Boolean); + return type == typeof (bool) || Nullable.GetUnderlyingType(type) == typeof (bool); } /// @@ -226,7 +281,7 @@ public static IEnumerable AllBaseAndImplementingTypes(this Type type) return type.BaseTypes() .Concat(type.GetTypeInfo().GetInterfaces()) .SelectMany(ThisAndMaybeOpenType) - .Where(t=>t != type && t != typeof(Object)); + .Where(t => t != type && t != typeof (object)); } static IEnumerable BaseTypes(this Type type) From 42be6f0c745f496e898363697895996f4f6aef4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=B8rge=20Nordli?= Date: Fri, 11 Mar 2016 12:46:36 +0100 Subject: [PATCH 3/3] Ensure Initialize is run only once in CommandHandlerInvoker #686 Original issue: ProCoSys/Bifrost#20 --- Source/Bifrost.Specs/Bifrost.Specs.csproj | 1 + ...n_receiving_asynchronous_initialization.cs | 36 +++++ .../Bifrost/Commands/CommandHandlerInvoker.cs | 123 ++++++++++-------- 3 files changed, 107 insertions(+), 53 deletions(-) create mode 100644 Source/Bifrost.Specs/Commands/for_CommandHandlerInvoker/when_receiving_asynchronous_initialization.cs diff --git a/Source/Bifrost.Specs/Bifrost.Specs.csproj b/Source/Bifrost.Specs/Bifrost.Specs.csproj index 840d96528..0d4f9d16f 100644 --- a/Source/Bifrost.Specs/Bifrost.Specs.csproj +++ b/Source/Bifrost.Specs/Bifrost.Specs.csproj @@ -106,6 +106,7 @@ + diff --git a/Source/Bifrost.Specs/Commands/for_CommandHandlerInvoker/when_receiving_asynchronous_initialization.cs b/Source/Bifrost.Specs/Commands/for_CommandHandlerInvoker/when_receiving_asynchronous_initialization.cs new file mode 100644 index 000000000..27b017133 --- /dev/null +++ b/Source/Bifrost.Specs/Commands/for_CommandHandlerInvoker/when_receiving_asynchronous_initialization.cs @@ -0,0 +1,36 @@ +using System; +using System.Threading; +using Bifrost.Commands; +using Machine.Specifications; +using Moq; +using It = Machine.Specifications.It; + +namespace Bifrost.Specs.Commands.for_CommandHandlerInvoker +{ + [Subject(Subjects.handling_commands)] + public class when_receiving_asynchronous_initialization : given.a_command_handler_invoker_with_no_command_handlers + { + protected static bool result; + + Because of = () => + { + var command = new Command(); + var thread = new Thread(() => invoker.TryHandle(command)); + + type_discoverer_mock + .Setup(t => t.FindMultiple()) + .Callback( + () => + { + thread.Start(); + Thread.Sleep(50); + }) + .Returns(new Type[0]); + result = invoker.TryHandle(command); + thread.Join(); + }; + + It should_initialize_only_once = () => + type_discoverer_mock.Verify(m => m.FindMultiple(), Times.Once); + } +} diff --git a/Source/Bifrost/Commands/CommandHandlerInvoker.cs b/Source/Bifrost/Commands/CommandHandlerInvoker.cs index 5e6952f7b..0c342dfa3 100644 --- a/Source/Bifrost/Commands/CommandHandlerInvoker.cs +++ b/Source/Bifrost/Commands/CommandHandlerInvoker.cs @@ -11,67 +11,84 @@ namespace Bifrost.Commands { - /// - /// Represents a ICommandHandlerInvoker for handling - /// command handlers that have methods called Handle() and takes specific commands - /// in as parameters - /// - [Singleton] - public class CommandHandlerInvoker : ICommandHandlerInvoker - { + /// + /// Represents a ICommandHandlerInvoker for handling + /// command handlers that have methods called Handle() and takes specific commands + /// in as parameters + /// + [Singleton] + public class CommandHandlerInvoker : ICommandHandlerInvoker + { const string HandleMethodName = "Handle"; - readonly ITypeDiscoverer _discoverer; - readonly IContainer _container; + readonly ITypeDiscoverer _discoverer; + readonly IContainer _container; readonly Dictionary _commandHandlers = new Dictionary(); - bool _initialized; + readonly object _initializationLock = new object(); + bool _initialized; - /// - /// Initializes a new instance of CommandHandlerInvoker - /// - /// A to use for discovering command handlers - /// A to use for getting instances of objects - public CommandHandlerInvoker(ITypeDiscoverer discoverer, IContainer container) - { - _discoverer = discoverer; - _container = container; - _initialized = false; - } + /// + /// Initializes a new instance of CommandHandlerInvoker + /// + /// A to use for discovering command handlers + /// A to use for getting instances of objects + public CommandHandlerInvoker(ITypeDiscoverer discoverer, IContainer container) + { + _discoverer = discoverer; + _container = container; + _initialized = false; + } - private void Initialize() - { - var handlers = _discoverer.FindMultiple(); + void EnsureInitialized() + { + if (_initialized) + { + return; + } + + lock (_initializationLock) + { + if (!_initialized) + { + Initialize(); + _initialized = true; + } + } + } + + void Initialize() + { + var handlers = _discoverer.FindMultiple(); handlers.ForEach(Register); - _initialized = true; - } + } - /// - /// Register a command handler explicitly - /// - /// - /// - /// The registration process will look into the handler and find methods that - /// are called Handle() and takes a command as parameter - /// - public void Register(Type handlerType) - { - var allMethods = handlerType.GetRuntimeMethods().Where(m => m.IsPublic || !m.IsStatic); - var query = from m in allMethods - where m.Name.Equals(HandleMethodName) && - m.GetParameters().Length == 1 && - typeof(ICommand) - .GetTypeInfo().IsAssignableFrom(m.GetParameters()[0].ParameterType.GetTypeInfo()) - select m; + /// + /// Register a command handler explicitly + /// + /// + /// + /// The registration process will look into the handler and find methods that + /// are called Handle() and takes a command as parameter + /// + public void Register(Type handlerType) + { + var handleMethods = handlerType + .GetRuntimeMethods() + .Where(m => m.IsPublic || !m.IsStatic) + .Where(m => m.Name.Equals(HandleMethodName)) + .Where(m => m.GetParameters().Length == 1) + .Where(m => typeof(ICommand).GetTypeInfo().IsAssignableFrom(m.GetParameters()[0].ParameterType)); - foreach (var method in query) + foreach (var method in handleMethods) + { _commandHandlers[method.GetParameters()[0].ParameterType] = method; - } + } + } #pragma warning disable 1591 // Xml Comments - public bool TryHandle(ICommand command) - { - if( !_initialized) - Initialize(); + public bool TryHandle(ICommand command) + { + EnsureInitialized(); var commandType = command.GetType(); if (_commandHandlers.ContainsKey(commandType)) @@ -83,8 +100,8 @@ public bool TryHandle(ICommand command) return true; } - return false; - } + return false; + } #pragma warning restore 1591 // Xml Comments - } + } } \ No newline at end of file