23

According to DDD domain logic should not be polluted with technical concerns like serialization, object-relational mapping, etc.

So how do you serialize or map the aggregates' state without publicly exposing it via getters and setters? I have seen lots of examples for e.g. repository implementations, but practically all of them relied on public accessors on the entities and value objects for mapping.

We could use reflection to avoid public accessors, but IMO these domain objects would still implicitly be dependent on the serialization concern. E.g. you couldn't rename or remove a private field without tweaking your serialization/mapping configuration. So you have to consider serialization where you should instead be focussing on domain logic.

So what's the best compromise to follow here? Live with public accessors, but avoid using them for anything else but mapping code? Or did I just miss something obvious?

I'm explicitly interested in serializing the state of DDD domain objects (aggregates consisting of entities and value objects). This is NOT about serialization in general or transcation script scenarios where stateless services operate on simple data container objects.

12

Kinds of objects

For purposes of our discussion, let's separate our objects into three different kinds:

Business Domain logic

These are the objects that get work done. They move money from one checking account to another, fulfill orders, and all of the other actions that we expect business software to take.

Domain logic objects normally do not require accessors (getters and setters). Rather, you create the object by handing it dependencies through a constructor, and then manipulate the object through methods (tell, don't ask).

Data Transfer Objects

Data Transfer Objects are pure state; they don't contain any business logic. They will always have accessors. They may or may not have setters, depending on whether or not you're writing them in an immutable fashion. You will either set your fields in the constructor and their values will not change for the lifetime of the object, or your accessors will be read/write. In practice, these objects are typically mutable, so that a user can edit them.

View Model objects

View Model objects contain a displayable/editable data representation. They may contain business logic, usually confined to data validation. An example of a View Model object might be an InvoiceViewModel, containing a Customer object, an Invoice Header object, and Invoice Line Items. View Model objects always contain accessors.

So the only kind of object that will be "pure" in the sense that it doesn't contain field accessors will be the Domain Logic object. Serializing such an object saves its current "computational state," so that it can be retrieved later to complete processing. View Models and DTO's can be freely serialized, but in practice their data is normally saved to a database.

Serialization, dependencies and coupling

While it is true that serialization creates dependencies, in the sense that you have to deserialize to a compatible object, it does not necessarily follow that you have to change your serialization configuration. Good serialization mechanisms are general purpose; they don't care if you change the name of a property or member, so long as it can still map values to members. In practice, this only means that you must re-serialize the object instance to make the serialization representation (xml, json, whatever) compatible with your new object; no configuration changes to the serializer should be necessary.

It is true that objects should not be concerned with how they are serialized. You've already described one way such concerns can be decoupled from the domain classes: reflection. But the serializer should be concerned about how it serializes and deserializes objects; that, after all, is its function. The way you keep your objects decoupled from your serialization process is to make serialization a general-purpose function, able to work across all object types.

One of the things people get confused about is that decoupling has to occur in both directions. It does not; it only has to work in one direction. In practice, you can never decouple completely; there is always some coupling. The goal of loose coupling is to make code maintenance easier, not to remove all dependencies.

  • I agree with your view on decoupling. The serializer depends on the domain object and that's OK. But not the other way round. I disagree however with your view on public accessors on domain objects. In practice they often have them, yes. But IMO it would be preferrable to implement domain logic in a clean object-oriented design: Tell, don't ask. But still you need accessors for mapping purposes (ORM, serialization, GUI...). And that's what I would like to avoid, if possible. – EagleBeak Dec 15 '14 at 9:48
  • How do you plan on accessing your fields, if you don't have accessors? – Robert Harvey Dec 15 '14 at 16:17
  • Actually I'm referring to neither of the three kinds of objects you describe, but to "aggregates" in DDD terminology and their sub objects (enities, value objects). I realize now, that my question was not explicit enough about this. Sorry! Please see my edit above. – EagleBeak Dec 15 '14 at 17:24
  • 1
    This is basically unsolved problem - you can't have encapsulation, decoupling and serialization\encoding at the same time exposing DTO, is one way to achieve a compromise. There are, however, much less intrusive ways: yegor256.com/2016/07/06/data-transfer-object.html – Basilevs Nov 28 '16 at 10:03
  • 1
    That drops encapsulation, anyone can implement or use friend class to read object's internals,. – Basilevs Nov 28 '16 at 11:11
-1

The underlying purpose of serialization is to ensure that data produced by one system is able to be consumed by one or more compatible systems.

The easiest and most robust approach to serialization is to translate the data into a type agnostic format that maintains the structure in a simple and easy-to-consume format. For instance the most ubiquitous serialization formats (ie JSON, XML) use a well-defined text-based format. Text is simple to produce, transmit, and consume.

There are 2 reasons why using one of these formats may not be ideal.

  1. Efficiency

    There is an inherent cost involved in translating all of the data into their text-based equivalents. Data types wouldn't exist if text was the most efficient way to express all the different forms of data. In addition, the structure of these formats is not ideal for retrieving subsets of data asynchronously or in parts.

    For instance, XML and JSON assume that the data being used will be written and read from start to finish. For processing very large data sets where memory is scarce, the system consuming the data may require the ability to process the data in parts. In that case, a special purpose serialization/deserialization implementation may be required to handle the data.

  2. Precision

    The casting required to serialize/deserialize the data from it's intended type to data agnostic type result in a loss of precision.

One could argue that producing a binary representation of the objects and data is clearly the most efficient and accurate solution. The major drawback is that the implementation of all systems that both consume and produce data need to remain compatible. It's a simple constraint in theory but it's a nightmare to maintain in practice as production systems tend to change/evolve over time.

With that said. Decoupling serialization/deserialization from the domain-specific details makes sense as a general rule because general purpose formats are more robust, better supported across diverse systems, and require little-to-no increased maintenance overhead to use.

  • Sorry, but this doesn't answer my question. It's about decoupling domain objects from serialization, not about the reasons for serialization or pros and cons of various formats. How do I serialize domain objects without publicly exposing their private state? – EagleBeak Dec 15 '14 at 9:31
  • @EagleBeak Oh, I didn't realize your concern was specifically about handling private members. In your case you could serialize in binary (assuming the receiving system follows the same rules/structure that the domain objects were created under) or write some logic that extracts just the public data prior to serialization. – Evan Plaice Dec 15 '14 at 10:33
  • I think the 'usual' assumption is that the data being serialized into a general purpose format (ex xml, json) will be public and that privilege is controlled via the API through ACLs or some other equivalent. General purpose serialization/deserialization falls more along the lines of decoupling data from business logic going from one system to another. – Evan Plaice Dec 15 '14 at 10:36
  • I agree about public accessors being usually assumed on objects to be serialized. But I would still like to know more about how this relates to DDD and its strong focus on encapsulation of domain logic. Do all DDD practitioners just expose the domain model's state via public accessors for serialization (and never mention it in their examples)? I doubt it. Please don't get me wrong. I appreciate your input very much. It's just that I'm interested in a different aspect. (So far I don't think my question is too vague, but Robert Harvey's answer and yours got me thinking about that..) – EagleBeak Dec 15 '14 at 11:14

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.