All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.mats3.serial.MatsTrace Maven / Gradle / Ivy

Go to download

Mats^3 wire format solution called "MatsTrace", which defines a set of parameters and structures sufficient to represent an envelope carrying Mats messages, as well as a deser-interface "MatsSerializer" which defines methods between MatsTrace and byte arrays. Employed by the Mats^3 JMS Implementation.

There is a newer version: 0.19.22-2024-11-09
Show newest version
package io.mats3.serial;

import java.util.List;
import java.util.Optional;
import java.util.Set;

import io.mats3.serial.MatsTrace.Call.MessagingModel;

/**
 * Together with the {@link MatsSerializer}, this interface describes one way to implement a wire-protocol for how Mats
 * communicates. It is up to the implementation of the MatsFactory to implement a protocol of how the Mats
 * API is transferred over the wire. This is one such implementation that can be used, which is employed by the default
 * JMS implementation of Mats.
 * 

* The MatsTrace is designed to contain all previous {@link Call}s in a processing, thus helping the debugging for any * particular stage immensely: All earlier calls with data and stack frames for this processing is kept in the trace, * thus enabling immediate understanding of what lead up to the particular situation. *

* However, for any particular invocation (invoke, request or reply), only the current (last) {@link Call} - along with * the stack frames for the same and lower stack depths than the current call - is needed to execute the stage. This * makes it possible to use a condensed variant of MatsTrace that only includes the single current {@link Call}, along * with the relevant stack frames. This is defined by the {@link KeepMatsTrace} enum. *

* One envisions that for development and the production stabilization phase of the system, the long form is used, while * when the system have performed flawless for a while, one can change it to use the condensed form, thereby shaving * some cycles for the serialization and deserialization, but more importantly potentially quite a bit of bandwidth and * message processing compared to transfer of the full trace. * * @param * The type which STOs and DTOs are serialized into. When employing JSON for the "outer" serialization of * MatsTrace, it does not make that much sense to use a binary (Z=byte[]) "inner" representation of the DTOs * and STOs, because JSON is terrible at serializing byte arrays. * * @author Endre Stølsvik - 2018-03-17 23:37, factored out from original from 2015 - http://endre.stolsvik.com */ public interface MatsTrace { /** * String employed as return value for "debug only" fields which may as well be null - by setting them to null, we * conserve time and space in the serialization (For JSON, even the field itself is not serialized if the value is * null). If they are null, the corresponding getter returns this value. */ String NULLED = "-nulled-"; /** * Can only be set once.. * * @param initiatorId * the id set using matsInitiate.from(initiatorId) - it is not the name of the initiator. * @return this, for chaining. Note that this is opposed to the add[Request|Send|Next|Reply]Call(..) * methods, which return a new, independent instance. */ MatsTrace withDebugInfo(String initializingAppName, String initializingAppVersion, String initializingHost, String initiatorId, String debugInfo); /** * If this newly created MatsTrace is a child-flow (initiated within a Stage) of an existing flow, then this method * should be invoked to set the parent MatsMessageId, and the "total call number" which is a "Call Overflow * protection" mechanism. *

* Parent Mats Message Id: The MatsMessageId of the message whose processing spawned this new flow. *

* Total Call Number: Initializes the {@link #getTotalCallNumber()}. If this message is initiated within an * existing call flow, set to the current call's {@link #getTotalCallNumber()} + 1. This number will increase for * every subsequent call this flow is going through, just as with {@link #getCallNumber()} - the difference being * that it should continue increasing if a new message is initiated within a flow. Thereby it is possible to * stop an out-of-control initiate-send/request recursion, by checking that the {@link #getTotalCallNumber()} * doesn't ever go above a fixed number, e.g. 100. * * @param parentMatsMessageId * the MatsMessageId of the message whose processing spawned this new flow. * @param totalCallNumber * the {@link #getTotalCallNumber()} to initialize this MatsTrace with. * @return this, for chaining. Note that this is opposed to the add[Request|Send|Next|Reply]Call(..) * methods, which return a new, independent instance. */ MatsTrace withChildFlow(String parentMatsMessageId, int totalCallNumber); /** * @return the TraceId that this {@link MatsTrace} was initiated with - this is set once, at initiation time, and * follows the processing till it terminates. (All log lines will have the traceId set on the MDC.) */ String getTraceId(); /** * @return the "FlowId", which is a system-specified, guaranteed-globally-unique TraceId - and shall be the prefix * of each {@link Call#getMatsMessageId()}, separated by a "_". */ String getFlowId(); long getInitializedTimestamp(); /** * @return to which extent the Call history (with State) should be kept. The default is * {@link KeepMatsTrace#COMPACT}. */ KeepMatsTrace getKeepTrace(); /** * Specifies how the MatsTrace will handle historic values that are present just for debugging. Notice the annoyance * that this is effectively specified twice, once in the Mats API, and once here. That is better, IMHO, than this * package depending on the Mats API only for that enum. */ enum KeepMatsTrace { /** * Keeps all Data and State history for the entire trace. (Still the {@link Call#getFrom() from} and * {@link Call#getReplyStack() stack} will be nulled, as it provide zero value that cannot be deduced from the * previous calls). */ FULL, /** * Default: Nulls out Data for other than current call while still keeping the meta-info for the call * history, and condenses State to a pure stack. */ COMPACT, /** * Only keep the {@link MatsTrace#getCurrentCall() current call}, and condenses State to a pure stack. */ MINIMAL } /** * @return whether the message should be JMS-style "non-persistent", default is false (i.e. persistent, * reliable). */ boolean isNonPersistent(); /** * @return whether the message should be prioritized in that a human is actively waiting for the reply, default is * false. */ boolean isInteractive(); /** * @return the number of milliseconds the message should live before being time out. 0 means "forever", and is the * default. */ long getTimeToLive(); /** * @return a hint to the underlying implementation, or to any monitoring/auditing tooling on the Message Broker, * that it does not make much value in auditing this message flow, typically because it is just a "getter" * of information to show to some user, or a health-check validating that some service is up and answers in * a timely fashion. */ boolean isNoAudit(); // --- Stuff set with the 'withDebugInfo(...)' method. String getInitializingAppName(); String getInitializingAppVersion(); String getInitializingHost(); /** * @return a fictive "endpointId" of the initiator, see MatsInitiator.MatsInitiate.from(String). */ String getInitiatorId(); String getDebugInfo(); /** * @return the number of calls that this MatsTrace have been through, i.e. how many times * {@link MatsTrace#addRequestCall(String, String, MessagingModel, String, MessagingModel, Object, Object, Object) * MatsTrace.add[Request|Next|Reply..](..)} has been invoked on this MatsTrace. This means that right after * a new MatsTrace has been created, before a call has been added, 0 is returned. With KeepMatsTrace at * {@link KeepMatsTrace#FULL FULL} or {@link KeepMatsTrace#COMPACT COMPACT}, the returned number will be the * same as {@link #getCallFlow()}.size(), but with {@link KeepMatsTrace#MINIMAL MINIMAL}, that number of * always 1, but this number will still return the number of calls that has been added through the flow. * * @see #getTotalCallNumber() */ int getCallNumber(); /** * "Stack overflow protection" mechanism. * * @return the total call number, which is the same as {@link #getCallNumber()} unless this flow was * initiated within a stage, in which case the totalCallNumber starts at the current call number at that * stage (as set with {@link #withChildFlow(String, int)}). This ensures that if we end up with e.g. a mats * flow initiating a new mats flow to itself, thus creating a loop, this number will continuously increase, * and we can thus break out at some obviously-too-large value. */ int getTotalCallNumber(); /** * If this is a {@link #withChildFlow(String, int) child flow} of an existing flow, this should return the * MatsMessageId of the message whose processing spawned this new flow. * * @return the MatsMessageId of the message whose processing spawned this new flow. */ String getParentMatsMessageId(); /** * Sets a trace property, refer to ProcessContext.setTraceProperty(String, Object). Notice that on the * MatsTrace-side, the value must be of type {@code Z}. * * @param propertyName * the name of the property. * @param propertyValue * the value of the property. * * @see #getTracePropertyKeys() * @see #getTraceProperty(String) */ void setTraceProperty(String propertyName, Z propertyValue); /** * Retrieves a property value set by {@link #setTraceProperty(String, Object)}, refer to * ProcessContext.getTraceProperty(String, Class). Notice that on the MatsTrace-side, the value is of * type {@code Z}. * * @param propertyName * the name of the property to retrieve. * @return the value of the property. * * @see #setTraceProperty(String, Object) * @see #getTracePropertyKeys() */ Z getTraceProperty(String propertyName); /** * @return the set of keys containing {@link #getTraceProperty(String) trace properties}. * * @see #getTraceProperty(String) * @see #setTraceProperty(String, Object) */ Set getTracePropertyKeys(); /** * Adds a {@link Call.CallType#REQUEST REQUEST} Call, which is an invocation of a service where one expects a Reply * from this service to go to a specified endpoint, typically the next stage in a multi-stage endpoint: Envision a * normal invocation of some method that returns a value. * * @param from * which stageId this request is for. This is solely meant for monitoring and debugging - the protocol * does not need the from specifier, as this is not where any replies go to. * @param to * which endpoint that should get the request. * @param toMessagingModel * the {@link MessagingModel} of 'to'. * @param replyTo * which endpoint that should get the reply from the requested endpoint. * @param replyToMessagingModel * the {@link MessagingModel} of 'replyTo'. * @param data * the request data, most often a JSON representing the Request Data Transfer Object that the requested * service expects to get. * @param replyState * the state data for the stageId that gets the reply to this request, that is, the state for the stageId * that is at the first element of the replyStack. Most often a JSON representing the State Transfer * Object for the multi-stage endpoint. * @param initialState * an optional feature, whereby the state can be set for the initial stage of the requested endpoint. * Same stuff as replyState. */ MatsTrace addRequestCall(String from, String to, MessagingModel toMessagingModel, String replyTo, MessagingModel replyToMessagingModel, Z data, Z replyState, Z initialState); /** * Adds a {@link Call.CallType#SEND SEND} Call, meaning a "request" which do not expect a Reply: Envision an * invocation of a void-method. Or an invocation of some method that returns the value, but where you invoke it as a * void-method (i.e. not storing the result, e.g. the method map.remove("test") returns the removed * value, but is often invoked without storing this.). * * @param from * which stageId this request is for. This is solely meant for monitoring and debugging - the protocol * does not need the from specifier, as this is not where any replies go to. * @param to * which endpoint that should get the message. * @param toMessagingModel * the {@link MessagingModel} of 'to'. * @param data * the request data, most often a JSON representing the Request Data Transfer Object that the receiving * service expects to get. * @param initialState * an optional feature, whereby the state can be set for the initial stage of the requested endpoint. */ MatsTrace addSendCall(String from, String to, MessagingModel toMessagingModel, Z data, Z initialState); /** * Adds a {@link Call.CallType#NEXT NEXT} Call, which is a "skip call" to the next stage in a multistage service, as * opposed to the normal request out to a service expecting a reply. The functionality is functionally identical to * {@link #addSendCall(String, String, MessagingModel, Object, Object)} addSendCall(...)}, but has its own * {@link Call.CallType CallType} enum value {@link Call.CallType#NEXT NEXT}. *

* Note: Cannot specify {@link MessagingModel} here, as one cannot fathom where that would make sense: It must be * {@link MessagingModel#QUEUE QUEUE}. * * @param from * which stageId this request is for. This is solely meant for monitoring and debugging - the protocol * does not need the from specifier, as this is not where any replies go to. * @param to * which endpoint that should get the message - the next stage in a multi-stage service. * @param data * the request data, most often a JSON representing the Request Data Transfer Object that the next stage * expects to get. * @param state * the state data for the next stage. */ MatsTrace addNextCall(String from, String to, Z data, Z state); /** * Adds a {@link Call.CallType#REPLY REPLY} Call, which happens when a requested service is finished with its * processing and have some Reply to return. This method pops the stack (takes the last element) from the (previous) * current call, sets this as the "to" parameter, and uses the rest of the list as the stack for the next Call. * * @param from * which stageId this request is for. This is solely meant for monitoring and debugging - the protocol * does not need the from specifier, as this is not where any replies go to. * @param data * the request data, most often a JSON representing the Request Data Transfer Object that the requesting * service expects to get. */ MatsTrace addReplyCall(String from, Z data); /** * @param data * the request data, most often a JSON representing the Request Data Transfer Object that the passed-to * endpoint expects to get. * @param initialState * an optional feature, whereby the state can be set for the initial stage of the requested endpoint. */ MatsTrace addGotoCall(String from, String to, Z data, Z initialState); /** * Shall be invoked after adding the outgoing call, immediately before serializing the outgoing MatsTrace. *

    *
  • Sets the outgoing Call's {@link Call#getCalledTimestamp()} to be more closely aligned to the exact sending * time. (For example, the message may have been constructed, then a massive SQL query was performed, and then a new * message is constructed, and then the messages are actually turned into JMS messages and committed on the wire. * This means that the first message will have a much earlier timestamp than the second.) Using this method, all * outgoing messages can have the Called Timestamp set right before it is serialized and JMS-constructed and * committed.
  • *
  • If the {@link #getCurrentCall()} is a REQUEST, SEND, GOTO or PUBLISH (anything else than REPLY), it also sets * the same-height-called-timestamp, recorded on the stackframe below it, or on the MatsTrace itself if there is no * stackframe below (initial SEND). This is to be able to calculate the "time between stages" for the e.g. time * between stage1 and stage2 of a multi-stage endpoint, noting that this might entail multiple levels of Request and * Replies (thus it must reside on the stack).
  • *
*/ void setOutgoingTimestamp(long timestamp); /** * @return the timestamp set by {@link #setOutgoingTimestamp(long)} for the preceding call on the same stack height. * Used to calculate the "time between stages" for the different stages on an endpoint. It does not make * sense to get this for the initial stage of an Endpoint if the incoming is a REQUEST, and the return value * will then be -1. */ long getSameHeightOutgoingTimestamp(); /** * Invoke this as early as possible on the reception of a message. Used to calculate the "total endpoint time", * through all stages (including intermediate request/replies): Time from entry on the Initial Stage, to when the * endpoint Replies, or stops (no outgoing message). */ void setStageEnteredTimestamp(long timestamp); /** * @return the value of {@link #setStageEnteredTimestamp(long)} for the stages of the same endpoint. Used to * calculate the "total endpoint time", through all stages, when the endpoint Replies, or stops (no outgoing * message). */ long getSameHeightEndpointEnteredTimestamp(); /** * @return this MatsTrace's SpanId. If it is still on the initiator side, before having had a call added to it, or * on the terminator side, when the stack again is empty, the SpanId is derived from the {@link #getFlowId() * FlowId}. Otherwise, it is the topmost element of an internal stack, in the same way as * {@link #getCurrentCall()}.{@link Call#getReplyStack()}. */ long getCurrentSpanId(); /** * @return the {@link Call Call} which should be processed by the stage receiving this {@link MatsTrace} (which * should be the stageId specified in getCurrentCall().{@link Call#getTo() getTo()}). Returns * null if not call has yet been added to the trace. */ Call getCurrentCall(); /** * Returns the {@link StackState} for the {@link #getCurrentCall()}, if present. *

* Searches in the {@link #getStateFlow() 'State Flow'} from the back (most recent) for the first element that is at * the current stack height, as defined by {@link #getCurrentCall()}.{@link Call#getReplyStackHeight()}. If a more * shallow stackDepth than the specified is encountered, or the list is exhausted without the Stack Height being * found, the search is terminated with null. This happens for the initial stage for an endpoint, unless the * 'initialState' was set on the SEND or REQUEST. *

* The point of the 'State Flow' is the same as for the Call list: Monitoring and debugging, by keeping a history of * all calls in the processing, along with the states that was present at each call point. *

* If "condensing" is on ({@link KeepMatsTrace#COMPACT COMPACT} or {@link KeepMatsTrace#MINIMAL MINIMAL}), the * stack-state-list is - by the condensing algorithm - turned in to a pure stack (as available via * {@link #getStateStack()}), with the StackState for the earliest stack element at position 0, while the latest * (current) at end of list. The above-specified search algorithm still works, as it now will either find the * element with the correct stack depth at the end of the list, or it is not there. *

* NOTE: The StateStack (mostly) includes a frame for the current call, as opposed to the * {@link Call#getReplyStack()} (reply stack), which only includes frames below us. Note that as a matter of * avoiding space use, on a REQUEST call, the StackState is not added for the actual REQUEST message's state stack, * unless the "initial incoming state" is supplied (which is uncommon - a service invocation typically starts with * an empty state). However, on REPLY messages, it will always be present, and hence the state stack is typically * one level higher (includes current frame) than the reply stack (only includes frames below). *

* NOTE: As further info on how the state stack relates to the reply stack height: When a REPLY comes to a * terminator, there are 0 more frames below. However, the terminator needs its state, which is at state stack * height 0. * * @return the {@link StackState} for the {@link #getCurrentCall()} if it exists, null otherwise, as is * typical when entering initial stage of an endpoint. */ Optional> getCurrentState(); /** * @return the stack of the states for the current stack: getCurrentCall().getStack(). * @see #getCurrentState() for more information on how the "State Flow" works. */ List> getStateStack(); /** * @return the flow of calls, from the first REQUEST (or SEND), to the {@link #getCurrentCall() current call} - * unless {@link #getKeepTrace() KeepTrace} is MINIMAL, in which case only the current call is present in * the list. */ List> getCallFlow(); /** * @return the entire list of states as they have changed throughout the call flow. If {@link KeepMatsTrace} is * COMPACT or MINIMAL, then it will be a pure stack (as returned with {@link #getStateStack()}, with the * last element being the most recent stack frame. NOTICE: The index position in this list has little to do * with which stack level the state refers to. This must be gotten from {@link StackState#getHeight()}. * @see #getCurrentState() for more information on how the "State Flow" works. */ List> getStateFlow(); /** * Represents an entry in the {@link MatsTrace}. */ interface Call { long getCalledTimestamp(); /** * @return the Mats Message Id, a guaranteed-globally-unique id for this particular message - it SHALL be * constructed as follows: {@link MatsTrace#getFlowId()} + "_" + flow-unique messageId. */ String getMatsMessageId(); /** * Can only be set once. */ Call setDebugInfo(String callingAppName, String callingAppVersion, String callingHost, String debugInfo); String getCallingAppName(); String getCallingAppVersion(); String getCallingHost(); String getDebugInfo(); CallType getCallType(); /** * @return when {@link #getCallType()} is {@link CallType#REPLY REPLY}, the value of the REQUEST's SpanId is * returned, otherwise an {@link IllegalStateException} is thrown. */ long getReplyFromSpanId(); /** * Which type of Call this is. */ enum CallType { REQUEST, SEND, // Also used for Publish as it is technically the same, just sent over a topic NEXT, REPLY, /** Not yet used. Ref issue #69 */ GOTO } /** * @return the stageId that sent this call - will most probably be the string {@link #NULLED "-nulled-"} for any * other Call than the {@link MatsTrace#getCurrentCall()}, to conserve space in the MatsTrace. The * rationale for this, is that if those Calls are available, they are there for debug purposes only, and * then you can use the order of the Calls to see who is the caller: The previous Call's {@link #getTo() * "to"} is the {@link #getFrom() "from"} of this Call. */ String getFrom(); /** * @return the endpointId/stageId this Call concerns, wrapped in a {@link Channel} to also specify the * {@link MessagingModel} in use. */ Channel getTo(); /** * An encapsulation of the stageId/endpointId along with the {@link MessagingModel} the message should be * delivered over. */ interface Channel { String getId(); MessagingModel getMessagingModel(); } /** * Specifies what type of Messaging Model a 'to' and 'replyTo' is to go over: Queue or Topic. Queue is the * obvious choice for most traffic, but sometimes talking to all nodes in a cluster is of interest. */ enum MessagingModel { QUEUE, TOPIC } Z getData(); /** * @return the stack height of this Call - which is the number of elements below this call. I.e. for a * {@link CallType#REPLY REPLY} to a Terminator, the stack is of size 0 (there are no more elements to * REPLY to), while for the first {@link CallType#REQUEST REQUEST} from an initiator, the stack is of * size 1 (the endpointId for the Terminator is the one element below this Call). */ int getReplyStackHeight(); /** * @return a COPY of the replyTo stack of Channels (if you just need the height, which is the common case, use * {@link #getReplyStackHeight()}) - NOTICE: This will most probably be a List with * {@link #getReplyStackHeight()} elements containing "-nulled-" for any other Call than the * {@link MatsTrace#getCurrentCall()}, to conserve space in the MatsTrace. The LAST (i.e. position * 'size()-1') element is the most recent, meaning that the next REPLY will go here, while the FIRST * (i.e. position 0) element is the earliest in the stack, i.e. the stageId where the Terminator * endpointId typically will reside (unless the initial call was a {@link CallType#SEND SEND}, which * means that you don't want a reply). */ List getReplyStack(); } /** * The State instances (of type Z), along with the height of the stack the state relates to. */ interface StackState { /** * @return which stack height this {@link #getState()} belongs to. */ int getHeight(); /** * @return the state at stack height {@link #getHeight()}. */ Z getState(); /** * Sets "extra state" on this StackState. */ void setExtraState(String key, Z value); /** * Retrieves "extra state" on this StackState. */ Z getExtraState(String key); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy