⬆️ ⬇️

At the borders, applications are not object-oriented.

I received a lot of feedback on my recent series of posts on Poka-yoke design (I would be upset if it were otherwise). Many of these reviews relate to various serialization or translation technologies that are commonly used at application boundaries: serialization, XML (de) hydration (comment of the translator: the same as serialization), UI validation, etc. Note that this translation occurs not only around the perimeter of the application, but also at the persistence level. ORMs are also translational mechanisms.

A common feature of many comments is the statement that most serialization technologies require a default constructor. For example, the XmlSerializer class requires a default constructor and public writeable properties. Most of the object-relational converters that I studied seem to have the same requirements. Windows Forms and WPF controls (UI is also an application boundary) are almost required to have a default constructor. Does this break encapsulation? Yes and no.



Objects at the border

Encapsulation, definitely, would be broken if you exhibited your objects (domain) right on the border of the application. Consider a simple XML document:

<name> <firstName>Mark</firstName> <lastName>Seemann</lastName> </name> 


Regardless of the existence of a formal contract (XSD), we could agree that the elements firstName and lastName are mandatory . Despite such a contract, I can calmly create a document that violates it:

 <name> <firstName>Mark</firstName> </name> 


We cannot promote ( note translator: force to execute ) this contract for the reason that we cannot engage in this compilation phase. We can validate input (and output), but this is a completely different question. It is for the reason that we cannot enforce the fulfillment of a contract that it is very simple to create incorrectly formed input. The same argument can be attributed to the input forms in the UI and any kinds of serialized byte sequences. Therefore, we are obliged to treat any input as expected.

This is not a new observation at all. In the book Enterprise Application Templates , Martin Fowler described such objects as data transfer objects (DTO) . However, despite the name, we must understand that DTO objects are not exactly objects. And this is also nothing new. In 2004, Don Boxing formulated four positions for service orientation . (Yes, I know that they are no longer in fashion and that people want to send them to rest , but some of them still make a lot of sense). In particular, the third provision is relevant to this post:

Services separate the scheme and the contract, not a class.


Yes, and that means they are not objects . DTO is a representation of a piece of data mapped to an object-oriented language. It still does not make them objects in the sense of encapsulation. This would be impossible to imagine. As we expect any input, we cannot promote (maintain) any invariants.

Usually, as pointed out in comments to one of my previous posts by Craig Stants , even if the input data is incorrect, we want to capture what we received in order to display the correct error message (this argument also applies to the boundaries between the machines). This means that any DTO will have very weak invariants (if it has any at all).

DTO objects do not break encapsulation, since they are not objects at all.


Do not be fooled by your instruments. The .NET framework really, really wants you to consider your DTO objects to be real objects.

This follows from code generation.

The strong typing provided by such auto-generated classes gives a false sense of security. You may think that you get quick feedback from the compiler , but there are many possible situations in which you will get a runtime error (the most remarkable are those that you get, forgetting to update the automatically generated code after updating the schema).

The worst result of presenting input and output data as objects is that many developers, deceivingly, work with them as with real models of objects. Constant result - anemic domain model .

More and more, this chain of arguments leads me to the conclusion that the mental model of DTO that we have been using for the last 10 years is a dead end.



What should happen at the application boundary

Suppose we write object-oriented code, and the data on the boundaries is anything but object-oriented objects. How do we work with them?

One option is to stay with what we have. In this case, to fill the hole, we need to develop a translation layer that could translate DTO objects into properly encapsulated domain objects. This is the path that I follow in the examples in my book . However, this solution, I increasingly suspect, is not the best. It causes support problems. (By the way, such a problem arises when you write a book: by the time you completed, you know a lot more than you knew when you started it ... I don’t really condemn the book - it’s just not perfect ...)

Another possibility is to stop treating data as objects and start treating it as structured data , which is what it really is. It would be very cool if our programming language had a separate concept of structured data ... Interestingly, while C # does not know anything, F # has a lot of possibilities for modeling data structures that have no behavior. Perhaps this is a more honest approach to working with data ... It will be necessary to experiment with this ...

The third option is to look towards dynamic types. Dino Esposito, in his article “ On the Edge: Expando Objects in C # 4.0, ” outlines a dynamic approach to consuming structured data, which shortens the automatically generated code and provides a lightweight API to structured data. It also looks like a promising approach ... It does not provide feedback at the compilation stage, but in the end it’s just a false sense of security. We have to resort to unit tests to get quick feedback , we all practice TDD, right?

In conclusion, my entire encapsulation series relates to object-oriented programming . Despite the presence of multiple technologies for representing data as "objects", they are false objects. Even if we use object-oriented boundary, this code has nothing to do with object orientation. Thus, the Poka-yoke design rules are not applicable here.

Now go back to the beginning and re-read the post, replacing “DTO” with “Entity” (or something that your ORM calls the representation of a row in a relational table) and you should see the outlines of why object-relational converters so doubtful.


')

Source: https://habr.com/ru/post/205108/



All Articles