← Back to list

Most DTOs are Boilerplate Nonsense

How to avoid data structure duplication when crossing boundaries

Paul Smith · 2026-03-05 00:53 · 3 claps · 4.2 min read
#data-transfer-object #software-design-patterns #software-development #swift-programming #programming
Open on Medium ↗
Wiki topics: 💻 · Programming 📱 · Mobile Development 🧠 · Mental Wellness

Most DTOs are Boilerplate Nonsense

How to avoid data structure duplication when crossing boundaries

I’ve never been sold on the idea of using data transfer objects (DTOs) by any of the articles I’ve read about them. When I see yet another article putting forward the benefits of DTOs, I inevitably grind my teeth. Why do I want to duplicate the types I already have? Until I did the research for this article, I didn’t even realize DTOs had a use beyond serving as an intermediate representation of existing types to bridge domain and computer boundaries. It’s certainly the usage most people focus on, but there’s a better way to handle those scenarios than duplicating the types that already exist.

Serialization

The primary purpose of serialization is to flatten data into a sequence of bytes that can be easily stored on disk or sent across the network for later reconstruction. One thing inherent to a serialization format are the primitive data types it can represent, which are the very same types some people who consider DTOs to be little more than intermediate representations say a DTO should hold.

It’s true that serialization and deserialization can be an expensive way to cross boundaries inside the same process, but a specialized object isn’t needed for that either. In fact, if the data is being sent across boundaries using a DTO, it’s already being copied, which could be done just as easily using the original type to hold the copied data. Immutable types and types with value semantics can be sent as-is. If serialization, and direct copies, can transport data across boundaries, why do people bother using DTOs for the same purpose?

Separation of concerns

In the minds of some developers, a domain type that can serialize itself is a violation of the single-responsibility principle, and DTOs are the answer. The fear is that including serialization and deserialization code in a domain model will cause the type to be tightly coupled with its serialized representation. If a serialization API forces the developer to sprinkle annotations all over their code like radioactive pixie dust, or it requires a limited set of types to be used, that could indeed be an issue.

On the other hand, when serialization and deserialization for a type are isolated to their own declarations inside the larger type (e.g. a method that implements the serialization), it isn’t a big deal at all. In some languages, like Swift, that code can even be generated by the compiler or implemented as an extension to the original type. Even when a type can’t be extended directly, it can be wrapped with an adapter that provides the functionality.

Changing serialization format

Isolation from the serialization format, specifically how an abstract data model is converted to and from binary data, is another excuse I’ve seen for implementing an intermediate representation. Making the encoding and decoding interface abstract is all that needs to be done to support multiple serialization formats with the same code. In Swift, types that support serialization and deserialization implement the Encodable and Decodable protocols, respectively, and both of those receive an abstract interface to work with.

Changing field names

The possibility always exists that the field names used in a serialized representation will change, but that’s still no reason to reach for a DTO. It’s one of the easiest format changes to handle. Just like it isn’t a good idea to use magic numbers, it isn’t a good idea to hard-code the identifiers used in serialization either. The solution is the same: use constants to hold the identifiers, then if the names change, only the constants need to be changed. Swift programmers can provide alternative names for a type’s instance properties in the compiler-generated code by defining a CodingKeys enum that conforms to the CodingKey protocol inside their Codable conforming type.

Changing layout

A change in the external representation of a type need not affect how a domain model appears to the rest of the app, even if it implements serialization in the same type. How this issue is handled depends on whether one or multiple variations need to be supported.

If the current serialized representation is the only one that has to be supported, updating the existing code, or replacing compiler-generated code with a custom implementation, is all that needs to be done.

On the other hand, if multiple variations have to be supported, the strategy pattern can be used. If the variations are indicated by the presence of a field with a specific value, or some other easily detected feature, the strategy can be selected using that value or feature. Otherwise, each strategy will have to be attempted until one succeeds.

Changing nullability

This is, without a doubt, the most ridiculous reason I’ve EVER heard to justify duplicating a domain type to handle serialization. How the null would normally be handled when converting from the intermediate representation to the domain model is how it should be handled when deserializing the type. Easy!

Is a DTO good for anything?

I read Martin Fowler’s original definition of the data transfer object in my research for this article, and learned of one usage that’s frequently overlooked: batching interprocess and network requests. In that role, it can often make sense to merge multiple pieces of data into a single object to avoid data duplication in the request payload and reduce the overhead of making separate requests. This doesn’t mean, however, that the structure of existing types need to be duplicated, but it’s precisely how most developers who enthusiastically promote the use of DTOs say they should work. In this case, composing the DTO from existing types, and implementing custom serialization logic, is almost always a better solution than the wholesale duplication of the existing types.

It’s tempting to see one design pattern and apply it to every problem that seems to fit. But in the duplication of data structures, the risk always exists that bugs will be introduced unnecessarily at the point where the structure of one type is mapped into the structure of another. This applies to serialization as well, of course, but implementing serialization and deserialization will usually require less custom code than duplicating an existing type and implementing transformers for it.

The best solution for any problem depends on the nature of the issue to be resolved. In this article, I focused mainly on challenges presented by serialization, but similar solutions are also available for crossing domain boundaries in the same process, and for issues I didn’t mention at all. Given that less error-prone solutions exist, data structure duplication should be a last resort, not the default solution for every cross-domain challenge a developer might face.


메타데이터
post_id
6d219ab487e3
slug
most-dtos-are-boilerplate-nonsense-6d219ab487e3
url
https://medium.com/@webboy42/most-dtos-are-boilerplate-nonsense-6d219ab487e3
canonical_url
https://medium.com/@webboy42/most-dtos-are-boilerplate-nonsense-6d219ab487e3
author_url
https://medium.com/@webboy42
status
ok
fetched_at
2026-06-09 14:34:10