I revamped our availability engine few month ago in order to move our logic from a DB to a micro service. At that time, the business logic was fairly simple:
A resource (meeting room, desk, empty office, equipment) is available on a given time frame ONLY if it is not already booked (ie: no other booking using the same resource)
When a resource is not available, the closest available time frames must be calculated
To cover these requirements, I built the small piece of code below:
public class Schedule : IAggregateRoot
{
public int CityId { get; }
public int BuildingId { get; }
public int CentreId { get; }
public int ResourceId { get; }
public ICollection<Booking> Bookings { get; }
public Schedule(int cityId, int buildingId, int centreId, int resourceId, IEnumerable<Booking> bookings)
{
CityId = cityId;
BuildingId = buildingId;
CentreId = centreId;
ResourceId = resourceId;
Bookings = new List<Booking>(bookings);
}
public bool IsTimeSlotFree(DateTimeOffset startDate, DateTimeOffset endDate)
=> Bookings.Any(/* Predicate */);
public IEnumerable<Availability> GetFreeTimeSlots(
DateTimeOffset startDate,
DateTimeOffset endDate,
TimeSpan duration)
{
var nbSlots = Math.Floor((endDate - startDate) / duration);
for(int i=0; i<nbSlots; i++) {
/* yield return availability */
}
}
}
public class Availability : ValueObject
{
public DateTimeOffset StartDate { get; set; }
public DateTimeOffset EndDate { get; set; }
public int ResourceId { get; set; }
public bool IsAvailable { get; set; }
}
public class Resource : Entity
{
public string Code { get; set; }
// Required for EF Core
protected Resource() { }
}
public class Booking : Entity
{
public DateTimeOffset StartDate { get; set; }
public DateTimeOffset EndDate { get; set; }
public string Status { get; set; }
public int ResourceId { get; set; }
// Required for EF Core
protected Booking() { }
}
Few weeks ago I was asked to handle combined rooms (two smaller rooms can be merged into a bigger combined room). In this scenario a combined room is available ONLY its sub-rooms and itself are available. In other words, I need to check several schedules to determine the availability and unfortunately my current level of abstraction doesn't allow that (one schedule, one room).
The only way I found is to retrieve a resource and its children (=subrooms) and then create a schedule containing a dictionary of ResourceId and bookings.
public class Resource : Entity
{
public string Code { get; set; }
public Resource Parent { get; set; }
public ICollection<Resource> Children { get; set; }
// Required for EF Core
protected Resource() { }
}
public class Schedule : IAggregateRoot
{
public int CityId { get; }
public int BuildingId { get; }
public int CentreId { get; }
public int ResourceId { get; }
public IDictionnary<int, ICollection<Bookings>> Bookings
(...)
}
I don't find this solution really elegant. To me a better solution would be to retrieve the schedules and combine them in order to determine the actual availability. I tried several solutions but I ended-up writing spaghetti code.
Do you have any ideas on how I could re-design my aggregates to properly handle this new concept?
Thank you, Seb