-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
896 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.`, | ||
}, | ||
}, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 & 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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
Oops, something went wrong.