When DTOs Start Becoming Your Domain Model
Architecture Principles · 2026-06-06
A practical exploration of how DTOs gradually absorb validation, workflow logic, and business meaning — and why systems become harder to understand when transport models start replacing the domain itself.
When DTOs Start Becoming Your Domain Model
1. Introduction: DTOs are supposed to cross boundaries
Most systems use DTOs in some form.
Requests arrive from APIs or UI layers. Data is transferred between services. Results are returned to clients. Persistence models are reshaped into responses. Somewhere in that flow, objects are often introduced whose primary purpose is simply to carry information from one boundary to another.
That is what DTOs are meant to do.
A Data Transfer Object exists to move data across a boundary without exposing internal implementation details directly. In many systems, DTOs help separate:
- transport concerns from domain behavior,
- external contracts from internal models,
- persistence structures from application workflows,
- or UI requirements from business meaning. Used this way, DTOs are useful and often necessary.
A simple request model such as this is usually harmless:
public sealed record CreateArticleRequest(
string Title,
string ContentMarkdown);
The object communicates input. It does not attempt to own the business meaning of publishing, validation policy, workflow coordination, or persistence behavior. It simply carries data into the application.
Problems usually begin gradually.
A validation rule is added because “the DTO already has the fields.” A convenience property appears because the UI needs it. A formatting rule is added because it seems practical. A workflow flag is introduced. A helper method follows. Eventually the DTO starts becoming more than a transport object.
At first, none of this necessarily looks dangerous. The code may still compile cleanly. The application may still function correctly. In fact, the DTO may initially appear easier to work with because more logic is concentrated in one visible place.
But over time, something important starts changing:
- the domain model becomes thinner,
- business meaning becomes scattered,
- responsibilities become less clear,
- and the architecture gradually loses its sense of ownership. The problem is not that DTOs exist. The problem is that transport models often begin absorbing responsibilities that belong elsewhere.
This usually happens quietly because DTOs sit near boundaries where many concerns naturally meet:
- validation,
- serialization,
- UI shaping,
- workflow coordination,
- persistence mapping,
- and business operations. Without deliberate architectural boundaries, those concerns begin accumulating inside the same objects simply because the objects are already there.
This creates a subtle but important architectural shift.
The system gradually stops expressing business behavior through domain concepts and starts expressing it through data structures moving between layers.
That transition is easy to miss because the system may still look architecturally clean from a distance. Layers may still exist. Services may still be separated. Interfaces may still be present. But the actual ownership of business meaning has started drifting away from the domain and toward transport objects that were never intended to carry it.
A useful distinction is therefore:
A DTO should carry data across a boundary — not become the place where business meaning lives.
The challenge is not avoiding DTOs. The challenge is preventing them from quietly becoming the architecture itself.
2. How DTOs gradually absorb business meaning
DTOs rarely become problematic all at once.
Most systems do not begin with developers intentionally deciding that transport objects should own business rules, workflow behavior, validation policy, or domain meaning. The shift usually happens gradually through a series of individually reasonable decisions.
A DTO starts as a simple transport object:
public sealed record CreateArticleRequest(
string Title,
string ContentMarkdown);
Its purpose is clear:
- receive input,
- move data across a boundary,
- and communicate structure. At this stage, the DTO does not own behavior. It does not decide whether an article is publishable, valid for business purposes, or allowed within a workflow. It merely carries information.
The gradual shift begins when additional concerns accumulate because the DTO already contains the relevant fields.
For example, validation logic may appear:
public sealed class CreateArticleRequest
{
public string Title { get; init; } = "";
public string ContentMarkdown { get; init; } = "";
public bool IsValid() =>
!string.IsNullOrWhiteSpace(Title) &&
ContentMarkdown.Length >= 500;
}
At first glance, this may seem harmless. The object already contains the data, so placing validation nearby feels convenient.
But an important question immediately appears:
Is this validation expressing transport correctness or business meaning?
That distinction matters.
Checking whether a required field is present for deserialization or API contract purposes may belong near the DTO. But deciding whether an article is substantial enough to publish may be a business rule rather than a transport concern.
Once that boundary becomes unclear, more logic often follows naturally.
Convenience properties appear:
public bool CanPublish =>
!string.IsNullOrWhiteSpace(Title) &&
ContentMarkdown.Length >= 500 &&
ApprovedByEditor;
Formatting behavior may follow:
public string PreviewText =>
ContentMarkdown.Length <= 200
? ContentMarkdown
: ContentMarkdown[..200] + "...";
Workflow decisions may begin appearing:
public bool RequiresEditorialReview => Category == "Architecture";
Eventually the DTO may even start coordinating behavior directly:
public async Task PublishAsync()
{
...
}
At that point, the object is no longer merely transporting data across a boundary. It is beginning to act as a partial domain model, workflow coordinator, formatting utility, and UI helper simultaneously.
The problem is not that any individual addition is necessarily catastrophic. The problem is that the architecture gradually loses clarity about ownership.
Business meaning begins drifting toward the transport layer simply because the DTO sits at the intersection of many concerns:
- API input,
- serialization,
- UI shaping,
- validation,
- workflow state,
- formatting,
- and application coordination. The object becomes attractive as a “convenient place” for logic because it is already widely accessible.
This creates several long-term problems.
First, business rules become scattered. Some rules may exist in DTOs, others in domain objects, others in services, and others in UI logic. The system becomes harder to reason about because ownership is no longer clear.
Second, DTOs often become coupled to multiple unrelated concerns simultaneously:
- API contracts,
- UI behavior,
- business validation,
- workflow state,
- and persistence assumptions. Changing one concern may unexpectedly affect several others because the DTO has become a shared center of responsibility.
Third, the domain model itself often becomes weaker. If business behavior increasingly lives in DTOs, services, or handlers, the domain objects gradually become passive containers rather than meaningful business concepts.
This shift is especially subtle because DTOs usually begin life as intentionally “simple” objects. Developers may therefore feel comfortable adding small amounts of logic repeatedly because the object already appears lightweight and convenient.
But over time, convenience quietly becomes ownership.
A useful warning sign is:
When developers begin looking at DTOs to understand business behavior, the DTOs may already be absorbing too much meaning.
That does not mean DTOs should remain permanently empty or that every helper property is automatically wrong. The important question is whether the object is still primarily serving as a transport boundary — or whether it has started becoming the place where the system expresses business understanding itself.
3. The rise of the passive domain model
One of the clearest signs that DTOs are absorbing too much meaning is that the domain model gradually stops doing very much at all.
The entities still exist. The projects and layers may still look architecturally organized. Domain classes may still have names that suggest important business concepts. But when developers look more closely, the actual business behavior increasingly lives somewhere else.
The domain objects become passive containers for data while services, DTOs, handlers, controllers, and UI models begin making the real decisions.
For example, a healthy domain model may express publishing behavior directly:
public sealed class Article
{
public void Publish(DateTime publishedUtc)
{
if (IsPublished)
{
throw new InvalidOperationException(
"The article has already been published.");
}
if (ContentMarkdown.Length < 500)
{
throw new InvalidOperationException(
"The article content is too short.");
}
IsPublished = true;
PublishedAtUtc = publishedUtc;
}
}
The object owns:
- the publishing concept,
- the publishing rules,
- and the state transition. The behavior and the meaning remain together.
By contrast, a passive domain model often looks like this:
public sealed class Article
{
public string Title { get; set; } = "";
public string ContentMarkdown { get; set; } = "";
public bool IsPublished { get; set; }
public DateTime? PublishedAtUtc { get; set; }
}
The entity now merely stores state.
The actual publishing logic may instead appear inside:
- DTOs,
- application services,
- controllers,
- workflow handlers,
- UI models,
- or validation layers. For example:
public sealed class PublishArticleRequest
{
public string Title { get; init; } = "";
public string ContentMarkdown { get; init; } = "";
public bool CanPublish =>
!string.IsNullOrWhiteSpace(Title) &&
ContentMarkdown.Length >= 500;
}
Or:
if (request.CanPublish)
{
article.IsPublished = true;
article.PublishedAtUtc = _clock.UtcNow;
}
At this point, the domain object is no longer expressing business behavior. It is simply being manipulated by external logic.
This creates several architectural problems.
First, the system loses a clear home for business meaning.
If developers want to understand:
- what publishing means,
- when publishing is allowed,
- or what state transitions are valid, they may need to inspect multiple DTOs, services, handlers, and UI components rather than a coherent business concept.
Second, business rules become easier to duplicate.
If the domain object no longer protects its own invariants, every caller becomes responsible for remembering the rules independently. One handler may check content length. Another may forget. One endpoint may require approval. Another may accidentally bypass it.
The architecture gradually shifts from:
- protected behavior, to:
- distributed discipline. Third, passive domain models often encourage increasingly procedural application services.
As the entities lose behavior, higher layers begin coordinating more and more detailed business decisions directly:
public async Task PublishAsync(
PublishArticleRequest request,
CancellationToken ct)
{
var article = await _repository.GetByIdAsync(
request.ArticleId, ct);
if (article is null)
{
throw new ArticleNotFoundException();
}
if (string.IsNullOrWhiteSpace(article.Title))
{
throw new InvalidOperationException(
"Title is required.");
}
if (article.ContentMarkdown.Length < 500)
{
throw new InvalidOperationException(
"Article content is too short.");
}
article.IsPublished = true;
article.PublishedAtUtc = _clock.UtcNow;
await _repository.SaveAsync(article, ct);
}
The service now owns details that conceptually belong to the publishing behavior itself.
The issue is not that application services should contain no logic at all. Application services often coordinate workflows, transactions, permissions, integration points, and orchestration. But when they begin owning the actual business meaning of the operation, the domain becomes increasingly hollow.
This problem is especially deceptive because passive domain models can initially appear simpler.
The entities become:
- lightweight,
- serialization-friendly,
- easy to map,
- and easy to mutate. But the simplicity is often superficial. The complexity has not disappeared. It has merely been redistributed into places where ownership becomes less clear.
A useful warning sign is:
If most business rules live outside the business concepts themselves, the domain model may no longer be modeling much of the domain.
That does not mean every system requires deeply behavioral entities or complex domain-driven design patterns. Some systems genuinely are simple CRUD applications where richer domain behavior adds little value.
The important question is whether the system still has a coherent place where business meaning lives — or whether the meaning has gradually dissolved into transport objects and procedural coordination code simply because those locations became more convenient over time.
4. Why “just data” starts becoming architecture
One reason DTOs gradually absorb business meaning is that “just data” appears harmless.
A transport object seems neutral. It carries fields from one layer to another. It does not initially look architectural in the same way as services, repositories, workflows, or domain models. Because of that, developers often feel comfortable adding small amounts of additional responsibility to the DTO over time.
But data structures are never entirely neutral.
The shape of data influences:
- what callers know,
- what they are expected to decide,
- what becomes easy to express,
- and where business meaning naturally accumulates.
Over time, the structure of DTOs begins shaping the architecture itself.

