-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Brain dump] UB From Dataless Schema, Projection Workflow Duplication, and Proposed Solution #51
Comments
Implementation Detail:
|
Yes, we’re thinking along similar lines here. I’m not yet sure how to best integrate the detailed state computations — without them (and consequently without event payload data) we could condense everything down to the types given in the ECOOP paper. But that’s not expressive enough for real protocols. One idea here is that we generate the code for the machine definition but with placeholders for the command hooks and event transitions. Forgetting to overwrite one of the placeholders would immediately lead to an exception. This way we can keep the generated code in a separate file — messing with an existing user file is always tricky. |
Not quite sure I understand.
Thinking the same too. |
Anyway, for the current state of machine-runner, with TypeScript and stuff, we can add manual versioning API so that tags that are generated are "[swarmprotocolname]", "[swarmprotocolname]:[version]", and then |
@rkuhn this came up during examining SW's unit test problem: a new API that can be compatible with our current API const protocol =
Protocol
.build("theprotocolname", Events.all)
.roles([
"Manager",
"Storage"
])
.states([
"StateA",
"StateB",
"Statec",
"StateD",
"StateE",
"StateF",
"StateG",
"StateH",
])
.initial((states) => states.StateA)
// ^^^^^^^^^^^^^
// hinted
.transitions(({
command, states, roles
}) => {
/**
* List transitions here, the transitions are best written chronologically from the top to bottom
*/
command(roles.Manager, states.StateA, "commandName", [Events.B], states.stateB)
// ^^^^^^^^^^^^^ ^^^^^^^^^^^ ^^^^^^^^^^^^^
// hinted constrained hinted
command(roles.Storage, states.StateB, "commandName", [Events.C], states.stateC)
command(roles.Manager, states.StateB, "commandName", [Events.D], states.stateD)
command(roles.Storage, states.StateD, "commandName", [Events.E], states.stateE)
command(roles.Manager, states.StateC, "commandName", [Events.F], states.stateF)
})
/* alternatively, a command can instead take this shape if we want to process the type information
but the above one is safer */
.command(({states, roles}) => [roles.Manager, states.statesA, "commandName", [Events.B], states.stateB])
// ^^^^^^^^^^^^^ ^^^^^^^^^^^^^ ^^^^^^^^^^^ ^^^^^^^^^^^^^
// hinted hinted constrained hinted
.command(({states, roles}) => [roles.Storage, states.StateB, "commandName", [Events.C], states.stateC])
.finish()
/**
* used in machine-check, produce SwarmProtocolType
*/
const protocolAnalysis = protocol.createJSONForAnalysis();
// Role creation
// ===============
const ForManager = protocol.roles.Manager.createProtocol()
// State Creation
// ===============
const StateAForManager =
ForManager
.states.stateA.design()
// ^^^^^^
// hinted
.withPayload<ThePayload>()
/* probably is not safe from TS version change */
.commands.commandName.define([Events.B], (ctx, param => [param]))
/* or alternatively command can take place like this */
.command(protocol.commands.commandName, [Events.B], (ctx, param) => [param])
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// hinted if type is previously registered
.finish()
// Simpler checks
// NOTE: Throws if somehow this method is called after `checkSwarmProtocol` below is called for this state
ForManager.react(...)
// MACHINE CHECK
// ===============
// Simpler checks
const allRoles = [ForManager, ForStorage] as const
// NOTE:
// - machine-check knows machine-runner has `createJSONForAnalysis` method. It'll use it to grab the subscriptions.
// - Initial is provided
checkSwarmProtocol(protocol, allRoles)
checkProjection(protocol, allRoles, ForManager) |
A completely different approach could be to split state payload computation from state transitions: the observer sees the state name and the sequence of events that led to this state (i.e. the unhandled ones are filtered out). Payload computation could then be fully decoupled and independently versioned. Another (orthogonal) choice would be to use hashing instead of manual versioning, identifying a swarm protocol with the hash of its state machine description. This implies that new instances will not process events written by old instances, which should be fine for many use-cases. Where continuing an old process with new logic is required, a translation scheme like Cambria would be needed, explicitly opting into the processing of old events via a transformation function. |
But this will require the state machine description to be fully written in value, not type (except if we want to include manually using typescript API in the compilation process). Although, in my opinion, the semantics of event sets and event chains are the ones truly needing versioning, while state payload does not. |
Right, using hashing without splitting the state machine from the payload computation makes this more difficult. Anyway, these are future thoughts, I want to first await real world feedback on our current APIs before starting this. |
Potential problem
This issue will list some potential problems the current API does not handle.
UB from incorrect event payload schema in storage
Specific-role projection is not well-formed.
Proposed Solution
The first problem is solvable by caveats, versioning how-tos and best practices, and examples.
The second problem is negligible depending on how the developer can afford to sacrifice writability for the sake of easily solving the distributed-state-machine problem.
However, here is an alternative API:
The 3 steps compilation
Events and swarm state machine (state label and transitions) are defined here. Event payload schema is dataful and uniquely identifiable. Swarm state machine name and the event payload schema will be summed into a unique identifier for the Swarm Protocol. The format of the event tag will roughly be "[swarm_name]:[swarm_identifier]:[arbitrary_id]". The preceding information are compiled into what we will call from this point forward simply: The "swarm protocol". The swarm protocol includes 1.) the swarm name,2.) the events, and 3.) the unique identifier. The swarm protocol will be used for the later steps.
This step uses a compilation result of the first one, let it be just the "swarm_protocol". This step defines the 1.) roles involved in the swarm protocol and 2.) for each role, relevant states from the swarm protocol are marked, optionally, payloads are assigned to these states, and commands are assigned to each of these states. A command contains 1.) the command name, and 2.) the chain of events that will be emitted. The preceding information, mapped into the corresponding role, are compiled into several "agent protocol", one for each role.
From "agent protocol", we can deduce the complete list of "reactions" and "commands". The first action in this step is to "extract" "reactions" and "commands" which are written into a set of definitions that are useful to verify the user's code. Optionally, this extraction method can produce a boilerplate code. The user will then be able to write the details of the "reactions" and "commands" on top of the boilerplate code.
The text was updated successfully, but these errors were encountered: