Is it a DTO or a Value Object? – Matthias Noback

A common misunderstanding in my workshops (well, whose fault is it then? ;)), is about the distinction between a DTO and a value object. And so I’ve been looking for a way to categorize these objects without mistake.

What’s a DTO and how do you recognize it?

A DTO is an object that holds primitive data (strings, booleans, floats, nulls, arrays of these things). It defines the schema of this data by explicitly declaring the names of the fields and their types. It can only guarantee that all the data is there, simply by relying on the strictness of the programming language: if a constructor has a required parameter of type string, you have to pass a string, or you can’t even instantiate the object. However, a DTO does not provide any guarantee that the values actually make sense from a business perspective. Strings could be empty, integers could be negative, etc.

There are different flavours of the class design for DTOs:

/** * @object-type DTO * * Using a constructor and public readonly properties: */
final class AnExample
{ public function __construct( public readonly string $field, // ... ) { }
} /** * @object-type DTO * * Using a constructor with private readonly properties * and public getters: */
final class AnotherExample
{ public function __construct( private readonly string $field, // ... ) { } public function field(): string { return $this->field; }
}

Regarding the naming of a DTO: I recommend not adding “DTO” to the name itself. If you want to make it clear what the type is, add a comment, or an invented annotation (or attribute) like @object-type. This will be very useful for developers that are not aware of these object types. It may trigger them to look up an article about what it means (this article, maybe :)).

What’s a value object and how do you recognize it?

A value object is an object that wraps one or more values or value objects. It guarantees that all the data is there, and also that the values make sense from a domain perspective. Strings will no longer be empty, numbers will be verified to be in the correct range. A value object can offer these guarantees by throwing exceptions inside the constructor, which is private, forcing the client to use one of the static, named constructors. This makes a value object easy to recognize, and clearly distinguishable from a DTO:

final class AnExample
{ private function __construct( private string $value ) { } public static function fromValue( string $value ): self { /* * Throw an exception when the value doesn't * match all the expectations. */ return new self($value); }
}

While a DTO just holds some data for you and provides a clear schema for this data, a value object also holds some data, but offers evidence that the data matches the expectations. When the value object’s class is used as a parameter, property, or return type, you know that you are dealing with a correct value.

How should we use these object types?

Meaning is defined by use. If we are using “DTO” and “value object” in the wrong way, their names will eventually get a different meaning. This might be how the confusion between the two terms arises in the first place.

DTOs

A DTO should only be used in two places: where data enters the application or where it leaves the application. Some examples:

  1. When a controller receives an HTTP POST request, the request data may have any shape. We need to go from shapeless data to data with a schema (verified keys and types). We can use a DTO for this. A form library may be able to populate this DTO based on submitted form data, or we can use a serializer to convert the plain-text request body to a populated DTO.
  2. When we make an HTTP POST request to a web service, we may collect the input data in a DTO first, and then serialize it to a request body that our HTTP client can send to the service.
  3. For queries the situation is similar. Here we can use a DTO to represent the query result. As an example we can pass a DTO to a template to render a view based on it. We can use a DTO, serialize it to JSON and send it back as an API response.
  4. When we send an HTTP GET request to a web service, we may deserialize the API response into a DTO first, so we can apply a known schema to it instead of just accessing array keys and guessing the types. API client packages usually offer DTOs for requests and responses.

Truncated by Planet PHP, read more at the original (another 1622 bytes)