Skip to content

Unified Domain

The unified domain concept of QLive sits somewhere between the technical and purely practical, organizational concerns, and design philosophy.

QLive takes some inspiration from Domain-Driven Design, especially the benefits of having a ubiquitous language in your development team(s). We did not choose GraphQL because we are so fond of GraphQL, but because the GraphQL schema can express the common type language across the client/server divide and because it allows for other technical features.

Where we differ is in the focus on object-oriented programming: It’s not that we don’t like it or that we want to prevent you from having a true, non-anemic OOP domain, we are just saying: If you don’t want to, you don’t need to.

We are offering powerful declarative features that do the heavy lifting for you. If you want to consider our POJOs only a transport layer, feel free to do so.

The GraphQL schema is defined on the server-side by the generated POJO types from the database, the handwritten POJOs, and the GraphQL methods in the logic beans.

Generated by schema introspection, the schema.graphql file is the central artifact of the unified domain. It is the first human readable artifact of the complete domain and it is the source of all client side type generation.

TypeScript type definitions are generated for the types in the schema and for GraphQL query definitions, so that their return types are fully defined in TypeScript including all the detail coming from the slice selection of types.

The types exist at different positions within the fullstack, each with a slightly different representation.

Server TypeScript Database
Domain Types POJOs Immutable Objects* Table
FilterDSL jOOQ-Condition/Java-Filter FilterDSL / TypeScript filter SQL condition
* provided by useSyncExternalStore and snapshots

The FilterDSL concept bridges all fullstack levels in the same way. It is a unified condition language that exists in the most appropriate form for the current technology.

If we’re doing queries, the conditions are translated into jOOQ conditions before executing the SQL query. They have SQL query semantics.

If we’re filtering pubsub messages on the same server, the conditions are translated into a Java predicate tree that can express conditions on Java object graphs. Here, the conditions inherit the memory access semantics from the use case.

We can also just use in-memory data. Every demo in this documentation is built on a extended state snapshot of the actual view. Then we set up astro islands where we provide the snapshot data.

This uses pre-existing functionality (See useLocalDocument) in a new way useful to demos off a static file host like this or tests or storybook.

Here, inside an MDX document, we can then just invoke the QLive demo by its view

unified-domain.mdx
<QLiveDemo view="grid/Basic"/>

to create a live demo based on the extended data, but refiltered with the TypeScript FilterDSL implementation, so that the components inside, for example, Basic.tsx don’t even see any difference.

The update function that normally requests an update from the server now just creates a new slice of the extended data.

One condition graph, written once, therefore does not mean only one thing. There are differences in the translation of these conditions, of course. After all, they are targeting three very different platforms. The goal can’t be perfectly identical behavior. We want the best way to do the job on all platforms, not the lowest common denominator.

What is a reference to an in-memory object graph in one world means an EXIST query in another.

See Filter DSL for more details.