Skip to content

Commit

Permalink
Initial vitepress documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
olle committed Jul 8, 2024
1 parent 427e823 commit 6d98ca3
Show file tree
Hide file tree
Showing 8 changed files with 896 additions and 0 deletions.
46 changes: 46 additions & 0 deletions xdocs/.vitepress/config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { defineConfig } from "vitepress";

export default defineConfig({
title: "Query/Response",
description: "A VitePress Site",
lastUpdated: true,
themeConfig: {
nav: [{ text: "Home", link: "/" }],
sidebar: [
{
text: "Guide",
items: [
{
text: "What is Query/Response",
link: "/guide/what-is-query-response",
},
{ text: "Getting started", link: "/guide/getting-started" },
{ text: "Example revisited", link: "/guide/the-example-revisited"}
],
},
{
text: "Reference",
items: [
{
text: "Specification",
link: "/reference/the-query-response-specification",
},
{
text: "Maturity Model",
link: "/reference/query-response-maturity-model",
},
],
},
],
socialLinks: [
{
icon: "github",
link: "https://github.com/olle/query-response-spring-amqp",
},
],
footer: {
message: "Published under the Apache-2.0 license",
copyright: `Copyright © 2019-${new Date().getFullYear()} Olle Törnström and all other contributors.`,
},
},
});
25 changes: 25 additions & 0 deletions xdocs/attributes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// code-src: ../java/com/studiomediatech/queryresponse,
// test-src: ../../test/java/com/studiomediatech/queryresponse,
// examples-src: ../../../examples,

// java-required-version: 11,
// java-name: Java,
// java-name-and-version: {java-name} {java-required-version},
// java-name-and-latest-version: {java-name} {java-version},

export const springname = "Spring";
export const springamqpname = "Spring AMQP";
export const springbootversion = "2.x";
export const springbootverifiedversion = "3.0.3";
export const springbootname = "Spring Boot";
export const springbootnameandversion = `${springbootname} ${springbootversion}`;
export const springbootnameandverifiedversion = `${springbootname} ${springbootverifiedversion}`;

export const qrcurrentversion = "0.0.0-SNAPSHOT";
export const qrpfx = "Query/Response";
export const qrname = `${qrpfx} for ${springamqpname}`;
export const qruiname = `${qrpfx} Monitoring UI`;
export const qrnameandversion = `${qrname} ${qrcurrentversion}`;

// qr-gh-link: https://github.com/olle/query-response-spring-amqp,
// qr-ui-link: https://github.com/olle/query-response-spring-amqraw/main/ui/query-response-ui.jar,
160 changes: 160 additions & 0 deletions xdocs/guide/getting-started.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
<script setup>
import * as attr from "../attributes.js"
</script>

# Getting Started

{{attr.qrname}} makes it really easy to extend {spring-boot-name} stand-alone,
production-grade applications, that are using {spring-amqp-name}. We have taken
a working pattern for building highly decoupled evolving service architectures,
and wrapped it in a developer friendly library.

## System Requirements

{qr-name-and-version} requires at least **{spring-boot-name-and-version}** and
**{java-name-and-version}**, and should work for later releases too. We are
building and running it successfully with **{java-name-and-latest-version}** and
the **{spring-boot-name-and-verified-version}** version.

## Installation &amp; Configuration

:Maven: https://maven.apache.org
:Gradle: https://gradle.org
:Quickstart: https://github.com/olle/query-response-spring-amqp#quickstart

It is distributed as a {Maven}[Maven] dependency, and is known to work well with
Maven 3.3+. Using the dependency with {Gradle}[Gradle] should work too. Please
see the {Quickstart}[Quickstart] information, available on the project
{qr-gh-link}[Github page], for information on how to get the Maven dependency.

Enabling {qr-name} is done by loading the `QueryResponseConfiguration`
class. The most simple way to do this, is by annotating your {spring-boot-name}
application with the `@EnableQueryResponse` annotation.

## [source,java]

<<<@../../examples/myapp/src/main/java/app/MyApp.java{java}

## include::{examples-src}/myapp/src/main/java/app/MyApp.java[tags=install]

NOTE: This annotation will do nothing more but to import the
`QueryResponseConfiguration` class.

That's it! There is no more infrastructure code, wiring or setup that needs to
be done. **It's just that easy.**

### Connecting to an AMQP broker

Before you can run your application you need to make sure there is an AMQP
broker available. By default {spring-amqp-name} tries to connect to a
https://www.rabbitmq.com[RabbitMQ], running locally on port `5672`.

