0

In general, I would my domain objects (customer, contract, etc.) to be always in a consistent state. However there are situations, where they might be in an inconsistent state, and nevertheless I want to work with them:

  1. When I load the domain-object from the database and it is in an inconsistent state (maybe someone changed a field directly on the database)
  2. When I create them in a Creation-Dialog and I need to handle some validation errors.

Are there any best practices how to handle these contradicting requirements?

3
  • 3
    In the first situation, I would throw an exception. Your application really can't operate with an inconsistent state. In the second case, I would create a class that manages the user input and validate it. Until data is valid, you shouldn't create a real instance of your domain object. – Héctor Mar 8 '18 at 10:30
  • (1) "Make invalid states un-representable" (q). If you want to migrate data between formats, use explicit format versioning. (2) See builder pattern. A builder is a partial representation that knows how to produce a valid domain object, and when to refuse. – 9000 Mar 8 '18 at 17:30
  • In addition to the answers given here I would like to say that, personally, I really learned to see the power of DDD when we actually disallowed situations like "someone changed a field directly on the database". For example, if your Orders domain is the only code / actor that is allowed to modify data in the Orders tables then you never get into an inconsistent state. I found the concept of data ownership to be very important in DDD but not talked about much. – Richiban Mar 9 '18 at 11:55
1

In general, you have the right idea. Assume that "(...) they might be in an inconsistent state, and nevertheless I want to work with them." is false and that you never-ever want to see your objects in an inconsistent state. Let's run with this idea, and see where it takes us.

When loading from the data base

When you load an object from a database you have to construct a value that will be an instance of the desired type from an instance of some type that represents the result you got from the database. You must deserialise the object. In my opinion it makes sense to validate that the invariants required by the hold - and a good place to do it is the constructor.

class Foo {
  public:
    Foo(Some_type_representing_db_result const);
};

If you need to construct Foo objects from Some_type_representing_db_results, you write an appropriate constructor to do that for you (I illustrate the answer using C++, but I'm sure you can adapt the examples to your language of choice). Throw an exception if the constructor cannot establish the required invariants - there is no need to force yourself to weaken them to accommodate some possibly broken values. You would compromise your program's correctness this way.

If you have to deal with broken objects I'd suggest creating a different data type, with weaker invariants; e.g. Foo and WeakerFoo. This way the compiler will warn you if you try to use a WeakerFoo as a non-broken Foo.

When handling validation errors

Take advantage of the fact that you do not have to construct the object immediately. Validate the fields one-by-one, and when you're sure they have acceptable values - combine them into your desired type using that type's constructor.

int count_of_stuff = a_form.count_of_stuff();
std::string name_of_stuff = a_form.name_of_stuff();

// These two functions should throw descriptive exceptions that
// will be useful to you.
check_if_count_of_stuff_is_in_acceptable_range(count_of_stuff);
check_if_name_of_stuff_is_valid(name_of_stuff);

Foo correct_foo { count_of_stuff, name_of_stuff };

This is really the same situation as the database one. It's only different because you have a user to whom you can report per-field errors.

A side note

Try to embed invariants into your types. If you know you have a type whose members must conform to some specification, create a type for them. Why would you want to use an int and have to remember validate it explicitly when you can use NumberForWhichSpecificRangeIsValid and have it validate itself?

3

When I load the domain-object from the database and it is inconsitent (maybe someone changed a field directly on the database)

The best thing to do is make sure that corrupt data can't be inserted into the db by adding constraints. Foreign keys, unique or length constraints etc.

When I create them in a Creation-Dialog and I need to handle some validation errors.

Write a validator on on your objects and validate them before inserting/updating.

Normally, I don't validate my items retrieved from the DB as they were already checked at insert/update time. You can of course also validate them while retrieving and handle it. Handling it with an exception makes sense here as this is something that should never happen.

0

These are business decisions.

For data in the database, it's notable that data can also fall out of"validity" as business rules change. When they do change, or when the business rules are violated by a bad actor, how does the business deal with it's paper files?

Likewise, on data intake, how would the business deal with being handed the paper version of the data? I suspect some invalid values would require handing the form back to the customer, decorated with error messages in red ink. For other invalid entries, the business might prefer their reps to smile, say thank-you, and quietly make some assumptions behind the scenes.

Pose your questions to "the business" for guidance.

Ideally, come to the discussion armed with a knowledge of what options are possible digitally that couldn't be done on paper and vice versa. Consult UX experts, as needed, for creative solutions to poke customers nicely for corrections -- and to make them feel rewarded for fixing their mistakes. Etc.

Most importantly, don't make business decisions without "the business." So, "best practice" is to consult the business (and/or a domain expert, which you someday will be).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.