Last active
April 10, 2016 11:38
-
-
Save romeshniriella/cbee3bccada809c2b6e6df899f4fd0f7 to your computer and use it in GitHub Desktop.
Commands, Queries and Decorators
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Linq; | |
using System.Threading.Tasks; | |
using Oklo.BookingService.Core.Booking.Data.Responses; | |
using Oklo.BookingService.Core.Booking.Engine; | |
using Oklo.BookingService.Core.Exceptions; | |
using Oklo.BookingService.Domain.Models.Booking; | |
using Oklo.BookingService.Domain.Models.Commands; | |
using Oklo.BookingService.Domain.Models.Results; | |
using Oklo.Core.Framework.Commands; | |
using Oklo.Core.Utils.Extensions; | |
using Oklo.Shared.Entities.Bookings.Items; | |
namespace Oklo.BookingService.Engines.CommandHandlers | |
{ | |
public class CreateBookingCommandHandler : ICommandHandler<CreateBookingCommand, CreateBookingResult> | |
{ | |
private readonly IBookingValidationProvider<CreateBookingCommand, BookingValidationResult> _bookingValidationProvider; | |
private readonly IBookingPreProcessor<CreateBookingCommand, BookingProcessorResult> _bookingPreProcessor; | |
public CreateBookingCommandHandler( | |
IBookingPreProcessor<CreateBookingCommand, BookingProcessorResult> bookingPreProcessor, | |
IBookingValidationProvider<CreateBookingCommand, BookingValidationResult> bookingValidationProvider ) | |
{ | |
_bookingPreProcessor = bookingPreProcessor; | |
_bookingValidationProvider = bookingValidationProvider; | |
} | |
public async Task<CreateBookingResult> HandleAsync(CreateBookingCommand command) | |
{ | |
var validationResult = await _bookingValidationProvider.ValidateAsync(command); | |
if (!validationResult.IsValid) | |
{ | |
throw new BookingValidationException(validationResult); | |
} | |
var processorResult = await _bookingPreProcessor.ProcessAsync(command); | |
if (!processorResult.Success) | |
{ | |
throw new BookingProcessorException<BookingProcessorResult>(processorResult); | |
} | |
var booking = processorResult.Booking; | |
if (command.SaveAsOption) // if the user is requesting | |
{ | |
// see if the booking can be made an option | |
booking.TryMakeOption(); | |
} | |
booking.Save(command.Context); | |
return await Task.FromResult(new CreateBookingResult | |
{ | |
BookingId = booking.BookingID, | |
BookingReference = booking.BookingRef, | |
Status = CommandStatus.Success | |
}); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// <summary> | |
/// Specifies a command in the system. | |
/// </summary> | |
/// <remarks> | |
/// thanks goes to: https://www.cuttingedge.it/blogs/steven/pivot/entry.php?id=91 | |
/// Commands alter state whereas queries don't. | |
/// Usually, commands should be sent to a queue and the command handler/processor | |
/// should get the commands from the queue and handle them appropriately. | |
/// This method eventually reduces data conflicts and improves the performance and also | |
/// it can improve reliability. | |
/// Reliability: since these commands can be serialized, once they are in a queue | |
/// and are handling through a transaction, once an error occured we can retry them or put | |
/// them in a different queue which can be processes later using a smarter command handler/processor | |
/// or by hand (if needed) | |
/// Performance: since the commands are in a queue, a seperate background instance can process these. | |
/// events such as saving things in the database. sending emails. If we were to use a distributed queue | |
/// then, we can connect to this queue from a different machine and process the queue while the web server | |
/// serves web pages. | |
/// Data Conflicts: sending commands through a transactional processor will ensure there are no inconsistenties in data | |
/// and we have to implement validators and error handlers thus giving more information about the data. | |
/// </remarks> | |
public interface ICommand | |
{ | |
} | |
/// <summary> | |
/// TResult is the result after executing this command in a cmd handler. | |
/// </summary> | |
public interface ICommand<TResult> : ICommand | |
{ | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// <summary> | |
/// This handler will not return anything back. Ideal for background commands. | |
/// </summary> | |
public interface ICommandHandler<in TCommand> | |
{ | |
/// <summary> | |
/// handle the command asynchornously. | |
/// </summary> | |
/// <param name="command"></param> | |
/// <returns></returns> | |
Task HandleAsync(TCommand command); | |
} | |
public interface ICommandHandler<in TCommand, TResult> | |
where TCommand : ICommand<TResult> | |
{ | |
/// <summary> | |
/// handle the command asynchornously. | |
/// </summary> | |
Task<TResult> HandleAsync(TCommand command); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SimpleInjector initialization | |
private static void InitializeContainer(Container container) | |
{ | |
// register all command handlers in the Domain assembly | |
container.Register(typeof(ICommandHandler<,>), new[] { typeof(Domain.App).Assembly }); | |
// register validators | |
container.Register(typeof(IBookingCommandValidator<>), new[] { typeof(Domain.App).Assembly }, Lifestyle.Singleton); | |
// decorator for measuring execution time. | |
// If a hotel search is being made, HotelSearchCommandHandler will be wrapped inside a HotelSearchCommandResultFilterHandler | |
// and then HotelSearchCommandResultFilterHandler will be wrapped inside MeasuringCommandHandlerDecorator | |
container.RegisterDecorator(typeof(ICommandHandler<,>), typeof(MeasuringCommandHandlerDecorator<,>)); | |
// decorator to log the query | |
// this will wrap MeasuringCommandHandlerDecorator in it. so measuring will not include logging time. | |
container.RegisterDecorator(typeof(ICommandHandler<,>), typeof(LoggingCommandHandlerDecorator<,>)); | |
container.Register(typeof(IBookingValidationProvider<,>), new[] { typeof(Domain.App).Assembly }); | |
container.Register(typeof(IBookingPreProcessor<,>), new[] { typeof(Domain.App).Assembly }); | |
container.Register(typeof(IBookingSearchService<>), new[] { typeof(Domain.App).Assembly }); | |
container.Register<ISystemLogger, Logger>(Lifestyle.Singleton); | |
container.Register<IBookingRepository, BookingRepository>(Lifestyle.Singleton); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Diagnostics; | |
using System.Threading.Tasks; | |
using Oklo.Core.Runtime.Logging; | |
using Oklo.Core.Utils.Extensions; | |
namespace Oklo.Core.Framework.Commands.Handlers | |
{ | |
/// <summary> | |
/// Measures the time taken to execute an query operation. | |
/// Only measuing time taken for a query execution. nothing else. | |
/// </summary> | |
/// <remarks> | |
/// This decorator should be the first parent of the query handler implementation. | |
/// Thus, it measures only the time taken to execute the query. not the time taken to | |
/// execute all other decorators + query handler. | |
/// </remarks> | |
/// <typeparam name="TCommand"></typeparam> | |
/// <typeparam name="TResult"></typeparam> | |
public class MeasuringCommandHandlerDecorator<TCommand, TResult> | |
: ICommandHandler<TCommand, TResult> | |
where TCommand : ICommand<TResult> | |
{ | |
private readonly ISystemLogger _logger; | |
private readonly ICommandHandler<TCommand, TResult> _decorated; | |
public MeasuringCommandHandlerDecorator(ICommandHandler<TCommand, TResult> decorated, ISystemLogger logger) | |
{ | |
_logger = logger.SetLogger(GetType()); | |
_decorated = decorated; | |
} | |
public async Task<TResult> HandleAsync(TCommand command) | |
{ | |
//CSharpName() & ToTitleCase() are extensions. | |
var commandName = command.GetType().CSharpName().ToTitleCase(); | |
var st = new Stopwatch(); | |
st.Start(); | |
try | |
{ | |
return await this._decorated.HandleAsync(command); | |
} | |
finally | |
{ | |
st.Stop(); | |
// in other words, string.Format("Time Taken for {0}: {1}ms",commandName,st.ElapsedMilliseconds) | |
_logger.Info($"Time Taken for {commandName}: {st.ElapsedMilliseconds}ms"); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment