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
.
What do you practically mean with "not dealing with
ExecutionContext
extension"? Does that mean we are not supposed to try to solve the request scoped data storage problem in a generic way with this solution?What is in practice an
ExecutionContextComponent
and how is it represented?How would you implement this constrain?
If we already have a
ZipkinRequestUUID
with numeric ids why would Zipkin need to extendUUIDGenerator
if it can't benefit from any of this interface's methods?validateUuid
is inadequate if we consider Zipkin uses separate and numeric ids, not merged strings, andgetNextUUID
is not really about Zipkin data but a Cougar requirement for UUID which Zipkin doesn't use.Is it just because of the
cougar.client.uuidgenerator
property? In that case it will not solve the problems I referred on the email (someone might just implement acougar.client.uuidgenerator
not compatible with Zipkin) and will still make it impossible for any Cougar that uses Zipkin to have a different system to generate the UUID (which Zipkin actually doesn't enforce in any way). The first problem could be solved by removing the support for a configurablecougar.client.uuidgenerator
but that would break backwards compatibility, the second one seems impossible to solve if the Zipkin generator must absolutely be anUUIDGenerator
. If you really want to implement this onRequestUUID
then wouldn't it be better to handle Zipkin ids generation only inZipkinRequestUUID
(or a non-configurable class used by this class) and delegate theUUID
to a traditional Cougar generator set bycougar.client.uuidgenerator
? In my opinion, it seems that you are forcing Zipkin data into a Cougar construction when it doesn't really fit in there.Also, even though you have the
ZipkinRequestUUID
, this doesn't solve the problem of accessing the numeric ids via the provided interface onExecutionContext
, as it only returns theRequestUUID
which only supports Strings. So still reflection or casting would be required. Additional validations might grant partial integrity for these expectations to actually not trigger runtime errors, but still it is only runtime validation, not enforced by compile time contracts and possibly more complex and harder to maintain.I think your proposal has the right intentions to bring flexibility to Cougar and it is surely nice to be able to define how the
ExecutionContext
primitives are actually built in a composable way, but, in practice, it will maintain the current request scoped data limitation, the casts/reflection requirement for Zipkin, theuuidgenerator
inadequacy and the isolation problem between UUID and Zipkin Ids generation. It will still require the additional Filters constructions to access the data, while removing from them the needed control over the data they use, and end up trimming the flexibility to the small number of components already thought for a generic Cougar implementation and, even those, only under the inflexible constrains of their interfaces.I appreciate your effort in trying to bring flexibility and improvement while keeping backwards compatibility in the
ExecutionContext
but I'm afraid any solution that doesn't actually address theExecutionContext
will come out as limited, contrived and unnecessarily complex. In any case, as I said before, if we really can't change the ExecutionContext and this is the way to go in order to implement Zipkin, then let's do it.P.S.: I didn't understand
InProcessExecutable
role in all this. Could you clarify please?