Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
367 views
in Technique[技术] by (71.8m points)

.net - Domain Driven Design, Domain objects, attitude about Setters

Been watching some Greg Young videos lately and I'm trying to understand why there is a negative attitude towards Setters on Domain objects. I thought Domain objects were supposed to be "heavy" with logic in DDD. Are there any good examples online of the bad example and then they way to correct it? Any examples or explanations are good. Does this only apply to Events stored in a CQRS manner or does this apply to all of DDD?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

I'm contributing this answer to complement Roger Alsing answer about invariants with other reasons.

Semantic information

Roger clearly explained that property setters don't carry semantic information. Allowing a setter for a property like Post.PublishDate can add confusion as we can't know for sure if the post has been published or only if the publication date has been set. We can't know for sure that this is all that is needed to publish an article. The object "interface" does not show its "intent" clearly.

I believe, though, that properties like "Enabled" do carry enough semantic for both "getting" and "setting". It is something that should be effective immediately and I can't see the need for SetActive() or Activate()/Deactivate() methods for the reason alone of missing semantics on the property setter.

Invariants

Roger also talked about the possibility of breaking invariants through property setters. This is absolutely right, and one should make properties that work in tandem to provide a "combined invariant value" (as the angles of triangle to use Roger's example) as read-only properties and create a method to set them all together, which can validate all the combinations in a single step.

Property order initialization/setting dependencies

This is similar to the problem with invariants as it causes problems with properties that should be validated/changed together. Imagine the following code:

    public class Period
    {
        DateTime start;
        public DateTime Start
        {
            get { return start; }
            set
            {
                if (value > end  && end != default(DateTime))
                    throw new Exception("Start can't be later than end!");
                start = value;
            }
        }

        DateTime end;
        public DateTime End
        {
            get { return end; }
            set
            {
                if (value < start && start != default(DateTime))
                    throw new Exception("End can't be earlier than start!");
                end = value;
            }
        }
    }

This is a naive example of "setter" validation that causes access order dependencies. The following code illustrates this problem:

        public void CanChangeStartAndEndInAnyOrder()
        {
            Period period = new Period(DateTime.Now, DateTime.Now);
            period.Start = DateTime.Now.AddDays(1); //--> will throw exception here
            period.End = DateTime.Now.AddDays(2);
            // the following may throw an exception depending on the order the C# compiler 
            // assigns the properties. 
            period = new Period()
            {
                Start = DateTime.Now.AddDays(1),
                End = DateTime.Now.AddDays(2),
            };
            // The order is not guaranteed by C#, so either way may throw an exception
            period = new Period()
            {
                End = DateTime.Now.AddDays(2),
                Start = DateTime.Now.AddDays(1),
            };
        }

Since we can't change the start date past the end date on a period object (unless it is an "empty" period, with both dates set to default(DateTime) - yes, it's not a great design, but you get what I mean...) trying to set the start date first will throw an exception.

It gets more serious when we use object initializers. Since C# don't guarantee any assignment order, we can't make any safe assumptions and the code may or may not throw an exception depending on the compiler choices. BAD!

This is ultimately a problem with the DESIGN of the classes. Since the property can't "know" that you are updating both values, it can't "turn off" the validation until both values are actually changed. You should either make both properties read-only and provide a method to set both at the same time (losing the feature of object initializers) or remove the validation code from the properties altogether (perhaps introducing another read-only property like IsValid, or validating it whenever needed).

ORM "hydration"*

Hydration, in a simplistic view, means getting the persisted data back into objects. For me this is really the biggest problem with adding logic behind property setters.

Many/most ORMs map the persisted value into a property. If you have validation logic or logic that changes the object state (other members) inside property setters you'll end up trying to validate against an "incomplete" object (one still being loaded). This is very similar to the object initialization problem since you can't control the order the fields are "hydrated".

Most ORMs allow you to map the persistence to private fields instead of properties, and this will allow the objects to be hydrated, but if your validation logic lies mostly inside property setters you may have to duplicate it elsewhere to check that a loaded object is valid or not.

Since many ORM tools support lazy loading (a fundamental aspect of an ORM!) through the use of virtual properties (or methods) mapping to fields will make it impossible for the ORM to lazy load objects mapped into fields.

Conclusion

So, in the end, to avoid code duplication, allow ORMs to perform as best as they could, prevent surprising exceptions depending on the order the fields are set, it is wise to move logic away from property setters.

I'm still figuring out where this 'validation' logic should be. Where do we validate the invariants aspects of an object? Where do we put higher-level validations? Do we use hooks on the ORMs to perform validation (OnSave, OnDelete, ...)? Etc. Etc. Etc. But this is not the scope of this answer.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...