public class JmsMatsProcessContext<R,S,Z> extends java.lang.Object implements MatsEndpoint.ProcessContext<R>, JmsMatsStatics
MatsEndpoint.ProcessContext
. Instantiated for each incoming JMS message that is processed,
given to the MatsStage
's process lambda.EXTRA_GRACE_MILLIS, ILLEGAL_CALL_FLOWS, JMS_MSG_PROP_AUDIT, JMS_MSG_PROP_DISPATCH_TYPE, JMS_MSG_PROP_FROM, JMS_MSG_PROP_INITIALIZING_APP, JMS_MSG_PROP_INITIATOR_ID, JMS_MSG_PROP_MATS_MESSAGE_ID, JMS_MSG_PROP_MESSAGE_TYPE, JMS_MSG_PROP_TO, JMS_MSG_PROP_TRACE_ID, LOG_PREFIX, MAX_STACK_HEIGHT, MAX_TOTAL_CALL_NUMBER, MDC_MATS_APP_NAME, MDC_MATS_APP_VERSION, MDC_MATS_CALL_NUMBER, MDC_MATS_IN_MESSAGE_SYSTEM_ID, MDC_MATS_INIT, MDC_MATS_OUT_MATS_MESSAGE_ID, MDC_MATS_STAGE, MDC_MATS_STAGE_ID, MDC_TRACE_ID, NO_INVOCATION_POINT, RANDOM_ALPHABET, THREAD_PREFIX, TOTAL_JMS_MSG_PROPS_SIZE
Modifier and Type | Method and Description |
---|---|
void |
addBytes(java.lang.String key,
byte[] payload)
Attaches a binary payload ("sideload") to the next outgoing message, being it a request or a reply.
|
void |
addString(java.lang.String key,
java.lang.String payload)
Attaches a
String payload ("sideload") to the next outgoing message, being it a request or a
reply. |
void |
doAfterCommit(java.lang.Runnable runnable)
The Runnable will be performed after messaging and external resources (DB) have been committed.
|
<T> java.util.Optional<T> |
getAttribute(java.lang.Class<T> type,
java.lang.String... name)
Provides a way to get hold of (optional) attributes/objects from the Mats implementation, either specific to
the Mats implementation in use, or configured into this instance of the Mats implementation.
|
byte[] |
getBytes(java.lang.String key)
Get binary "sideloads" from the incoming message.
|
java.util.Set<java.lang.String> |
getBytesKeys() |
java.lang.String |
getEndpointId() |
java.lang.String |
getFromAppName() |
java.lang.String |
getFromAppVersion() |
java.lang.String |
getFromStageId() |
java.time.Instant |
getFromTimestamp() |
java.lang.String |
getInitiatingAppName() |
java.lang.String |
getInitiatingAppVersion() |
java.time.Instant |
getInitiatingTimestamp() |
java.lang.String |
getInitiatorId() |
java.lang.String |
getMatsMessageId() |
java.lang.String |
getStageId() |
java.lang.String |
getString(java.lang.String key)
Get
String "sideloads" from the incoming message. |
java.util.Set<java.lang.String> |
getStringKeys() |
java.lang.String |
getSystemMessageId() |
java.lang.String |
getTraceId() |
<T> T |
getTraceProperty(java.lang.String propertyName,
java.lang.Class<T> clazz)
Retrieves the Mats Trace property with the specified name, deserializing the value to the specified class,
using the active MATS serializer.
|
MatsInitiator.MessageReference |
goTo(java.lang.String endpointId,
java.lang.Object gotoDto)
Sends a message which passes the current call stack over to another endpoint, so that when that endpoint
replies, it will return to the endpoint which invoked this endpoint.
|
MatsInitiator.MessageReference |
goTo(java.lang.String endpointId,
java.lang.Object gotoDto,
java.lang.Object initialTargetSto)
Variation of
MatsEndpoint.ProcessContext.goTo(String, Object) method, where the incoming state is sent along. |
void |
initiate(MatsInitiator.InitiateLambda lambda)
Initiates a new message out to an endpoint.
|
boolean |
isInteractive()
This is relevant if stashing or otherwise when a stage is accessing an external system (e.g.
|
boolean |
isNoAudit()
Hint to monitoring/logging/auditing systems that this call flow is not very valuable to fully audit,
typically because it is just a "getter" of information for display to a user, or is health check request to
see if the endpoint is up and answers in a timely manner.
|
boolean |
isNonPersistent()
This is relevant if stashing or otherwise when a stage is accessing an external system (e.g.
|
void |
logMeasurement(java.lang.String metricId,
java.lang.String metricDescription,
java.lang.String baseUnit,
double measure,
java.lang.String... labelKeyValue)
Adds a measurement of a described variable, in a base unit, for this Stage - be sure to understand that
the three String parameters are constants for each measurement. To exemplify, you may measure five
different things in a Stage, i.e.
|
void |
logTimingMeasurement(java.lang.String metricId,
java.lang.String metricDescription,
long nanos,
java.lang.String... labelKeyValue)
Same as
addMeasurement(..) , but
specifically for timings - Read that JavaDoc!
Note: It is illegal to use the same 'metricId' for more than one measurement for a given stage, and this also
goes between timing measurements and measurements . |
MatsInitiator.MessageReference |
next(java.lang.Object nextDto)
Sends a message which passes the control to the next stage of a multi-stage endpoint.
|
void |
nextDirect(java.lang.Object nextDirectDto)
Specialized, less resource demanding, and faster "direct" variant of
MatsEndpoint.ProcessContext.next(Object) which executes the
next stage of a multi-stage endpoint within the same stage processor and transactional demarcation that this
stage is in - that is, there is no actual message sent. |
MatsInitiator.MessageReference |
reply(java.lang.Object replyDto)
Sends a reply to the requesting service.
|
MatsInitiator.MessageReference |
request(java.lang.String endpointId,
java.lang.Object requestDto)
Sends a request message, meaning that the specified endpoint will be invoked, with the reply-to endpointId
set to the next stage in the multi-stage endpoint.
|
void |
setTraceProperty(java.lang.String propertyName,
java.lang.Object propertyValue)
Adds a property that will "stick" with the Mats Trace from this call on out.
|
byte[] |
stash()
Returns a binary representation of the current Mats flow's incoming execution point, which can be
unstashed again at a later time
using the MatsInitiator , thereby providing a simplistic "continuation" feature in Mats. |
java.lang.String |
toString() |
clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
unwrapFully
createFlowId, getInvocationPoint, handleIncomingMessageMatsObject, handleIncomingState, id, id, idThis, ms3, produceAndSendMsgSysMessages, randomString, setConcurrencyWithLog, stageOrInit
public java.lang.String getStageId()
getStageId
in interface MatsEndpoint.DetachedProcessContext
MatsEndpoint.DetachedProcessContext.getEndpointId()
for the first stage in multi-stage-endpoint, and for the sole stage of a
single-stage and terminator endpoint. Should probably never be necessary, but accessible for
introspection.public java.lang.String getFromAppName()
getFromAppName
in interface MatsEndpoint.DetachedProcessContext
AppName
of the MatsFactory from which the currently processing
message came. Thus, if this message is the result of a 'next' call, it will be yourself.public java.lang.String getFromAppVersion()
getFromAppVersion
in interface MatsEndpoint.DetachedProcessContext
AppVersion
of the MatsFactory from which the currently
processing message came. Thus, if this message is the result of a 'next' call, it will be yourself.public java.lang.String getFromStageId()
getFromStageId
in interface MatsEndpoint.DetachedProcessContext
MatsEndpoint.DetachedProcessContext.getInitiatorId()
.public java.time.Instant getFromTimestamp()
getFromTimestamp
in interface MatsEndpoint.DetachedProcessContext
Instant
which this message was created on the sending stage.public java.lang.String getInitiatingAppName()
getInitiatingAppName
in interface MatsEndpoint.DetachedProcessContext
AppName
of the MatsFactory that initiated the Flow which the
currently processing is a part of. Thus, if this endpoint is the initial target of the initiation,
this value is equal to MatsEndpoint.DetachedProcessContext.getFromAppName()
.public java.lang.String getInitiatingAppVersion()
getInitiatingAppVersion
in interface MatsEndpoint.DetachedProcessContext
AppVersion
of the MatsFactory that initiated the Flow which
the currently processing is a part of. Thus, if this endpoint is the initial target of the
initiation, this value is equal to MatsEndpoint.DetachedProcessContext.getFromAppVersion()
.public java.lang.String getInitiatorId()
getInitiatorId
in interface MatsEndpoint.DetachedProcessContext
MatsInitiator.MatsInitiate.from(String)
.public java.time.Instant getInitiatingTimestamp()
getInitiatingTimestamp
in interface MatsEndpoint.DetachedProcessContext
Instant
which this message was initiated, i.e. sent from a MatsInitiator (or within a
Stage).public java.lang.String getMatsMessageId()
getMatsMessageId
in interface MatsEndpoint.DetachedProcessContext
public java.lang.String getSystemMessageId()
getSystemMessageId
in interface MatsEndpoint.DetachedProcessContext
MatsEndpoint.DetachedProcessContext.getMatsMessageId()
. (For a JMS
Implementation, this will be the "JMSMessageID").public boolean isNonPersistent()
MatsEndpoint.DetachedProcessContext
isNonPersistent
in interface MatsEndpoint.DetachedProcessContext
MatsInitiator.MatsInitiate.nonPersistent()
.public boolean isInteractive()
MatsEndpoint.DetachedProcessContext
isInteractive
in interface MatsEndpoint.DetachedProcessContext
MatsInitiator.MatsInitiate.interactive()
.public boolean isNoAudit()
MatsEndpoint.DetachedProcessContext
isNoAudit
in interface MatsEndpoint.DetachedProcessContext
MatsInitiator.MatsInitiate.noAudit()
.public java.util.Set<java.lang.String> getBytesKeys()
getBytesKeys
in interface MatsEndpoint.DetachedProcessContext
bytes
lives.public java.lang.String toString()
toString
in interface MatsEndpoint.DetachedProcessContext
toString
in class java.lang.Object
public java.lang.String getTraceId()
getTraceId
in interface MatsEndpoint.DetachedProcessContext
trace id
for the processed message.MatsInitiator.MatsInitiate.traceId(CharSequence)
public java.lang.String getEndpointId()
getEndpointId
in interface MatsEndpoint.DetachedProcessContext
public byte[] getBytes(java.lang.String key)
MatsEndpoint.DetachedProcessContext
getBytes
in interface MatsEndpoint.DetachedProcessContext
key
- the key for which to retrieve a binary payload from the incoming message.MatsEndpoint.DetachedProcessContext.getBytesKeys()
,
MatsEndpoint.ProcessContext.addBytes(String, byte[])
,
MatsEndpoint.DetachedProcessContext.getString(String)
,
MatsEndpoint.DetachedProcessContext.getTraceProperty(String, Class)
public java.util.Set<java.lang.String> getStringKeys()
getStringKeys
in interface MatsEndpoint.DetachedProcessContext
strings
lives.public java.lang.String getString(java.lang.String key)
MatsEndpoint.DetachedProcessContext
String
"sideloads" from the incoming message.getString
in interface MatsEndpoint.DetachedProcessContext
key
- the key for which to retrieve a String payload from the incoming message.MatsEndpoint.DetachedProcessContext.getStringKeys()
,
MatsEndpoint.ProcessContext.addString(String, String)
,
MatsEndpoint.DetachedProcessContext.getBytes(String)
,
MatsEndpoint.DetachedProcessContext.getTraceProperty(String, Class)
public void addBytes(java.lang.String key, byte[] payload)
MatsEndpoint.ProcessContext
MatsInitiator.MatsInitiate
instance.
The rationale for having this is to not have to encode a largish byte array inside the JSON structure that
carries the Request or Reply DTO - byte arrays represent very badly in JSON.
Note: The byte array is not compressed (as might happen with the DTO), so if the payload is large, you might
want to consider compressing it before attaching it (and will then have to decompress it on the receiving
side).
Note: This will be added to the subsequent request
, reply
or next
message - and then cleared. Thus, if you perform multiple request or
next calls, then each must have their binaries, strings and trace properties set separately. (Any
initiations
are separate from this, neither getting nor consuming binaries,
strings nor trace properties set on the ProcessContext
- they must be set on the
MatsInitiate
instance within the initiate-lambda).addBytes
in interface MatsEndpoint.ProcessContext<R>
key
- the key on which to store the byte array payload. The receiver will have to use this key to get
the payload out again, so either it will be a specific key that the sender and receiver agree
upon, or you could generate a random key, and reference this key as a field in the outgoing DTO.payload
- the payload to store.MatsEndpoint.DetachedProcessContext.getBytes(String)
,
MatsEndpoint.ProcessContext.addString(String, String)
,
MatsEndpoint.DetachedProcessContext.getString(String)
public void addString(java.lang.String key, java.lang.String payload)
MatsEndpoint.ProcessContext
String
payload ("sideload") to the next outgoing message, being it a request or a
reply. Note that for initiations, you have the same method on the MatsInitiator.MatsInitiate
instance.
The rationale for having this is to not have to encode a largish string document inside the JSON structure
that carries the Request or Reply DTO.
Note: The String payload is not compressed (as might happen with the DTO), so if the payload is large, you
might want to consider compressing it before attaching it and instead use the
addBytes(..)
method (and will then have to decompress it on the receiving
side).
Note: This will be added to the subsequent request
, reply
or next
message - and then cleared. Thus, if you perform multiple request or
next calls, then each must have their binaries, strings and trace properties set separately. (Any
initiations
are separate from this, neither getting nor consuming binaries,
strings nor trace properties set on the ProcessContext
- they must be set on the
MatsInitiate
instance within the initiate-lambda).addString
in interface MatsEndpoint.ProcessContext<R>
key
- the key on which to store the String payload. The receiver will have to use this key to get the
payload out again, so either it will be a specific key that the sender and receiver agree upon, or
you could generate a random key, and reference this key as a field in the outgoing DTO.payload
- the payload to store.MatsEndpoint.DetachedProcessContext.getString(String)
,
MatsEndpoint.ProcessContext.addBytes(String, byte[])
,
MatsEndpoint.DetachedProcessContext.getBytes(String)
public void setTraceProperty(java.lang.String propertyName, java.lang.Object propertyValue)
MatsEndpoint.ProcessContext
MatsInitiator.MatsInitiate
instance. The functionality effectively acts like a
ThreadLocal
when compared to normal java method invocations: If the Initiator adds it, all subsequent
stages will see it, on any stack level, including the terminator. If a stage in a service nested some levels
down in the stack adds it, it will be present in all subsequent stages including all the way to the
Terminator. Note that any initiations within a Stage will also inherit trace properties present on the
Stage's incoming message.
Possible use cases: You can for example "sneak along" some property meant for Service X through an invocation
of intermediate Service A (which subsequently calls Service X), where the signature (DTO) of the intermediate
Service A does not provide such functionality. Another usage would be to add some "global context variable",
e.g. "current user", that is available for any down-stream Service that requires it. Both of these scenarios
can obviously lead to pretty hard-to-understand code if used extensively: When employed, you should code
rather defensively, where if this property is not present when a stage needs it, it should throw
MatsEndpoint.MatsRefuseMessageException
and clearly explain that the property needs to be present.
Note: This will be added to the subsequent request
, reply
or next
message - and then cleared. Thus, if you perform multiple request or
next calls, then each must have their binaries, strings and trace properties set separately. (Any
initiations
are separate from this, neither getting nor consuming binaries,
strings nor trace properties set on the ProcessContext
- they must be set on the
MatsInitiate
instance within the initiate-lambda).
Note: incoming trace properties (that was present on the incoming message) will be added to all
outgoing message, including initiations within the stage.setTraceProperty
in interface MatsEndpoint.ProcessContext<R>
propertyName
- the name of the propertypropertyValue
- the value of the property, which will be serialized using the active MATS serializer.MatsEndpoint.DetachedProcessContext.getTraceProperty(String, Class)
,
MatsEndpoint.ProcessContext.addString(String, String)
,
MatsEndpoint.ProcessContext.addBytes(String, byte[])
public void logMeasurement(java.lang.String metricId, java.lang.String metricDescription, java.lang.String baseUnit, double measure, java.lang.String... labelKeyValue)
MatsEndpoint.ProcessContext
timing
measurements
.
Inclusion as metric by plugin 'mats-intercept-micrometer': A new meter will be created (and cached),
of type DistributionSummary
, with the 'name' set to
"mats.exec.ops.measure.{metricId}.{baseUnit}"
("measure"->"time" for timings), and
'description' to description. (Tags/labels already added on the meter by the plugin include 'appName',
'initiatorId', 'initiatingAppName', 'stageId' (for stages), and 'initiatorName' (for inits)). Read about
parameter 'labelKeyValue' below.
Inclusion as log line by plugin 'mats-intercept-logging': A log line will be output by each added
measurement, where the MDC for that log line will have an entry with key
"mats.ops.measure.{metricId}.{baseUnit}"
. Read about parameter 'labelKeyValue' below.
It generally makes most sense if the same metrics are added for each processing of a particular Stage, i.e.
if the "number of items" are 0, then that should also be recorded along with the "total amount for order in
dollar" as 0, not just elided. Otherwise, your metrics will be skewed.
You should use a dot-notation for the metricId if you want to add multiple meters with a
hierarchical/subdivision layout.
The vararg 'labelKeyValue' is an optional element where the String-array consist of one or several alternate
key, value pairs. Do not employ this feature unless you know what the effects are, and you actually need
it! This will be added as labels/tags to the metric, and added to the SLF4J MDC for the measurement log
line with the key being "mats.ops.measure.{metricId}.{labelKey}"
("measure"->"time" for
timings). The keys should be constants as explained for the other parameters, while the value can change, but
only between a given set of values (think enum
) - using e.g. the 'customerId' as value doesn't
make sense and will blow up your metric cardinality. Notice that if you do employ e.g. two labels, each
having one of three values, you'll effectively create 9 different meters, where your measurement will
go to one of them.
NOTICE: If you want to do a timing, then instead use
MatsEndpoint.ProcessContext.logTimingMeasurement(String, String, long, String...)
logMeasurement
in interface MatsEndpoint.ProcessContext<R>
metricId
- constant, short, possibly dot-separated if hierarchical, id for this particular metric, e.g.
"items" or "amount", or "db.query.orders".metricDescription
- constant, textual description for this metric, e.g. "Number of items in customer order", "Total
amount of customer order"baseUnit
- the unit for this measurement, e.g. "quantity" (for a count measure), "dollar" (for an amount), or
"bytes" (for a document size).measure
- value of the measurementlabelKeyValue
- a String-vararg array consisting of alternate key,value pairs which will becomes labels or tags or
entries for the metrics and log lines. Read the JavaDoc above; the keys shall be "static" for a
specific measure, while the values can change between a specific small set values.public void logTimingMeasurement(java.lang.String metricId, java.lang.String metricDescription, long nanos, java.lang.String... labelKeyValue)
MatsEndpoint.ProcessContext
addMeasurement(..)
, but
specifically for timings - Read that JavaDoc!
Note: It is illegal to use the same 'metricId' for more than one measurement for a given stage, and this also
goes between timing measurements and measurements
.
For the metrics-plugin 'mats-intercept-micrometer' plugin, the 'baseUnit' argument is deduced to whatever is
appropriate for the receiving metrics system, e.g. for Prometheus it is "seconds", even though you always
record the measurement in nanoseconds using this method.
For the logging-plugin 'mats-intercept-logging' plugin, the timing in the log line will be in milliseconds
(with fractions), even though you always record the measurement in nanoseconds using this method.logTimingMeasurement
in interface MatsEndpoint.ProcessContext<R>
metricId
- constant, short, possibly dot-separated if hierarchical, id for this particular metric, e.g.
"db.query.orders" or "calcprofit".metricDescription
- constant, textual description for this metric, e.g. "Time taken to execute order query", "Time
taken to calculate profit or loss".nanos
- time taken in nanosecondslabelKeyValue
- a String-vararg array consisting of alternate key,value pairs which will becomes labels or tags or
entries for the metrics and log lines. Read the JavaDoc at
addMeasurement(..)
public byte[] stash()
MatsEndpoint.ProcessContext
unstashed
again at a later time
using the MatsInitiator
, thereby providing a simplistic "continuation" feature in Mats. You will have
to find storage for these bytes yourself - an obvious place is the co-transactional database that the stage
typically has available. This feature gives the ability to "pause" the current Mats flow, and later restore
the execution from where it left off, probably with some new information that have been gathered in the
meantime. This can typically relieve the Mats Stage Processing thread from having to wait for another
service's execution (whose execution must then be handled by some other thread). This could be a longer
running process, or a process whose execution time is variable, maybe residing on a Mats-external service
structure: E.g. some REST service that sometimes lags, or sometimes is down in smaller periods. Or a service
on a different Message Broker. Once this "Mats external" processing has finished, that thread can invoke
unstash(stashBytes,...)
to get the
Mats flow going again. Notice that functionally, the unstash-operation is a kind of initiation, only that
this type of initiation doesn't start a new Mats flow, rather continuing an existing flow.
Notice that this feature should not typically be used to "park" a Mats flow for days. One might have a
situation where a part of an order flow potentially needs manual handling, e.g. validating a person's
identity if this has not been validated before. It might (should!) be tempting to employ the stash function
then: Stash the Mats flow in a database. Make a GUI where the ID-validation can be performed by some
employee. When the ID is either accepted or denied, you unstash the Mats flow with the result, getting a very
nice continuous mats flow for new orders which is identical whether or not ID validation needs to be
performed. However, if this ID-validation process can take days or weeks to execute, it will be a poor
candidate for the stash-feature. The reason is that embedded within the execution context which you get a
binary serialization of, there might be several serialized state representations of the endpoints
laying upstream of this call flow. When you "freeze" these by invoking stash, you have immediately made a
potential future deserialization-crash if you change the code of those upstream endpoints (which quite
probably resides in different code bases than the one employing the stash feature), specifically changes of
the state classes they employ: When you deploy these code changes while having multiple flows frozen in
stashes, you will have a problem when they are later unstashed and the Mats flow returns to those endpoints
whose state classes won't deserialize back anymore. It is worth noting that you always have these problems
when doing deploys where the state classes of Mats endpoints change - it is just that usually, there won't be
any, and at least not many, such flows in execution at the precise deploy moment (and also, that changing the
state classes are in practice really not that frequent). However, by stashing over days, instead of a normal
Mats flow that take seconds, you massively increase the time window in which such deserialization problems
can occur. You at least have to consider this if employing the stash-functionality.
Note about data and metadata which should be stored along with the stash-bytes: You only get a binary
serialized incoming execution context in return from this method (which includes the incoming message,
incoming state, the execution stack and trace
properties
, but not "sideloaded" bytes
and
strings
). The returned byte array are utterly opaque seen from the
Mats API side (however, depending on the serialization mechanism employed in the Mats implementation, you
might be able to peek into them anyway - but this should at most be used for debugging/monitoring
introspection). Therefore, any information from the incoming message, or from your state object, or anything
else from the MatsEndpoint.DetachedProcessContext
which is needed to actually execute the job that should be
performed outside of the Mats flow, must be picked out manually before exiting the process lambda.
This also goes for "sideloaded" objects (bytes
and
strings
) - which will not be available inside the unstashed process
lambda (they are not a part of the stash-bytes). Also, you should for debugging/monitoring purposes also
store at least the Mats flow's TraceId
and a timestamp along with the
stash and data. You should probably also have some kind of monitoring / health checks for stashes that have
become stale - i.e. stashes that have not been unstashed for a considerable time, and whose Mats flow have
thus stopped up, and where the downstream endpoints/stages therefore will not get invoked.
Notes:
stash()
will not affect the stage processing in any way other than producing a
serialized representation of the current incoming execution point. You can still send out messages. You could
even reply, but then, what would be the point of stashing?stash()
don't affect the incoming execution point.lastStage
, as you cannot return from such a stage
without actually sending a reply (return null
replies with null
). Instead employ a
normal stage
, using MatsEndpoint.ProcessContext.reply(Object)
to
return a reply if needed.stash
in interface MatsEndpoint.ProcessContext<R>
public <T> T getTraceProperty(java.lang.String propertyName, java.lang.Class<T> clazz)
MatsEndpoint.DetachedProcessContext
MatsEndpoint.ProcessContext.setTraceProperty(String, Object)
.getTraceProperty
in interface MatsEndpoint.DetachedProcessContext
propertyName
- the name of the Mats Trace property to retrieve.clazz
- the class to which the value should be deserialized.MatsEndpoint.ProcessContext.setTraceProperty(String, Object)
public MatsInitiator.MessageReference reply(java.lang.Object replyDto)
MatsEndpoint.ProcessContext
lastStage
lambda, this is the method that actually
gets invoked.
This will be ignored if there is no endpointId on the stack, i.e. if this endpoint it is semantically a
terminator (the replyTo
of an initiation's request), or if it is the last stage of an endpoint
that was invoked directly (using MatsInitiate.send(msg)
).
It is possible to do "early return" in a multi-stage endpoint by invoking this method in a stage that is not
the last.
Note: Legal outgoing flows: Either one or several request
messages; OR a
single reply
, next
, nextDirect
, or
goTo
message. The reason that multiple requests are allowed is that this could
be used in a scatter-gather scenario - where the replies come in to the next stage of the same
endpoint. However, multiple replies to the invoking endpoint makes very little sense, which is why
only one reply is allowed, and it cannot be combined with request or next, nor goto, as then the next stage
could also perform a reply.
Note: The current state and DTO is serialized when invoking this method. Any changes to the state
object or the DTO performed afterwards won't be present on the reply-receiving stage.reply
in interface MatsEndpoint.ProcessContext<R>
replyDto
- the reply DTO to return to the invoker.public MatsInitiator.MessageReference request(java.lang.String endpointId, java.lang.Object requestDto)
MatsEndpoint.ProcessContext
request
messages; OR a
single reply
, next
, nextDirect
, or
goTo
message. The reason that multiple requests are allowed is that this could
be used in a scatter-gather scenario - where the replies come in to the next stage of the same
endpoint. However, multiple replies to the invoking endpoint makes very little sense, which is why
only one reply is allowed, and it cannot be combined with request or next, nor goto, as then the next stage
could also perform a reply.
Note: The current state and DTO is serialized when invoking this method. This means that in case of
multiple requests, you may change the state in between each request, and the next stage will get different
"incoming states" for each of the replies, which may be of use in a scatter-gather scenario.request
in interface MatsEndpoint.ProcessContext<R>
endpointId
- which endpoint to invokerequestDto
- the message that should be sent to the specified endpoint.public MatsInitiator.MessageReference next(java.lang.Object nextDto)
MatsEndpoint.ProcessContext
if (something missing) { request another endpoint with reply coming to next stage. } else { pass to next stage }This can be of utility if an endpoint in some situations requires more information from a collaborating endpoint, while in other situations it does not require that information. Note: You should rather use
MatsEndpoint.ProcessContext.nextDirect(Object)
if your transactional demarcation needs allows
it!
Note: Legal outgoing flows: Either one or several request
messages; OR a
single reply
, next
, nextDirect
, or
goTo
message. The reason that multiple requests are allowed is that this could
be used in a scatter-gather scenario - where the replies come in to the next stage of the same
endpoint. However, multiple replies to the invoking endpoint makes very little sense, which is why
only one reply is allowed, and it cannot be combined with request or next, nor goto, as then the next stage
could also perform a reply.
Note: The current state and DTO is serialized when invoking this method. Any changes to the state
object or the DTO performed afterwards won't be present in the subsequent stage.next
in interface MatsEndpoint.ProcessContext<R>
nextDto
- the object for the next stage's incoming DTO, which must match what the next stage expects. When
using this method to skip a request, it probably often makes sense to set it to null
,
which the next stage then must handle correctly.MatsEndpoint.ProcessContext.nextDirect(Object)
public void nextDirect(java.lang.Object nextDirectDto)
MatsEndpoint.ProcessContext
MatsEndpoint.ProcessContext.next(Object)
which executes the
next stage of a multi-stage endpoint within the same stage processor and transactional demarcation that this
stage is in - that is, there is no actual message sent. This ensures that you neither incur any transactional
cost nor the overhead of serialization and sending a message on the message queue with subsequent receiving
and deserialization.
The functionality is meant for doing conditional calls, e.g.:
if (something missing) { request another endpoint with reply coming to next stage. } else { directly execute the next stage }This can be of utility if an endpoint in some situations requires more information from a collaborating endpoint, while in other situations it does not require that information. A scenario nextDirect is particularly good for is lazy cache population where the caching of the information only incurs cost if the information is missing, since if the information is present in the cache, you can do a close to zero cost nextDirect invocation. Had you been using
ordinary next
, you would incur the cost of
sending and receiving a message even though the information is present in the cache.
Implementation details which are unspecified and whose effects or non-effects shall not be relied on:
stageCompleted(..)
vs. stageCompletedNextDirect(..)
request
messages; OR a
single reply
, next
, nextDirect
, or
goTo
message. The reason that multiple requests are allowed is that this could
be used in a scatter-gather scenario - where the replies come in to the next stage of the same
endpoint. However, multiple replies to the invoking endpoint makes very little sense, which is why
only one reply is allowed, and it cannot be combined with request or next, nor goto, as then the next stage
could also perform a reply.nextDirect
in interface MatsEndpoint.ProcessContext<R>
public MatsInitiator.MessageReference goTo(java.lang.String endpointId, java.lang.Object gotoDto)
MatsEndpoint.ProcessContext
initialState
-feature, or by modifying the DTO before goTo, or by
sideloads
).
Another use is tail calls, whereby one endpoint A does some preprocessing, and then invokes another
endpoint B, but where endpoint A really just want to directly reply with the reply from endpoint B. The extra
reply stage of endpoint A is thus totally useless and just incurs additional message passing and processing.
You can instead just goTo endpoint B, which achieves just this outcome: When endpoint B now replies, it
will reply to the caller of endpoint A.
Note: Legal outgoing flows: Either one or several request
messages; OR a
single reply
, next
, nextDirect
, or
goTo
message. The reason that multiple requests are allowed is that this could
be used in a scatter-gather scenario - where the replies come in to the next stage of the same
endpoint. However, multiple replies to the invoking endpoint makes very little sense, which is why
only one reply is allowed, and it cannot be combined with request or next, nor goto, as then the next stage
could also perform a reply.
Note: The current state and DTO is serialized when invoking this method. Any changes to the state
object or the DTO performed afterwards won't be present for the targeted endpoint.goTo
in interface MatsEndpoint.ProcessContext<R>
endpointId
- which endpoint to go to.gotoDto
- the message that should be sent to the specified endpoint. Will in a dispatcher scenario often be
the incoming DTO for the current endpoint, while in a tail call situation be the requestDto for
the target endpoint.public MatsInitiator.MessageReference goTo(java.lang.String endpointId, java.lang.Object gotoDto, java.lang.Object initialTargetSto)
MatsEndpoint.ProcessContext
MatsEndpoint.ProcessContext.goTo(String, Object)
method, where the incoming state is sent along.
This only makes sense if the same code base "owns" both the current endpoint, and the endpoint to which
this message is sent.goTo
in interface MatsEndpoint.ProcessContext<R>
endpointId
- which endpoint to go to.gotoDto
- the message that should be sent to the specified endpoint. Will in a dispatcher scenario often be
the incoming DTO for the current endpoint, while in a tail call situation be the requestDto for
the target endpoint.initialTargetSto
- the object which the target endpoint will get as its initial stage STO (State Transfer Object).public void initiate(MatsInitiator.InitiateLambda lambda)
MatsEndpoint.ProcessContext
the same method
on a MatsInitiator
gotten via
MatsFactory.getOrCreateInitiator(String)
, only that this way works within the transactional context
of the MatsStage
which this method is invoked within. Also, the traceId and from-endpointId is
predefined, but it is still recommended to set the traceId, as that will append the new string on the
existing traceId, making log tracking (e.g. when debugging) better.
IMPORTANT NOTICE!! The MatsInitiator
returned from MatsFactory.getDefaultInitiator()
is "magic" in that when employed from within a Mats Stage's context
(thread), it works exactly as this method: Any initiations performed participates in the Mats Stage's
transactional demarcation. Read more at the JavaDoc of default initiator's
JavaDoc
..initiate
in interface MatsEndpoint.ProcessContext<R>
lambda
- provides the MatsInitiator.MatsInitiate
instance on which to create the message to be sent.public void doAfterCommit(java.lang.Runnable runnable)
MatsEndpoint.ProcessContext
null
, you "cancel" any
previously set Runnable.
Note: If any Exception is raised from the stage lambda code after the Runnable has been set, or any Exception
is raised by the processing or committing, the Runnable will not be run.
Note: If the doAfterCommit
Runnable throws a RuntimeException
, it will be logged on
ERROR level, then ignored.doAfterCommit
in interface MatsEndpoint.ProcessContext<R>
runnable
- the code to run right after the transaction of both external resources and messaging has been
committed. Setting to null
"cancels" any previously set Runnable.public <T> java.util.Optional<T> getAttribute(java.lang.Class<T> type, java.lang.String... name)
MatsEndpoint.ProcessContext
MatsInitiator.MatsInitiate.getAttribute(Class, String...)
. There is also a
ThreadLocal-accessible version at MatsFactory.ContextLocal.getAttribute(Class, String...)
.
Mandatory: If the Mats implementation has a transactional SQL Connection, it shall be available by
'context.getAttribute(Connection.class)'
.getAttribute
in interface MatsEndpoint.ProcessContext<R>
T
- The type of the attribute.type
- The expected type of the attributename
- The (optional) (hierarchical) name(s) of the attribute.MatsEndpoint.ProcessContext.getAttribute(Class, String...)
,
MatsFactory.ContextLocal.getAttribute(Class, String...)