using BeyondNetCode.Shell.Ddd.Rules; using BeyondNetCode.Shell.Ddd.Interfaces; using BeyondNetCode.Shell.Ddd.Extensions; using BeyondNetCode.Shell.Ddd.Rules.Impl; using BeyondNetCode.Shell.Ddd.Services.Impl; namespace BeyondNetCode.Shell.Ddd { /// /// Represents an abstract base class for entities in the domain-driven design. /// /// The type of the entity. /// The type of the entity properties. public abstract class Entity : IEntity where TEntity : class where TProps : class, IProps { #region Members /// /// The properties of the entity. /// private TProps _props; #endregion #region Properties public IdValueObject Id { get; private set; } public IdValueObject SetId(string id) { return IdValueObject.Load(id); } public BrokenRulesManager BrokenRules { get; } public TrackingStateManager TrackingState { get; } public ValidatorRuleManager> ValidatorRules { get; } #endregion #region Constructors /// /// Initializes a new instance of the class. /// /// The properties of the entity. protected Entity(TProps props) { BrokenRules = new BrokenRulesManager(); TrackingState = new TrackingStateManager(); ValidatorRules = new ValidatorRuleManager>(); Id = IdValueObject.Create(); _props = props; Validate(); TrackingState.MarkAsNew(); } #endregion #region Methods /// /// Gets a copy of the entity properties. /// /// A copy of the entity properties. public TProps GetPropsCopy() { var copyProps = _props!.Clone(); return (TProps)copyProps; } /// /// Gets the properties of the entity. /// public TProps Props { get { return _props!; } } /// /// Sets the properties of the entity. /// /// The properties to set. public void SetProps(TProps props) { _props = props; TrackingState.MarkAsDirty(); } #endregion #region BusinessRules /// /// Gets a value indicating whether the entity is valid. /// /// true if the entity is valid; otherwise, false. public bool IsValid() { Validate(); return !BrokenRules.GetBrokenRules().Any(); } /// /// Validates the entity. /// public void Validate() { Guard(); // Add validators for Entity AddValidators(); // Get broken rules for Entity BrokenRules.Add(ValidatorRules.GetBrokenRules().ToList()); if (BrokenRules.GetBrokenRules().Any()) { TrackingState.MarkAsDirty(); return; } // Explore broken rules for properties var props = GetPropsCopy().GetType().GetProperties(); var propsBrokenRules = props.GetPropertiesBrokenRules(_props); if (propsBrokenRules.Any()) { BrokenRules.Add(propsBrokenRules); TrackingState.MarkAsDirty(); } } private void Guard() { if (!(this is TEntity)) throw new InvalidOperationException($"Entity '{GetType().Print()}' specifies '{typeof(TEntity).Print()}' as generic argument, it should be its own type"); } /// /// Adds the validators for the value object. /// public virtual void AddValidators() { } #endregion #region Equality /// public override bool Equals(object? obj) { if (obj == null || !(obj is Entity)) return false; if (ReferenceEquals(this, obj)) return true; if (GetType() != obj.GetType()) return false; return ReferenceEntityPropertiesEquals(obj); } private bool ReferenceEntityPropertiesEquals(object? obj) { if (obj is not Entity entity) return false; return Id.Equals(entity.Id); } /// public override int GetHashCode() { return Id.GetHashCode(); } /// /// Determines whether two entities are equal. /// /// The left entity. /// The right entity. /// true if the entities are equal; otherwise, false. public static bool operator ==(Entity left, Entity right) { if (Equals(left, null)) return Equals(right, null) ? true : false; else return left.Equals(right); } /// /// Determines whether two entities are not equal. /// /// The left entity. /// The right entity. /// true if the entities are not equal; otherwise, false. public static bool operator !=(Entity left, Entity right) { return !(left == right); } #endregion #region TransitionStatus /// /// Finite State Machine (FSM) Pattern /// private readonly Dictionary> _validTransitions = new(); protected void DefineValidTransitions(Dictionary> transitions) { ArgumentNullException.ThrowIfNull(transitions, nameof(transitions)); _validTransitions.Clear(); foreach (var transition in transitions) { _validTransitions[transition.Key] = transition.Value; } } protected bool CanTransitionTo(object currentState, object newState) { if (!_validTransitions.ContainsKey(currentState)) { throw new InvalidOperationException($"No transitions defined for state {currentState}."); } return _validTransitions[currentState].Contains(newState); } #endregion } }