Start an and run RabbitMQ using `docker`:

....
$ docker run -p 5672:5672 -p 15672:15672 rabbitmq:3-management
....

NOTE: The `3-management` tag will enable the RabbitMQ Management UI. When the
broker is running, it can be accessed at http://localhost:15672 with
username and password `guest`.

Now running your application, will enable {qr-name}, connect to the broker and
create all the resources necessary on the broker.

....
$ mvn spring-boot:run
....

Now is a good time to use the RabbitMQ Management UI, available at
http://localhost:15672, to inspect the exchange, queues and bindings created
by {qr-name} by default.

## Queries

Publishing **queries** is a way for your application to ask for information that
it may need in order to accomplish tasks. Queries express a _need_, and are not
addressed to any specific service or component.

{qr-name} makes it really really easy, to create and publish a query using
the `QueryBuilder`.

## [source,java]

## include::{examples-src}/myapp/src/main/java/app/Queries.java[tags=query]

<1> Initiates a query for the term `marco`, with any results being consumed as,
or _mapped_ to, the type `String.class`. Returned results are always
gathered in a collection. Either **none, one or many** elements may be
returned.

<2> Queries require a timeout, here we set it to `1000L` milliseconds. This
means that this specific query will **always** block for 1 second.

<3> The query may not receive any responses, so it _always_ needs to specify
how that case should be handled. Default here is an empty collection, of
the declared return type `String.class`.

====
Hopefully this shows, how concise and powerful the `QueryBuilder` is, dealing
with results mapping, fault tolerance and default values in just a couple of
lines of code.
====

If you run the application now, it will publish a **query** to the message
broker, which we can see in the logs.

....
$ mvn spring-boot:run
...
c.s.queryresponse.RabbitFacade : |<-- Published query: marco - (Body:'{}' MessageProperties [headers={x-qr-published=1589642002076}, replyTo=94f0fff4-c4f3-4491-831d-00809edb6f95, contentType=application/json, contentLength=2, deliveryMode=NON_PERSISTENT, priority=0, deliveryTag=0])
....

At the moment there are no responses to be consumed, so after blocking for 1
second, nothing is printed `STDOUT`.

## Responses

Building services, medium, large or _micro_ (who cares), that publish
**responses** to queries is also really easy with {qr-name}, using the
`ResponseBuilder`.

## [source,java]

## include::{examples-src}/myapp/src/main/java/app/Responses.java[tags=response]

<1> Initializes a response to queries for `marco`, providing the type-hint on
how to map entries in the response. Set to `String.class` here.

<2> The response `withAll()` will publish all elements in one single response.

<3> And finally this response is provided the elements `"polo", "yolo"` as the
actual data to publish. _The builder varags method, used here, is mostly
for trying out {qr-name}, or for static responses._

====
Again, the builder makes it really easy to create a responding service, without
any special setup or complicated configurations.
====

Now if you run the application again, with the response component registered
before the query publisher, it will publish the response.

....
$ mvn spring-boot:run
...
c.s.queryresponse.RabbitFacade : |<-- Published query: marco - (Body:'{}' MessageProperties [headers={x-qr-published=1589642489894}, replyTo=c77a8a1d-c959-4f2a-bd51-85b7e6b5b69b, contentType=application/json, contentLength=2, deliveryMode=NON_PERSISTENT, priority=0, deliveryTag=0])
c.s.queryresponse.Response : |--> Consumed query: marco
c.s.queryresponse.RabbitFacade : |<-- Published response: c77a8a1d-c959-4f2a-bd51-85b7e6b5b69b - (Body:'{"elements":["polo","yolo"]}' MessageProperties [headers={x-qr-published=1589642489941}, contentType=application/json, contentEncoding=UTF-8, contentLength=28, deliveryMode=NON_PERSISTENT, priority=0, deliveryTag=0])
c.s.queryresponse.Query : |--> Received response message: MessageProperties [headers={x-qr-published=1589642489941}, contentType=application/json, contentEncoding=UTF-8, contentLength=0, receivedDeliveryMode=NON_PERSISTENT, priority=0, redelivered=false, receivedExchange=, receivedRoutingKey=c77a8a1d-c959-4f2a-bd51-85b7e6b5b69b, deliveryTag=1, consumerTag=amq.ctag-Q_ghWp4TWU9EYhi_rqErcg, consumerQueue=c77a8a1d-c959-4f2a-bd51-85b7e6b5b69b]
marco? polo
marco? yolo
....

