ExecutionContext
must remain immutable once passed to theExecutionVenue
- Maintain transport independence/agnosticism for services:
- Execution context interface must be the same for all transports (within a paradigm)
- Filters must support all transports within a paradigm (including in-process)
- Enable Zipkin integration (tracer calls and resolution of ids)
- Server transports initially, but will need to consider client (at least for zipkin)
- Cleanup of existing transport code would be a bonus
- Provides the option to change how the
ExecutonContext
is resolved - Doesn't try to deal with
ExecutionContext
extension
Cougar users have long been requesting a filters capability that would allow them access to the http request (in the case of Jetty transport) and a bucket o' values in the ExecutionContext to put stuff, and for just as long we've been rejecting it on the following basis:
- We want to maintain Cougar's key selling point - transport independence with the interface wholely defined in an IDD
However, a recent request for filters was made in the context of providing tracing integration with Zipkin, and although our stance remained the same to begin, we soon realised that there was scope for a filters capability which provides protocol specific implementations of transport independent features (as opposed to protocol specific features).
Andre Pinto has put together a sketch of what a filters capability for Cougar might look like, which provides hooks for pre- and post- command execution.
Note, this gist doesn't try to address the idea of ExecutionContext
extension, as I don't believe it is currently necessary and also to be a hard problem in terms of its implications.
In addition to Andres sketch, which provides hooks for pre- and post- command execution, I'd like to suggest we rework execution context resolution such that plugins can provide alternate implementations from those currently supported in the transports (mostly in ExecutionContextFactory) which would enable more flexibility that current mechanism allow (for example you may want to resolve data from more than 2 headers for building request uuids).
This would entail the following changes:
- Introduce an
ExecutionContextBuilder
, which provides a mutable interface for constructing an ExecutionContext ExecutionContextFactory
would delegate to this builder, but we'd remove from it the methods which take headers.- Introduce a new interface
ExecutionContextResolver<In>
(?), where each transport defines what type<In>
is (c.f.IdentityTokenResolvers
which provides 2 methods: getComponents():ExecutionContextComponent[]
- allows the resolver to declare what parts it can resolveresolve(ExecutionContextBuilder builder, In request, ExecutionContextComponent[] allowed):void
- requests that the resolver resolve those components it has been selected to resolve (if it does more or less there will be an error)- EC resolution will be setup at startup such that there is exactly one resolver for each part of the EC. Resolvers will always be called if they have been registered so that they have an option to error if they don't get enough - which would neatly affect health service calls too).
- Would possibly need to have a ResolverFactory which returns the appropriate one for each transport. Should mandate that resolver used for one part of EC is the same for all transports.
Changes to be made alongside this
- Rename
ExecutionContextWithTokens
toDehydratedExecutionContext
- makes it obvious that we're hydrating when we resolve ID tokens to IDs rather than mutating EC and I never liked the name anyway. Perhaps rework so both this and EC inherit from a base iface. - Rework tracer and ID token resolution into filters / EC resolvers
- Provide default resolver which resolves EC in the way we currently do now
- Introduce an
InProcessExecutable
, which acts as both client and server transport. Make default namespace on clients "InProcess" or similar, and then when they initialise, make a call to ensure that the InProcessExecutable is registered for the service interface that instance represents (ie register if not already done so). This would ensure we have a new request uuid etc for in process calls.
- Something similar to the
IdentityTokenResolver
'srewrite()
method, but we would need a seperate set of resolvers per client. Perhaps have a default set of resolvers that all clients use and then a seperate list of overrides to construct the specific set with.
This details how the Zipkin integration would work in the context of this proposal:
- Zipkin would provide a
ZipkinRequestUUID
iface which extends fromRequestUUID
which provides access to numeric ids. - Zipkin would provide a
ZipkinRequestUUIDGenerator
iface which extends fromRequestUUIDGenerator
which constructs extended uuids it needs. - At startup the plugin would validate that the configured generator implemented this interface.
- Plugin would provide an EC resolver providing resolution for the request uuid. This would obtain the extra headers containing the numeric ids and then construct a zipkin uuid using the configured generator. If it was called and it was not requested to construct the uuid then it could error or warn as appropriate.
- Similarly it would provide an EC writer for the client, we'll have to work something out so it's easy to register default writers for all clients so zipkin can be used for all clients without having to register with each individually.
Assuming that the tracer SPI has been completed, zipkin would also provide a Tracer
implementation, and the plugin's spring context could auto-wire itself in as a tracer into the configured CompoundTracer
.
Ip & port can be obtained from the request, but we don't have access to the request in the Tracer methods (only the RequestUUID and, sometimes, the ExecutionContext).
Regarding the service name:
and it should be the same on the client and server span:
openzipkin/brave#18
https://groups.google.com/d/topic/zipkin-user/Q_EZp3pQXk4/discussion
so ideally it shouldn't be the hostname as it is quite common to have the client directing the RPCs to an intermediate network layer and not to a specific host, in which case the client wouldn't know the hostname of the machine that will process its request.