Skip to content

Instantly share code, notes, and snippets.

@romeshniriella
Last active April 10, 2016 11:38
Show Gist options
  • Save romeshniriella/cbee3bccada809c2b6e6df899f4fd0f7 to your computer and use it in GitHub Desktop.
Save romeshniriella/cbee3bccada809c2b6e6df899f4fd0f7 to your computer and use it in GitHub Desktop.
Commands, Queries and Decorators
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
});
}
}
}
/// <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
{
}
/// <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);
}
// 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);
}
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