Now you can see a full roundtrip of the **query** being published and consumed,
and the **response** being published and also consumed. And the finished output
is "polo" and "yolo" printed on `STDOUT`.

NOTE: We are using the `@Order` annotation in our example only to ensure that
responses are built and registered before queries, when they are built
in one and the same app.
150 changes: 150 additions & 0 deletions xdocs/guide/the-example-revisited.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
---
outline: deep
---

# The example revisited

Let's examine one of the most powerful aspects of using the Query/Response
pattern. If we think back to our [initial example](./what-is-query-response.md)
we published a query for books in the sci-fi genre.

```
query: books.sci-fi
reply-to: library/books.sci-fi#42
```

We also learned that responses may come from different sources, with different
payloads and we are responsible for dealing with validation and duplicates etc.

The query in this example uses only some minimal semantics to express the
genre of books requested, the term `sci-fi`. This is part of a contract from
our domain, together with rules on how any result payload should be presented.
The list of strings within quotes are not by accident, it is also by design.

The Query/Response pattern does not enforce any structural rules for query,
address or response syntax. This must come from designers and developers. _I
would suggest, using [Domain Driven Design](https://en.wikipedia.org/wiki/Domain-driven_design)
to leverage the power of a ubiquitous language in the queries_.

All this together puts us in a position to allow change and evolution in our
system.

## A better library protocol

We have agreed on supporting _stars_ for book ratings, and different teams
scramble to their stations to extend for the new feature.

We saw earlier that data returned was formed as a list of quoted strings, and
the contract for parsing was: "first quoted string per line is book title".

```
body:
"Neuromancer"
```

That rule and the capability to extend it, made it possible to agree on a new
optional format: "trailing key-values are properties". For example:

```
body:
"Neuromancer" isbn:9780307969958 stars:4
```

This is great. Let's get to work.

## Top-3 books have stars

```
query: books.sci-fi
reply-to: library/books.sci-fi#77
```

At a later time a new query for science fiction books is published. Now, we
still must not assume anything about the service or collaborator publishing
the query. It may be that we have a new service running in our system, not yet
live, or an updated version of the first one - we don't need to know.

```
response: library/books.sci-fi#77
body:
"Neuromancer" stars:3
"Snow Crash" stars:5
"I, Robot" stars:4
```

The first response looks great, it's using the new extended protocol and
provides star-ratings with the top-3 sci-fi book list.

## One of each flavour

Another response is consumed:

```
response: library/books.sci-fi#77
body:
"I, Robot"
"The Gods Themselves"
"Pebble in the Sky"
```

Oh, ok seems that we've received a response with only Asimov books again, and
sadly no stars. Luckily the protocol rules allows us to still use the response
if we choose to.

```
response: library/books.sci-fi#77
body:
"I, Robot" stars:2
"The Gods Themselves"
"Pebble in the Sky" stars:5
```

And what is this now. We've consumed yet another response and it appears to be
the Asimov list again, but this time with star-ratings, but only for a few
titles.

This is quite normal and shows us a really important and valuable aspect of
the Query/Response pattern. If we would pull the curtain back a bit, it could
be reasonable to assume that the publisher of Asimov books now exists in 2
distinct versions. One supports the new updated format, and has a couple of
star-ratings set. The other appears to be the _older_ version.

We have effectively seen how response publishers can evolve, and even exist
side-by-side, if care is taken to design a suitable payload protocol.

_The backward compatibility of the payload format is not at all required in the
Query/Response pattern. Implementations could use version tags or classifiers
to check for compatibility at the consumer side._

::: warning Important!
The key point here is, the consumer is still responsible for asserting the
usefulness and value of the response information. Parsing, validating or
checking for version compatibility is required.
:::

## Out with the old

Let's jump forward and say that at some later time, the query for sci-fi books
is published again.

```
query: books.sci-fi
reply-to: library/books.sci-fi#88
```

And this time, the only consumed response with Asimov books is the following:

```
response: library/books.sci-fi#88
body:
"I, Robot" stars:3
"The Gods Themselves" stars:3
"Pebble in the Sky" stars:5
```

We can almost certainly conclude that the original version of the Asimov
book service has been shut down.

Again we can see how the Query/Response pattern helps in coping with a natural
evolution of the system. Services can be added, removed or upgraded at any
time.
Loading

0 comments on commit 6d98ca3

Please sign in to comment.