Domain Validation in a CQRS architecture

前端 未结 11 1534
南旧
南旧 2020-12-12 11:25

Danger ... Danger Dr. Smith... Philosophical post ahead

The purpose of this post is to determine if placing the validation logic outside of my domain entiti

11条回答
  •  一个人的身影
    2020-12-12 11:54

    I would not call a class which inherits from EntityBase my domain model since it couples it to your persistence layer. But that's just my opinion.

    I would not move the email validation logic from the Customer to anything else to follow the Open/Closed principle. To me, following open/closed would mean that you have the following hierarchy:

    public class User
    {
        // some basic validation
        public virtual void ChangeEmail(string email);
    }
    
    public class Employee : User
    {
        // validates internal email
        public override void ChangeEmail(string email);
    }
    
    public class Customer : User
    {
        // validate external email addresses.
        public override void ChangeEmail(string email);
    }
    

    You suggestions moves the control from the domain model to an arbitrary class, hence breaking the encapsulation. I would rather refactor my class (Customer) to comply to the new business rules than doing that.

    Use domain events to trigger other parts of the system to get a more loosely coupled architecture, but don't use commands/events to violate the encapsulation.

    Exceptions

    I just noticed that you throw DomainException. That's a way to generic exception. Why don't you use the argument exceptions or the FormatException? They describe the error much better. And don't forget to include context information helping you to prevent the exception in the future.

    Update

    Placing the logic outside the class is asking for trouble imho. How do you control which validation rule is used? One part of the code might use SomeVeryOldRule when validating while another using NewAndVeryStrictRule. It might not be on purpose, but it can and will happen when the code base grows.

    It sounds like you have already decided to ignore one of the OOP fundamentals (encapsulation). Go ahead and use a generic / external validation framework, but don't say that I didn't warn you ;)

    Update2

    Thanks for your patience and your answers, and that's the reason why I posted this question, I feel the same an entity should be responsible to guarantee it's in a valid state (and I have done it in previous projects) but the benefits of placing it in individual objects is huge and like I posted there's even a way to use individual objects and keep the encapsulation but personally I am not so happy with design but on the other hand it is not out of the table, consider this ChangeEmail(IEnumerable> validators, string email) I have not thought in detail the imple. though

    That allows the programmer to specify any rules, it may or may not be the currently correct business rules. The developer could just write

    customer.ChangeEmail(new IValidator[] { new NonValidatingRule() }, "notAnEmail")
    

    which accepts everything. And the rules have to be specified in every single place where ChangeEmail is being called.

    If you want to use a rule engine, create a singleton proxy:

    public class Validator
    {
        IValidatorEngine _engine;
    
        public static void Assign(IValidatorEngine engine)
        {
            _engine = engine;
        }
    
        public static IValidatorEngine Current { get { return _engine; } }
    }
    

    .. and use it from within the domain model methods like

    public class Customer
    {
        public void ChangeEmail(string email)
        {
            var rules = Validator.GetRulesFor("ChangeEmail");
            rules.Validate(email);
    
            // valid
        }
    
    }
    

    The problem with that solution is that it will become a maintenance nightmare since the rule dependencies are hidden. You can never tell if all rules have been specified and working unless you test every domain model method and each rule scenario for every method.

    The solution is more flexible but will imho take a lot more time to implement than to refactor the method who's business rules got changed.

提交回复
热议问题