Newer
Older
TransactionScriptVsDomain / ShipmentAggregateRoot.cs
@Derek Comartin Derek Comartin on 26 Jan 2022 5 KB Init
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Demo.EventSourced;

namespace Demo
{
    public class ShipmentAggregateRoot
    {
        private readonly SortedList<int, Stop> _stops = new();
        private readonly List<IEvent> _events = new();

        private ShipmentAggregateRoot(IReadOnlyList<Stop> stops)
        {
            for(var x = 0; x < stops.Count; x++)
            {
                _stops.Add(x, stops[x]);
            }
        }

        public static ShipmentAggregateRoot Factory(PickupStop pickup, DeliveryStop delivery)
        {
            return new ShipmentAggregateRoot(new Stop[] { pickup, delivery });
        }

        public static ShipmentAggregateRoot Factory(Stop[] stops)
        {
            if (stops.Length < 2)
            {
                throw new InvalidOperationException("Shipment requires at least 2 stops.");
            }

            if (stops.First() is not PickupStop)
            {
                throw new InvalidOperationException("First stop must be a Pickup");
            }

            if (stops.Last() is not DeliveryStop)
            {
                throw new InvalidOperationException("first stop must be a Pickup");
            }

            return new ShipmentAggregateRoot(stops);
        }

        public List<IEvent> GetUncommittedEvents()
        {
            var evnts = new List<IEvent>(_events);
            _events.Clear();
            return evnts;
        }

        public void Arrive(int stopId, DateTime arrived)
        {
            var currentStop = _stops.SingleOrDefault(x => x.Value.StopId == stopId);
            if (currentStop.Value == null)
            {
                throw new InvalidOperationException("Stop does not exist.");
            }

            var previousStopsAreNotDeparted = _stops.Any(x => x.Key < currentStop.Key && x.Value.Status != StopStatus.Departed);
            if (previousStopsAreNotDeparted)
            {
                throw new InvalidOperationException("Previous stops have not departed.");
            }

            currentStop.Value.Arrive(arrived);

            _events.Add(new Arrived(stopId, arrived));

            var arrivedToScheduled = arrived - currentStop.Value.Scheduled;
            if (arrivedToScheduled.TotalHours > 0)
            {
                _events.Add(new ArrivedLate(stopId, arrivedToScheduled));
            }
        }

        public void Pickup(int stopId, DateTime departed)
        {
            var currentStop = _stops.SingleOrDefault(x => x.Value.StopId == stopId);
            if (currentStop.Value == null)
            {
                throw new InvalidOperationException("Stop does not exist.");
            }

            if (currentStop.Value is not PickupStop)
            {
                throw new InvalidOperationException("Stop is not a pickup.");
            }

            if (currentStop.Value.Status != StopStatus.Arrived)
            {
                Arrive(stopId, departed);
            }

            currentStop.Value.Depart(departed);
            _events.Add(new PickedUp(stopId, departed));
        }

        public void Deliver(int stopId, DateTime departed)
        {
            var currentStop = _stops.SingleOrDefault(x => x.Value.StopId == stopId);
            if (currentStop.Value == null)
            {
                throw new InvalidOperationException("Stop does not exist.");
            }

            if (currentStop.Value is not DeliveryStop)
            {
                throw new InvalidOperationException("Stop is not a delivery.");
            }

            if (currentStop.Value.Status != StopStatus.Arrived)
            {
                Arrive(stopId, departed);
            }

            currentStop.Value.Depart(departed);
            _events.Add(new Delivered(stopId, departed));
        }

        public bool IsComplete()
        {
            return _stops.All(x => x.Value.Status == StopStatus.Departed);
        }
    }

    public class PickupStop : Stop
    {
        public PickupStop(int stopId)
        {
            StopId = stopId;
        }
    }

    public class DeliveryStop : Stop
    {
        public DeliveryStop(int stopId)
        {
            StopId = stopId;
        }
    }

    public record ShipmentStop(int StopId, StopType StopType, int Sequence);

    public abstract class Stop
    {
        public int StopId { get; protected set; }
        public StopStatus Status { get; private set; } = StopStatus.InTransit;
        public Address Address { get; protected set;}
        public DateTime Scheduled { get; protected set;}
        public DateTime Arrived { get; protected set; }
        public DateTime? Departed { get;  protected set; }

        public void Arrive(DateTime arrived)
        {
            if (Status != StopStatus.InTransit)
            {
                throw new InvalidOperationException("Stop has already arrived.");
            }

            Status = StopStatus.Arrived;
            Arrived = arrived;
        }

        public void Depart(DateTime departed)
        {
            if (Status == StopStatus.Departed)
            {
                throw new InvalidOperationException("Stop has already departed.");
            }

            if (departed < Arrived)
            {
                throw new InvalidOperationException("Departed Date/Time cannot be before Arrived Date/Time.");
            }

            Status = StopStatus.Departed;
            Departed = departed;
        }
    }

    public enum StopType
    {
        Pickup,
        Delivery
    }

    public enum StopStatus
    {
        InTransit,
        Arrived,
        Departed
    }

    public record Address(string Street, string City, string Postal, string Country);

    public interface IBus
    {
        Task Publish(IEvent evnt);
    }
}