Skip to content

Instantly share code, notes, and snippets.

@skleanthous
Created August 11, 2021 16:44
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save skleanthous/11ab343a9358b492bddef04f433580ad to your computer and use it in GitHub Desktop.
Save skleanthous/11ab343a9358b492bddef04f433580ad to your computer and use it in GitHub Desktop.
The parking lot implementation
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using EventStore.Client;
namespace BullOak.Repositories.EventStore.Test.Integration
{
internal record InspectionId(Guid Id);
internal record LicencePlate(string PlateNumber);
internal class Product
{
public DateTimeOffset CalculateFinishTimeFrom(DateTimeOffset startTime)
=> startTime + TimeSpan.FromMinutes(30);
}
internal record VehicleRegistered(string LicencePlate);
internal record VehicleBooked(string LicencePlate, string Location, long Start, long Finish);
internal record VehicleUnbooked(Guid InspectionId, long inspectionTime, string LicencePlate, string Location);
internal record Booking(DateTimeOffset startTime, DateTimeOffset endTime);
internal record VehicleState(bool IsRegistered)
{
public ImmutableList<Booking> Bookings { get; init; } = ImmutableList<Booking>.Empty;
public static VehicleState Default() => new VehicleState(false);
}
internal class Vehicle
{
public Maybe<VehicleUnbooked> Inspect(VehicleState state, InspectionId inspectionId, DateTimeOffset when,
LicencePlate licencePlate, string location)
{
bool conflictsWithBooking = state.Bookings.Any(x => x.startTime <= when && when <= x.endTime);
if (!conflictsWithBooking)
return Maybe<VehicleUnbooked>.Some(new VehicleUnbooked(inspectionId.Id, when.ToUnixTimeSeconds(),
licencePlate.PlateNumber, location));
return Maybe<VehicleUnbooked>.None;
}
public IEnumerable<object> Book(VehicleState state, LicencePlate licencePlate, string location,
DateTimeOffset start, Product product)
{
if (!state.IsRegistered)
yield return new VehicleRegistered(licencePlate.PlateNumber);
var finish = product.CalculateFinishTimeFrom(start);
yield return new VehicleBooked(
LicencePlate: licencePlate.PlateNumber,
Location: location,
Start: start.ToUnixTimeSeconds(),
Finish: finish.ToUnixTimeSeconds());
}
}
internal class Rehydration
{
public VehicleState RehydrateFrom(object[] events)
{
var state = VehicleState.Default();
foreach (var @event in events)
state = @event switch
{
VehicleRegistered registered => Apply(state, registered),
VehicleBooked booked => Apply(state, booked),
VehicleUnbooked unbooked => Apply(state, unbooked),
_ => throw new NotSupportedException()
};
return state;
}
private VehicleState Apply(VehicleState before, VehicleRegistered @event)
=> before with {IsRegistered = true};
private VehicleState Apply(VehicleState before, VehicleBooked @event)
=> before with
{
Bookings = before.Bookings.Add(new Booking(DateTimeOffset.FromUnixTimeSeconds(@event.Start),
DateTimeOffset.FromUnixTimeSeconds(@event.Finish)))
};
private VehicleState Apply(VehicleState before, VehicleUnbooked @event)
{
var timeOfInspection = DateTimeOffset.FromUnixTimeSeconds(@event.inspectionTime);
return before with
{
Bookings = before.Bookings.Where(x => x.startTime > timeOfInspection || x.endTime < timeOfInspection)
.ToImmutableList()
};
}
}
internal class CommandHandler
{
private EventStoreWrapper EventStore { get; init; }
private Rehydration Rehydrator { get; init; }
private Vehicle Vehicle { get; init; }
public void BookTheCar(LicencePlate licencePlate, string location,
DateTimeOffset start, Product product)
{
var read = EventStore.ReadVehicleFrom(licencePlate.PlateNumber);
var newEvents = Vehicle.Book(read.State, licencePlate, location, start, product);
if (newEvents.Any())
EventStore.WriteToVehicleStream(licencePlate.PlateNumber, newEvents, Check.ExpectConcurrency(read.Concurrency));
}
public void ScheduleInspection(InspectionId inspectionId, DateTimeOffset when,
LicencePlate licencePlate, string location)
{
var read = EventStore.ReadVehicleFrom(licencePlate.PlateNumber);
var newEvents = Vehicle.Inspect(read.State, inspectionId, when, licencePlate, location);
if (newEvents.hasValue)
EventStore.WriteToVehicleStream(licencePlate.PlateNumber, newEvents.value, Check.ExpectConcurrency(read.Concurrency));
}
}
internal interface EventStoreWrapper
{
ReadStreamResponse ReadVehicleFrom(string streamName);
WriteStreamResponse WriteToVehicleStream(string streamName, object[] events, Check check);
WriteStreamResponse WriteToVehicleStream(string streamName, object @event, Check check);
}
internal record ReadStreamResponse(object[] Events, VehicleState State, bool PreExisting, long Concurrency);
internal record WriteStreamResponse(bool Success, string errorMessageIfNotSuccess);
internal interface Check
{
static Check ExpectConcurrency(long expectedVersion)
=> new ConcurrencyCheck(expectedVersion);
static Check Pass() => new AppendWithoutConcurrency();
static Check IfIsNewStream() => new AppendIfNewStream();
}
internal record ConcurrencyCheck(long ExpectedVersion) : Check;
internal record AppendWithoutConcurrency() : Check;
internal record AppendIfNewStream() : Check;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment