APIs use data transfer objects (DTOs) to compose data from multiple domain entities into a single request. A DTO is an object that carries data between application layers to reduce the number of function calls (Fowler, M.). Compared to RESTful APIs, DTO APIs reduce the number of network requests sent between the API and clients. DTOs also obviate the need for field masks: query parameters used to return only the entity properties desired by clients. While DTO APIs and RESTful APIs are sometimes posed as an either-or choice, we can use them in combination to reduce demand on client devices while maintaining loose coupling between clients and backing services.
Web Interface As DTO API Client
A common client for a DTO is a web interface. Say we have a dashboard interface that presents a few pieces of data from many domain entities. Calling a RESTful API requires a request for each domain entity. Each request requires field masks to ensure we send only data the interface needs. A purpose-built DTO endpoint retrieves the exact data the client needs in a structure convenient to the client.
For an interface that updates multiple domain entities, we similarly expose a single endpoint expecting a DTO with updated data. By using DTOs, we allow the client to focus on presenting and interacting with data without holding responsibility for collating them.
DTOs Reduce Demand On Clients
By alleviating responsibility for data collation from the client, DTOs reduce demand on remote client devices. Network requests are a primary source of battery drain due to power required by WIFI and cellular radios (Android Developers, 2019). These radios consume power to send and receive packets as well as to power on and stay awake. Polling for data may keep radios powered on continuously, quickly using up battery power. DTOs conserve battery power by reducing network requests.
Once the client retrieves data from a RESTful API, it may need to reformat data into a shape suitable for the interface. This processing requires additional CPU cycles which, depending on processing scale, could significantly drain battery power. The time it takes to reorganize data may slow the time to which users interact with data on CPU-constrained devices. With a DTO, we’re ready to bind the data to the interface as soon as we receive it.
Transactional Execution Context
An advantage of the data transfer object (DTO) is the ability to execute database operations in a single transaction, assuming all the data exists in a single data store. If a DTO operation fails, many databases will automatically handle the failure and rollback for us.
Contrast the DTO transaction with a batch request endpoint accepting multiple operations within a single request. As a client, we have to wait for the response to see if any requests failed. If we want to perform a rollback on failure, we have to manually perform the rollback at the time we receive the response. In the meantime, other requests may be fetching the data I intend to roll back.
We could add metadata to the batch request to indicate our request dependencies and instructions for rollback in the case of failures. However, this approach is merely shifting complexity from the client to the server rather than reducing it.
For “all or nothing” operations, DTOs simplify operations that update a single data store.
Application Servers Provide DTO APIs
If we don’t collate data on the client, where does this work occur? We can use an application server to initiate requests to RESTful APIs and reformat responses into a DTO. When located in close proximity to backing services, the application server often experiences less latency than remote clients. While a request for a single domain entity through the application server may result slightly more latency due to the additional network hop, for a DTO generated from many requests, the reduction in latency can be significant. Furthermore, application servers tend to be less constrained in terms of power, CPU, and bandwidth than client devices, thereby upgrading performance of clients.
DTO API Versus RESTful API
Why not eliminate the RESTful API all together? Eliminating the RESTful API may make sense if we have a single client retrieving data from a domain managed by a single service. In this case, we are not aggregating data from multiple services, and we consume the data in a single format. Note that by building the DTO API only, we’re tightly coupling the backing service to the needs of the client. Since there is only one client and one service, a change in one necessitates changes to the other. Therefore, we don’t pay an additional cost for this coupling, and we save effort by not building the RESTful API.
When we retrieve data from multiple domains, multiple services likely expose pieces of the domain model, each with its own API. In this situation, we need to aggregate the domain data somewhere, be it on the server or the client. Where the aggregation occurs depends the resource constraints of the client and determining which piece of the architecture should have responsibility for translating the domain model to the client interface.
When multiple clients exist for our data, it’s unlikely all utilize the data in the same format. Some clients may be perfectly happy consuming data directly from RESTful APIs. For others, resource constraints encourage developing application servers that send and receive DTOs. How many application servers we build depends on the variance in our clients’ interfaces and the benefit we get from taking data collation off clients’ plates.
When To Use DTOs
DTOs provide the greatest benefit to remote clients interacting with many domain entities. For such clients, the aggregate latency of requests required to construct the client interface may degrade the user experience. For remote clients with power, CPU, or bandwidth constraints, alleviating responsibility for collating data conserves limited resources. While sometimes pitted against RESTful APIs, constructing a remote DTO API from local RESTful APIs is a helpful approach for improving the performance of remote clients.