If I understand the concept of read models correctly, then it's just a simple query to return a use case specific data set.
That's not quite right.
DTO's are a boundary artifact
an object that carries data between processes in order to reduce the number of method calls -- Martin Fowler, Patterns of Enterprise Application Architecture.
These days, where we try to avoid adding constraints that entangle clients and servers, we're more likely to use a representation of a message (bytes) to cross a process boundary. (What's the difference? we send data, but we don't send the getters and setters over the wire). So the "DTO" becomes the object representation of those bytes in this process.
"Read models" conceptually belong in the core -- they are the portions of our domain model that are written to provide query support. They arise from the logical bifurcation of the domain model into query responsibilities and mutation responsibilities.
A way to recognize the difference -- if your data is in language primitives (bytes, ints, Strings), then you are probably looking at a DTO. If you are looking at domain concepts (account numbers, amounts, SKUs) then you are more likely in the read model.
(That's not perfect, of course, there are a lot of "anemic" and primitive obsessed domain models out there).
Should I use the read model for "simple" data queries, and DTOs for more complex situations like rich user interfaces?
Pretty much - if you already have a representation of data suitable for the clients needs, then just ship it -- there's not a lot of value in going through the domain model. You are especially likely to see this for queries that can take advantage of a cache.
(A common pattern is to have a back ground process that uses the read model to pre-calculate representations useful for queries, and store the results in a cache -- live queries just grab the representation in the cache and go.
It's not entirely wrong to think of that background process using the read model to compute DTO representations that are stored in the cache.
So my read-models that represent the search results should be part of the domain model? Feels weird because I was under the impression that search is just a simple service.
I suspect the confusion comes from thinking that read models only exist in services. That isn't true -- the same pattern also fits in applications.
If we are sending a query across a process boundary, then ultimately we are going to be communicating bytes
, and if we already have the bytes that we need, there's no reason to hydrate them into a domain representation first just so that we can turn them into bytes again.
Similarly, if you are answering a query with data in an RDBMS, you are probably going to transform the ResultSet to bytes, or the ResultSet to JSON to bytes, or whatever. You certainly don't need domain entities, and you probably don't even need domain values.