-
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
3 changed files
with
331 additions
and
4 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
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,323 @@ | ||
--- | ||
outline: deep | ||
--- | ||
|
||
# Developers Reference | ||
|
||
include::attributes.adoc[] | ||
|
||
## Overview | ||
|
||
The goal of {qr-name} is to provide developers with tools that are easy to use | ||
and understand. We believe that a procedural and imperative style of writing | ||
programs, or thinking about tasks in programs, is broadly understood and | ||
valuable model. With {qr-name} we try to support this, rather than introducing | ||
any new concepts for _streams_, _futures_ or _promises_. | ||
|
||
:DISTR: https://en.wikipedia.org/wiki/Distributed_computing | ||
|
||
With {qr-name} developers should feel enabled to write code in a _normal_ way. | ||
However, it is our mission to raise awareness of things that are hard to | ||
consider, when building {DISTR}[distributed systems]. The tools try to convey | ||
these considerations, by making them transparent and part of the API. | ||
|
||
In the next couple of sections we'll look closer at the `QueryBuilder` and the | ||
`ResponseBuilder` types. We'll discuss how to use them in detail, and try to | ||
explain the concepts behind them, and the intention of their implementation. | ||
|
||
## `QueryBuilder` | ||
|
||
The `QueryBuilder` class is a central point of entry, and provides a fluent | ||
builder-API, for publishing queries. It's provided as a bean, by enabling | ||
{qr-name}, using the `@EnableQueryResponse` annotation. It may be injected | ||
as a dependency to provide access from methods in any Spring component. | ||
|
||
We recommend injecting it via the component constructor, and keeping it as a | ||
private field. The `findAuthors()` method below, shows how to access the | ||
`queryBuilder` field in order to publish a query. | ||
|
||
[source,java] | ||
---- | ||
include::{examples-src}/querying/src/main/java/examples/Authors.java[tags=class] | ||
---- | ||
|
||
In the example above, the published query is defined by the string **term** | ||
`"authors"`. This is how the most basic contract of {qr-pfx} is defined. Any | ||
string or text term may be published as a query. | ||
|
||
The second argument is the expected type of any received response elements. It | ||
is not published with the query, but rather used to coerce or interpret any | ||
received responses. This means that regardless of the payload of any response, | ||
in this case {qr-name} will attempt to read the response elements as the | ||
declared type `String.class`. | ||
|
||
Queries are built and published using the `queryFor(..)` _initial_ method. | ||
Any following call to one of the _terminal_ methods `orEmpty()`, | ||
`orDefaults(..)` and `orThrows(..)` will build and execute the query, and block | ||
on the calling thread. | ||
|
||
Since the call above to `orEmpty()` blocks the thread, users have to specify | ||
one or more query _conditionals_. In the example above, the call to | ||
`waitingFor(..)` defines that the call will block for around 800 milliseconds. | ||
|
||
Constructing queries with the `QueryBuilder` revolves around creating a | ||
composition of _initial_, _conditional_, maybe an optional _informal_ and | ||
exactly one _terminal_ method call. In the table below is a short review of the | ||
different builder methods and their types. | ||
|
||
[cols="1,1,3"] | ||
.`QueryBuilder` fluid API method types | ||
|=== | ||
| Method | Type | Description | ||
|
||
| `queryFor(..)` | _initial_ | Creates a new builder for a query | ||
| `waitingFor(..)` | _conditional_ | Specifies the waiting/blocking condition | ||
| `takingAtMost(..)` | _conditional_ | Sets a limit condition, a maximum | ||
| `takingAtLeast(..)` | _conditional_ | Sets a limit condition, a minimum | ||
| `orEmpty()` | _terminal_ | Terminates with empty, after conditionals are evaluated | ||
| `orDefaults(..)` | _terminal_ | Terminates with some defaults, after conditionals are evaluated | ||
| `orThrow(..)` | _terminal_ | Terminates by throwing, after conditionals are evaluated | ||
| `onError(..)` | _informal_ | Allows for explicit logging etc. | ||
|=== | ||
|
||
Let's take a closer look at each of the builder method types. | ||
|
||
### _Initial_ methods | ||
|
||
At the moment there's only one _initial_ method and it's declared as: | ||
|
||
```java | ||
public <T> ChainingQueryBuilder<T> queryFor(String term, Class<T> type) | ||
``` | ||
|
||
So we can query for any `String` **term** and given the expected mapped or | ||
coerced **type** as a `Class<T>`. The returned `ChainingQueryBuilder<T>` | ||
provides the capabilities of the fluid API. | ||
|
||
### _Conditional_ methods | ||
|
||
All _conditional_ properties can be composed together by the `QueryResponse` | ||
builder API, to define whether a query is successful or not. If an executing | ||
query is completed in a *successful* way, fulfilling the _conditionals_, it will | ||
return and not consume any more responses. | ||
|
||
* `waitingFor(..)` - defines a timeout _conditional_. The built query will | ||
evaluate as *successful* if _any_ responses were consumed after the | ||
(approximate) given time limit has elapsed. There are a few different methods | ||
declared, to specify the timeout: | ||
|
||
** `waitingFor(long millis)` | ||
** `waitingFor(long amount, TemporalUnit timeUnit)` | ||
** `waitingFor(Duration duration)` | ||
|
||
* `takingAtMost(int atMost)` - defines a limiting _conditional_ on the | ||
aggregated number of received elements. The built query evaluates to | ||
*successful*, and returns, when the given amount is reached. | ||
|
||
* `takingAtLeast(int atLeast)` - defines a minimum _conditional_ on the number | ||
of received element. The built query evaluates to *successful*, only if at | ||
least the given number of elements can be consumed. | ||
|
||
### _Terminal_ methods | ||
|
||
Only one _terminal_ method can be invoked on the builder, per query. It will | ||
ensure that the query is built and executed. All _terminal_ methods are | ||
declared to return `Collection<T>` where the type parameter `<T>` is given | ||
in the _initial_ method `type` parameter. | ||
|
||
* `orEmpty()` - defines the query to return an empty `Collection` in case the | ||
_conditionals_ do not evaluate to *successful*. | ||
|
||
* `orDefaults(..)` - defines the query to return with some provided _defaults_ | ||
in case the _conditionals_ do not evaluate to *successful*. There are a couple | ||
different methods declared for defaults: | ||
|
||
** `orDefaults(Collection<T> defaults)` - set at _build-time_. | ||
|
||
** `orDefaults(Supplier<Collection<T>> defaults)` - supplied at _run-time_. | ||
|
||
* `orThrow(..)` - defines the query to throw an exception in case the | ||
_conditionals_ do not evaluate to *successful*. | ||
|
||
### _Informal_ methods | ||
|
||
Currently there's only one _informal_ builder method, allowing for extended | ||
logging or information capture, in case the query fails or an exception is | ||
thrown. | ||
|
||
```java | ||
public ChainingQueryBuilder<T> onError(Consumer<Throwable> handler) | ||
``` | ||
|
||
TIP: Try to think more about how the `QueryBuilder` API covers the exceptional | ||
query-cases, as part of the composition of _conditionals_. If clients try | ||
to use _terminals_ that provide sensible defaults, it may not be necessary | ||
to build other types of complex recovery or retries. | ||
|
||
### `QueryBuilder` examples | ||
|
||
Below are some examples of how the different `QueryBuilder` API methods can be | ||
combined. | ||
|
||
Using `takingAtMost(..)`, combined with `waitingFor(..)`, system resources may | ||
be preserved and the client can be protected from consuming too much data. | ||
|
||
```java | ||
return queryBuilder.queryFor("authors", String.class) | ||
.takingAtMost(10) | ||
.waitingFor(800) | ||
.orDefaults(Authors.defaults()); | ||
``` | ||
|
||
It is possible to express constraints at the integration point, also when using | ||
{qr-name}, throwing on an unfulfilled query, as an option to more lenient | ||
handling with defaults. | ||
|
||
```java | ||
return queryBuilder.queryFor("offers/rental", Offer.class) | ||
.takingAtLeast(10) | ||
.takingAtMost(20) | ||
.waitingFor(2, ChronoUnit.SECONDS) | ||
.orThrow(TooFewOffersConstraintException::new); | ||
``` | ||
|
||
The _informal_ builder feature, allows for transparency into queries that may | ||
have to be observed. | ||
|
||
```java | ||
return queryBuilder.queryFor("offers/rental", NewOffer.class) | ||
.takingAtLeast(3) | ||
.waitingFor(400) | ||
.onError(error -> LOG.error("Failure!", error)) | ||
.orThrow(TooFewOffersConstraintException::new); | ||
``` | ||
|
||
## `ResponseBuilder` | ||
|
||
Another entry-point into {qr-name} is the `ResponseBuilder`. It provides a | ||
fluid builder-API that allows users to create responding services or components. | ||
|
||
It is also provided as a bean, when using the `@EnableQueryResponse` annotation | ||
in a Spring application. It can easily be injected as a dependency to provide | ||
access from methods in Spring components. | ||
|
||
The `respondWithAuthors()` method below, shows how the injected builder is used | ||
to create a responding service. It is invoked by the Spring application context, | ||
on the `ApplicationReadyEvent` event. | ||
|
||
[source,java] | ||
---- | ||
include::{examples-src}/responding/src/main/java/examples/OnlyThreeAuthors.java[tags=class] | ||
---- | ||
|
||
In the example above the responding service is defined by calling the builder | ||
method `respondTo(..)` with the query **term** parameter `"authors"`. It will | ||
be bound to publish the given 3 authors as `String.class` entries, whenever it | ||
consumes a query for the matching string **term** `"authors"`. | ||
|
||
This is the most basic premiss of {qr-pfx}, that any string or text term may be | ||
interpreted as a query - it is however up to the response publisher to | ||
determine what the query means. | ||
|
||
TIP: We've tried to provide information around the {qr-pfx} _protocol_ and | ||
philosophy in the later chapter on <<The Query/Response Protocol>>. Go | ||
there to find out more. | ||
The second parameter is the the type of each element, that will be published in | ||
the response. It is given both as a type hint for the compiler, as well as a | ||
parameter to the data mapper. Here it's trivial, the three authors are given as | ||
`String.class` entries. | ||
|
||
NOTE: The data mapper mentioned above, is in fact the | ||
`com.fasterxml.jackson.databind.ObjectMapper` and {qr-name} currently uses | ||
JSON as the transport format. This means that type hints, JSON mapping | ||
configuration annotations or custom mappings will apply. However as data | ||
mapping on the consumer side is done by coercion, the published format | ||
must conform to some agreed upon standard, shape or protocol. | ||
|
||
Response publishers are built using the `respondTo(..)` _initial_ method. Any | ||
following call to one of the _terminal_ methods `from(..)` or `suppliedBy(..)` | ||
will create and register it, as its own consumer in another thread. The | ||
builder call returns immediately. | ||
|
||
The `ResponseBuilder` comes with some methods to allow for _partitioning_ or | ||
_batching_, which can be used to control the transfer of data to some degree. | ||
|
||
The table below shows a summary of the builder methods and types. | ||
|
||
[cols="1,1,3"] | ||
.`ResponseBuilder` fluid API method types | ||
|=== | ||
| Method | Type | Description | ||
|
||
| `respondTo(..)` | _initial_ | Creates a new builder for a query | ||
| `withAll()` | _batching_ | Specifies NO batches | ||
| `withBatchesOf(..)` | _batching_ | Sets the batch size of responses | ||
| `from(..)` | _terminal_ | Terminates with some given response data | ||
| `suppliedBy(..)` | _terminal_ | Terminates with some supplied response data | ||
|=== | ||
|
||
Let's take a closer look at each of the builder method types. | ||
### _Initial_ methods | ||
At the moment there's only one _initial_ method for building responses. It is | ||
declared as: | ||
|
||
```java | ||
public <T> ChainingResponseBuilder<T> respondTo(String term, Class<T> type) | ||
``` | ||
|
||
So we can create a response for any `String` **term** and declare that we intend | ||
to publish elements of some **type** given as a `Class<T>`. The returned | ||
`ChainingResponseBuilder<T>` provides the capabilities of the fluid API. | ||
|
||
### _Batching_ methods | ||
|
||
Control over how response elements are published can be made by using the | ||
_batching_ methods that the builder provides. | ||
|
||
* `withAll()` - defines that **no** batching should be used, and will publish | ||
all given elements, or try to drain a supplied `Iterator` all at once. | ||
|
||
* `withBatchesOf(int size)` - defines a batch size, which the response publisher | ||
will use, to create a series of response messages, with up-to the given `size` | ||
of elements. | ||
|
||
### _Terminal_ methods | ||
|
||
Only one _terminal_ method can be called on the builder, per response. It will | ||
ensure that a responder is created and added as a query-consumer, a subscriber | ||
to the query **term** as a topic. It is not attached to the calling thread, so | ||
the builder call always returns after the _terminal_ call. | ||
|
||
* `from(..)` - declares the source for the provided response data elements. It | ||
is declared in a few different ways, for alternative use: | ||
|
||
** `from(T... elements)` - vararg elements | ||
** `from(Collection<T> elements)` - provided collection at _build-time_ | ||
** `from(Supplier<Iterator<T>> elements)` - supplied iterator at _build-time_ | ||
|
||
* `suppliedBy(Supplier<Collection<T>> elements)` - declares that response data | ||
is supplied at _run-time_. | ||
|
||
### `ResponseBuilder` examples | ||
|
||
Batch responses provide developers with more options to tune and throttle a | ||
system using {qr-pfx} across many services. It may tune and change the profile | ||
of resource use, in a network. | ||
|
||
```java | ||
responseBuilder.respondTo("offers/monday", Offer.class) | ||
.withBatchesOf(20) | ||
.from(offers.findAllOffersByDayOfWeek(Calendar.MONDAY)); | ||
``` | ||
|
||
Dynamic responses are easy to build, with an API that suits modern Java, using | ||
lazy calls to suppliers of data. | ||
|
||
```java | ||
responseBuilder.respondTo("users/current", Token.class) | ||
.withBatchesOf(128) | ||
.suppliedBy(userTokenService::findAllCurrentUserTokens); | ||
``` |
2 changes: 1 addition & 1 deletion
2
...rence/the-query-response-specification.md → .../reference/the-query-response-protocol.md
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