Unified Domain
The unified domain concept of QLive sits somewhere between the technical and purely practical, organizational concerns, and design philosophy.
Domain-Driven Design
Section titled “Domain-Driven Design”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.
Schema Central
Section titled “Schema Central”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 |
Unified Filter
Section titled “Unified Filter”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
<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.
import { DataGrid, useInjection } from "@qlivedev/qlive-ts";import { Q_FooList, Q_FooListResult } from "./Q_FooList";
/** * The least a grid takes: a query document and the fields to show. * * Everything else is derived from the schema. The headings are the i18n() of `Foo.name` and so on, each value is * formatted as its type, a header click sorts by the column, the filter row offers what fits the type -- text * contains, a yes/no select, a number, a date range -- and the pager pages. The owner is a relation and shows its * name field, the login. * * None of it is the grid's own state. Sort, filter and page are the document's config, and the grid changes them * through update(), so anything else in the view holding the same document sees the same rows. */export default function Basic(){ const foos: Q_FooListResult = useInjection(Q_FooList, {config: {pageSize: 5}});
return ( <div className="grid-example"> <h1>A plain grid</h1> <DataGrid doc={ foos } columns={ ["name", "num", "flag", "owner", "created"] }/> </div> );}One condition, three worlds
Section titled “One condition, three worlds”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.