If you are impatient you can accept this as a form of event buss and jump to the examples https://github.com/jantra-io/jantra-river-pond/tree/main/src/test/kotlin/no/nav/jantra/river/examples
JANTRA is acknowledging NAV implementation of rapids and rivers pattern as a Rapid provider. On top it provides an opinionated intepretation of the Rapids and Rivers (and Ponds) pattern.
There are 4 types of messages this interpretation defines EVENT, NEED, DATA and FAIL. Originaly @fred george talks about Solution as a responce to a NEED. Practice in NAV shows that the microservices very often are tiny, they may wrap a single integration method and a high level response object such as Solution is not practicle. A solution can represent a DATA it can represent a FAIL or it can represent both , so it is a kind of superposition between the two and Jantra does not find it suitable as a response to a simple NEED. On the other hand a Solution is ok as a high level response to a River (JANTRA consideres everything that happens between two consequent events to be part of the same River) execution. Here is a simple layout of the different messages:
Message Type | Required Keys |
---|---|
Event | event-name |
NEED | event-name need |
DATA | event-name data |
Fail | event-name fail |
All message types share a single key - event-name. The motivation is that every Need, Data or Fail is derived from an originaly occuring event. Each model class (Event,Need,Data,Fail) defines its own specification in the form of message validator. Here is example of a Event specification:
companion object {
val packetValidator = River.PacketValidation {
it.demandKey(Key.EVENT_NAME.str())
it.rejectKey(Key.BEHOV.str())
it.rejectKey(Key.DATA.str())
it.rejectKey(Key.FAIL.str())
it.rejectKey(Key.RIVER_ID.str())
it.interestedIn(Key.RIVER_ORIGIN.str)
it.interestedIn(Key.CLIENT_ID.str)
it.interestedIn(Key.EVENT_TIME)
it.interestedIn(Key.APP_KEY)
}
JANTRA opinion about the RAPID is that it does not hold knowledge about what kind of message it holds. From the RAPID perspective only messages exist, without any constraints. Only after a message is consumed by a River it becomes clear that is an Event, NEED(Behov), Data or FAIL The different messages have different nature. An event makes sense only in relation to the POND. Why ? Because it is static it is a Fact and it is unchangeable. At the same time a NEED makes sense in the context of a River, same goes for the Data. The data is not a single source of thruth as all Data may be a subject to change and it may be invalidated or discarded.One such example is when a River terminates with FAIL. All data messages are then invalid.
In contrast to NAV implementation JANTRA consideres all messages between two events to be part of a River. Each River is assigned a unique identifier and each Event can spawn multiple Rivers ending up with one or more Events.
JANTRA implements the pond as an Event store. In addition it holds a log of the River execution.
Each message has a set of keys.
Message Type | Identity | Description |
---|---|---|
Message(every message) | key | Unique identifier for each message type |
Event | river-id | In the context of Event riverID is assigned when the message is consumed by the river. riverID is never persisted on the rapid together with the Event |
river-origin | Identying the river that has spawned the Event | |
NEED(Behov) | river-id | river-id is aways persistent for a NEED Behov and it is assigned once a NEED is constructed from an Event |
event-id | points to the Event key that has spawned the NEED(Behov) | |
Other types of keys | client-id | It is used to retrieve a result from a River execution from a Client or API. In this implementation once a River is executed the solution is positioned in Redis under the client-id |