Patterns for Decoupling in Distributed Systems: Domain Query

Replace Free Queries with Domain Queries to decouple from knowledge of the server's internals.

By Mathias Verraes
Published on 08 May 2019



Domain Query

Replace Free Queries with Domain Queries to decouple from knowledge of the server’s internals.

Problem

The word query is usually associated with database queries. There are however other ways we can query a system that we don’t perceive as a database. REST and GraphQL come to mind. I propose the term Free Query for every type of message that knows about the structure, schema, endpoints, … and has a great freedom in accessing and combining data from that system, typically using a rich query language.

In a very simple setup with a single client and server, owned by a single team, this isn’t a great problem, as changes to the server and client can be applied simultaneously.

In a more complex environments, where multiple clients are outside the control of the server’s team, it is. The database schema or service endpoints are essentially a public API. The clients have intimate knowledge of the schema. We don’t know what queries they will send us, so the only way to guarantee backwards compatibility of that API is to keep the schema intact. Sure, we could log the queries, but that doesn’t guarantee there are yet-to-be sent queries hiding in the client.

Solution

Instead of exposing the schema or endpoints and allowing Free Queries, define a set of domain-specific messages and responses. These Domain Queries consist of a name and a small number of arguments. Both the Domain Queries and their responses are highly tailored to the use case and only use the Ubiquitous Language. I recommend the name to be a question expressed in natural language.

The server provides a single endpoint for all Domain Queries, and hides any implementation details. Knowledge about where and how to fetch and compute the relevant data, is encapsulated in the server.

Example

The client sends the following Free Query:

SELECT *
FROM Cars AS c
INNER JOIN LeasingContracts AS lc ON c.CarId = lc.CarId
WHERE c.RegistrationYear = 2014;

It is unclear from the query alone what the purpose is. Presumably not all of the fields from the two tables are used by the client, but we can’t change our schema without risking to break things.

Some investigation learns that the client wants to know which cars are up for replacement, and uses somes fields from LeasingContracts to compute the status. A better design is to close off all access to the database, and provide a Domain Query using natural language. The server fetches and computes the results, and the Response message contains only the data that the client actually needs.

WhichCarsAreUpForReplacement : Query {
  registrationYear: 2014
}

CarsThatAreUpForReplacement : Response {
   cars: [CarId] 
}

Discussion

It may feel strange to use names like WhichCarsAreUpForReplacement instead of programmer-language like CarReplacementQuery. I assert that this is only a matter of familiarity. The benefits are that we stay much closer to the language of the business, and that it puts us in the mindset of making very deliberate expressions of use cases.