Healthy architecture keeps DTOs focused on transport boundaries, while DTO-centered architecture gradually shifts business meaning, workflow coordination, and validation responsibility into transport models.
For example, consider a response model like this:
public sealed class ArticleDto
{
public string Title { get; init; } = "";
public bool IsPublished { get; init; }
public bool CanPublish { get; init; }
public bool RequiresEditorialReview { get; init; }
public bool IsVisibleToAnonymousUsers { get; init; }
}
At first glance, this may still look like “just data.”
But the object is already communicating business meaning:
- what publishing means,
- when review is required,
- visibility rules,
- and workflow state. The DTO is no longer merely carrying information. It is becoming part of how the system expresses its understanding of the business itself.
This shift matters because architecture is heavily shaped by where decisions become visible.
If callers repeatedly depend on DTO-level flags such as:
- CanPublish,
- RequiresApproval,
- IsVisible,
- or ShouldArchive, then the transport model gradually becomes the place where workflows and business understanding are discovered.
The DTO starts acting as an informal domain layer.
This often leads to a subtle architectural inversion:
instead of the domain shaping the DTOs,
the DTOs begin shaping the domain. For example, once a UI depends on:
CanPublish,
CanEdit,
CanDelete,
CanApprove,
CanArchive, those properties become difficult to move elsewhere because external layers have already started depending on them directly.
The DTO evolves into a shared behavioral contract.
At that point:
- services begin coordinating around DTO flags,
- controllers begin branching on DTO state,
- workflows begin depending on transport semantics,
- and the actual domain concepts become increasingly secondary. The architecture slowly reorganizes itself around data movement rather than business meaning.
This is especially common in systems where:
APIs dominate the design,
frontend requirements strongly shape models,
or mapping layers become the primary architectural activity. The system may still technically contain:
entities,
services,
repositories,
and abstractions, but the real conceptual center of gravity has shifted toward transport structures.
This also affects discoverability.
If developers want to understand publishing behavior and the answer is spread across:
- DTO flags,
- UI conditions,
- API response properties,
- validation attributes,
- and application handlers, then the architecture no longer communicates ownership clearly.
The business meaning exists, but it has become fragmented across data contracts.
A useful warning sign is:
When DTOs begin expressing the system’s behavior more clearly than the domain model does, the transport layer may be becoming the architecture.
This does not mean DTOs should contain no derived values or convenience properties. In many systems, shaping responses for clients is entirely reasonable.
The important distinction is whether the DTO is:
- exposing information derived from business behavior, or:
- becoming the place where the business behavior itself is defined. That boundary is often subtle, but it strongly affects how understandable the system remains as it evolves.
5. Validation drift and duplicated rules
One of the most common consequences of DTOs absorbing business meaning is that validation gradually loses a clear owner.
At first, the validation may appear harmless and localized.
For example:
public sealed class CreateArticleRequest
{
public string Title { get; init; } = "";
public string ContentMarkdown { get; init; } = "";
public bool IsValid() =>
!string.IsNullOrWhiteSpace(Title) &&
ContentMarkdown.Length >= 500;
}
The DTO already contains the fields, so placing validation nearby feels practical. The object appears self-contained, and callers can quickly check whether the request is acceptable.
But over time, additional validation often begins appearing elsewhere too.
The UI may perform client-side checks:
if (titleInput.Length > 200)
{
ShowValidationError("The title is too long.");
}
The application service may repeat similar rules:
if (request.ContentMarkdown.Length < 500)
{
return PublishArticleResult.Failed(
"The article content is too short.");
}
The domain object may protect invariants independently:
public void Publish(DateTime publishedUtc)
{
if (ContentMarkdown.Length < 500)
{
throw new InvalidOperationException(
"The article content is too short.");
}
...
}
Eventually the same business rule may exist simultaneously:
- in the DTO,
- in the UI,
- in the application layer,
- and in the domain model. This is where validation drift begins.
The problem is not merely duplication. The more serious issue is that the architecture no longer communicates which layer actually owns the rule.
Different kinds of validation belong in different places.
For example:
- transport validation may ensure required fields are present,
- UI validation may improve user experience,
- API validation may protect contracts,
- and domain validation may protect business invariants. Those responsibilities are not identical, even when they involve similar data.
The confusion begins when DTOs start mixing:
- transport correctness,
- workflow policy,
- and business meaning into the same validation logic.
For example, this may initially look reasonable:
public bool CanPublish =>
!string.IsNullOrWhiteSpace(Title) &&
ContentMarkdown.Length >= 500 &&
ApprovedByEditor;
But the DTO is now expressing:
- publishing policy,
- workflow requirements,
- and domain behavior. The validation is no longer merely checking whether the request structure is acceptable. It is deciding whether the business operation itself is allowed.
Once this happens, duplicated rules become extremely difficult to avoid because other layers still need to protect themselves independently.
- The UI may need immediate feedback.
- The API may need contract validation.
- The domain may still need invariant protection.
- The workflow layer may still need authorization or approval logic. Without clear ownership boundaries, each layer gradually reimplements overlapping pieces of the same business rule.
This creates several long-term problems.
First, rules become inconsistent.
- One layer may require 500 characters.
- Another may require 300.
- One endpoint may check approval state.
- Another may forget. The system slowly loses behavioral coherence.
Second, developers stop knowing where to look.
If a publishing rule changes, should the update happen:
- in the DTO,
- in the validator,
- in the application service,
- in the UI,
- or in the domain object? The architecture no longer provides a clear answer.
Third, changes become riskier.
Because the same rule exists in multiple locations, modifying behavior safely requires discovering every duplicated version. Missing one copy can create subtle inconsistencies that only appear under specific workflows.
This is one reason DTO-heavy systems often become harder to maintain over time even when the individual objects appear simple.
The simplicity exists locally, but the responsibility ownership becomes globally unclear.
A useful distinction is:
Validation should usually exist closest to the responsibility it protects.
For example:
- DTO validation may protect transport correctness,
- UI validation may improve interaction flow,
- but domain validation should protect business invariants. Those are related concerns, but they are not interchangeable.
A useful warning sign is:
When the same business rule exists in several DTOs, services, and UI models simultaneously, validation ownership may already be drifting.
The problem is not that multiple layers validate. Multiple layers often should validate. The problem is when the architecture no longer communicates which layer is responsible for protecting which kind of correctness.
6. APIs that expose workflow instead of intent
Another sign that DTOs are beginning to dominate the architecture is when APIs start exposing workflow mechanics instead of business intent.
At first, the difference may seem subtle.
An API often begins with straightforward request and response models:
public sealed record PublishArticleRequest(string ArticleId);
The request communicates intent clearly:
the caller wants to publish an article. The API expresses:
what operation is being requested, not:
how the internal workflow happens to function. Over time, however, DTOs often begin accumulating workflow-specific flags and transitional state:
public sealed class ArticleDto
{
public bool CanPublish { get; init; }
public bool RequiresEditorialReview { get; init; }
public bool IsPendingApproval { get; init; }
public bool HasCompletedMetadataValidation { get; init; }
public bool IsEligibleForPublicListing { get; init; }
}
At this point, the transport model is no longer simply communicating business data. It is increasingly exposing the internal workflow structure itself.
The DTO becomes a map of process coordination rather than a focused expression of business intent.
This creates several architectural problems.
First, external layers begin depending on workflow details directly.
The UI may now contain logic such as:
if (article.CanPublish && !article.RequiresEditorialReview)
{
ShowPublishButton();
}
Or:
if (article.IsPendingApproval)
{
DisplayPendingState();
}
Over time, the client becomes tightly coupled to the internal coordination mechanics of the system.
The problem is not that the UI needs information. The problem is that the workflow itself is leaking outward through DTO contracts.
Second, APIs shaped around workflow flags often become increasingly procedural.
Instead of expressing meaningful operations:
POST /articles/{id}/publish
the system gradually shifts toward state manipulation APIs:
PATCH /articles/{id}
{
"isPublished": true,
"isPendingApproval": false
}
At that point, the caller is no longer requesting a business operation. The caller is directly manipulating workflow state.
This weakens encapsulation significantly because the system no longer controls transitions through explicit business behavior. Instead, external layers begin coordinating the workflow themselves through DTO mutation.
Third, workflow-oriented DTOs often encourage increasingly fragmented business logic.
One layer checks:
CanPublish Another checks:
RequiresEditorialReview Another checks:
IsEligibleForPublicListing Eventually no single place clearly owns the meaning of publishing anymore. The workflow becomes distributed across consumers interpreting transport flags independently.
The architecture slowly shifts from:
- business operations, to:
- shared interpretation of state. This also affects discoverability.
If developers want to understand:
how publishing works,
when approval is required,
or what transitions are valid, they may need to inspect:
DTO flags,
UI conditions,
controller branching,
frontend behavior,
and API response structures rather than a coherent business concept inside the domain or application layer.
A more intention-revealing design usually keeps workflows behind explicit operations:
await _articlePublishingService.PublishAsync(articleId, ct);
or:
article.Publish(_clock.UtcNow);
The caller requests a business action. The system itself decides:
- whether publishing is allowed,
- whether approval is required,
- what transitions occur,
- and what side effects follow. The workflow remains internally coordinated rather than externally reconstructed.
This does not mean APIs should never expose state or derived information. Clients often legitimately need:
visibility information,
progress indicators,
or display-oriented workflow summaries. The important distinction is whether the API is:
informing the caller about the current business state, or:
requiring the caller to coordinate the workflow itself. A useful warning sign is:
When clients must understand internal workflow mechanics in order to use the API correctly, the DTOs may be exposing coordination rather than intent.
Good APIs usually communicate:
- what the caller wants to achieve, not:
- how the internal state machine happens to work.
7. When DTOs are actually appropriate
The goal is not to avoid DTOs.
DTOs solve real problems, and many systems would be worse without them. They help define boundaries, protect internal models, shape data for clients, and keep external contracts stable even when the internal implementation changes.
The problem is not the DTO itself. The problem is misplaced responsibility.
DTOs are especially appropriate when data needs to cross a boundary.
For example, a request model is useful when receiving input from an API, UI, or external system:
public sealed record CreateArticleRequest(
string Title,
string ContentMarkdown);
This object describes the data required to ask for an operation. It does not need to own the operation itself.
A response model is also useful when returning data to a client:
public sealed record ArticleSummaryResponse(
string Title,
string Summary,
DateTime? PublishedAtUtc);
This object shapes output for a particular use. It avoids exposing internal entities directly and allows the application to decide what information should cross the boundary.
DTOs are also appropriate when the external contract differs from the internal model.
For example, an internal Article object may contain fields, methods, invariants, and relationships that should not be exposed directly through an API. A response DTO can provide only the information needed by the caller:
public sealed record ArticleDetailsResponse(
string Title,
string ContentHtml,
string Category,
bool IsPublished);
This protects the domain model from being shaped entirely by external presentation needs.
DTOs are also useful for versioning. An API contract may need to remain stable even while the internal model evolves. In that situation, DTOs provide a useful layer of separation between:
- what external callers depend on,
- and how the system chooses to organize itself internally. They are also useful when different clients need different shapes of data. An admin screen, public article page, search result, export function, and integration endpoint may all need different representations of the same underlying concept.
A DTO can express those different representations without forcing the domain model to satisfy every presentation need directly.
For example:
public sealed record PublicArticleSummary(
string Title,
string Summary,
string Slug);
public sealed record AdminArticleListItem(
string Title,
string Slug,
bool IsPublished,
DateTime? PublishedAtUtc);
These DTOs are doing useful work. They shape information for specific boundaries and avoid leaking unnecessary internal detail.
The important distinction is that these DTOs represent views of business concepts. They should not become the primary owners of the business concepts themselves.
A healthy DTO usually:
carries data across a boundary,
expresses a specific input or output shape,
protects internal models from external coupling,
avoids unnecessary behavior,
and remains tied to a clear use case or contract. An unhealthy DTO tends to:
accumulate business rules,
coordinate workflows,
expose internal process mechanics,
become shared across unrelated use cases,
and make the domain model less meaningful. A useful question is:
Is this DTO describing information that crosses a boundary, or is it deciding what the business operation means?
If it is describing information, it is probably serving its purpose. If it is deciding meaning, enforcing workflow, or becoming the place developers inspect to understand business behavior, it may be carrying more responsibility than it should.
DTOs are valuable when they preserve boundaries. They become problematic when they replace the concepts those boundaries were meant to protect.
8. Signs the DTOs are taking over
DTO-heavy architectures rarely become problematic overnight.
The transition usually happens gradually through many individually reasonable decisions:
- one more convenience property,
- one more validation rule,
- one more workflow flag,
- one more helper method,
- one more service branching on DTO state. Because each individual step appears small, the architectural shift often remains difficult to notice until the system has already become harder to reason about.
For that reason, it is useful to recognize the warning signs early.
One common sign is that developers increasingly look at DTOs to understand business behavior.
For example, if someone wants to understand:
- when publishing is allowed,
- what approval requires,
- why an article becomes visible,
- or what state transitions are valid, and the answer primarily exists in DTO properties and transport models, then business meaning may already be drifting away from the domain.
Another sign is that the domain model itself becomes increasingly passive.
Entities may still exist, but they mainly expose:
- getters,
- setters,
- state containers,
- and persistence-friendly structures, while the actual rules and decisions live elsewhere.
For example:
public sealed class Article
{
public string Title { get; set; } = "";
public bool IsPublished { get; set; }
}
combined with:
public sealed class ArticleDto
{
public bool CanPublish => !string.IsNullOrWhiteSpace(Title) &&
ApprovedByEditor;
}
suggests that the DTO is beginning to express more business meaning than the entity itself.
Another warning sign is widespread duplication of business rules.
For example:
- DTO validation,
- UI conditions,
- controller branching,
- workflow flags,
- and application services all independently checking variations of the same publishing rules.
When developers must update several DTOs and handlers to change one business policy, ownership is likely becoming fragmented.
A related sign is that APIs begin exposing increasingly detailed workflow mechanics.
For example:
CanPublish
RequiresApproval
IsEligibleForPublicListing
HasCompletedMetadataValidation
may indicate that clients are gradually becoming responsible for coordinating workflow decisions themselves.
The system stops communicating intent and starts exposing internal process structure instead.
Another important warning sign is DTO reuse across unrelated concerns.
For example, the same DTO may gradually become used for:
- API transport,
- UI rendering,
- persistence mapping,
- validation,
- workflow coordination,
- exports,
- background jobs,
- and integration endpoints. At that point, changing the DTO safely becomes increasingly difficult because many unrelated parts of the system now depend on it simultaneously.
This often creates “gravity” around the DTO. New behavior keeps accumulating there simply because the object is already shared widely.
Another warning sign is that mapping becomes the dominant architectural activity.
When systems contain:
- DTO → DTO mapping,
- DTO → entity mapping,
- entity → DTO mapping,
- response shaping,
- transformation pipelines,
- enrichment layers,
- and duplicated state projections, developers may gradually spend more effort moving data structures around than expressing business behavior clearly.
The architecture becomes organized around transformation mechanics rather than domain understanding.
A subtler sign is that developers stop speaking in terms of business concepts and start speaking in terms of transport structures.
For example:
“The DTO says this can publish.” “The response model controls approval.” “The request determines visibility.”
instead of:
“The publishing policy allows this.” “The article can be published.” “The workflow requires approval.”
The language itself begins shifting toward data movement rather than business meaning.
This matters because architecture is heavily influenced by where developers naturally look for understanding.
A useful warning sign is therefore:
When DTOs become the easiest place to discover business behavior, they may already be becoming the architecture itself.
That does not mean every large DTO is automatically wrong or that every derived property is dangerous. Some systems legitimately require rich transport models for performance, client shaping, reporting, or integration scenarios.
The important question is whether the DTOs are still serving boundaries — or whether the boundaries themselves are gradually disappearing behind increasingly behavior-heavy transport objects.
9. Conclusion
DTOs are useful tools.
They help systems cross boundaries cleanly, shape information for clients, stabilize contracts, and separate external concerns from internal implementation details. Most non-trivial systems benefit from them in some form.
The problem begins when DTOs gradually stop acting as transport models and start becoming the place where the system expresses business meaning itself.
That shift rarely happens intentionally.
It usually emerges through convenience:
validation added because the fields are already present,
workflow flags introduced for the UI,
helper methods added to simplify callers,
business decisions expressed through transport state,
and APIs gradually shaped around process mechanics rather than intent. Each individual change may appear reasonable in isolation. Over time, however, the architecture slowly loses clarity about ownership.
Business rules become scattered.
Validation drifts across layers.
Workflows leak through DTO contracts.
The domain model becomes increasingly passive.
Developers begin looking at transport structures to understand how the business actually behaves. The system may still appear architecturally organized from a distance:
layers still exist,
services still exist,
repositories still exist,
abstractions still exist. But the conceptual center of gravity has shifted toward data movement rather than business meaning.
That shift matters because software becomes easier to maintain when responsibilities remain discoverable.
Developers should be able to understand:
- where business decisions belong,
- which layer protects which kind of correctness,
- what operations mean,
- and where workflow coordination actually lives. When DTOs begin accumulating too much responsibility, those answers become harder to discover because meaning is distributed across transport models, workflow flags, validation helpers, and procedural coordination code.
The goal is therefore not to eliminate DTOs. The goal is to keep their responsibility clear.
A healthy DTO usually:
- carries information across a boundary,
- shapes data for a specific use case,
- protects internal models from external coupling,
- and remains focused on transport concerns. It should help the architecture communicate clearly — not quietly become the architecture itself.
This idea closely relates to the architectural guidance discussed in another article in this series. 1
A useful distinction is:
DTOs should describe business data crossing boundaries, not replace the business concepts those boundaries were meant to protect.
That distinction is subtle at first, but over time it strongly affects:
- discoverability,
- ownership,
- maintainability,
- and the ability of the system to express business meaning coherently as it evolves.
10. References
Need a second opinion on a .NET architecture decision?
I offer practical architecture and maintainability advisory for .NET teams working on long-lived business applications.
Want occasional updates?
Subscribe to receive occasional practical notes on .NET architecture, maintainable software, project progress, and new articles.