Extend backend "ClientTypes"#4542
Conversation
c9f868a to
785949a
Compare
| LibService.Init.init name | ||
| LibExecution.Init.init () | ||
| Telemetry.Console.loadTelemetry name Telemetry.DontTraceDBQueries | ||
| //? ClientTypes.Init.init name |
There was a problem hiding this comment.
TODO: figure out if this should be removed/uncommented.
| (LibBackend.Init.init LibBackend.Init.WaitForDB name).Result | ||
| let result = (run options).Result | ||
| LibService.Init.shutdown name | ||
| //? ClientTypes.Init.init name |
There was a problem hiding this comment.
TODO: figure out if this should be removed/uncommented.
| LibService.Telemetry.Console.loadTelemetry | ||
| "DataTests" | ||
| LibService.Telemetry.DontTraceDBQueries | ||
| //? ClientTypes.Init.init name |
There was a problem hiding this comment.
TODO: figure out if this should be removed/uncommented.
|
This is ready for an initial review. I tried my best to break the commits down well and add appropriate commentary. |
pbiggar
left a comment
There was a problem hiding this comment.
This looks great.
My one suggestion would be to make the testing more thorough - we currently don't have full end-to-end analysis including the conversion functions, and there's so much conversion that typos are easy to sneak in (both now and in the future).
| @@ -1,7 +0,0 @@ | |||
| { | |||
There was a problem hiding this comment.
We don't yet handle the case of the Pusher.com AddOpV1 payload being too big to push to the client.
let tooBigPayload = { tlids = tlids } |> Json.Vanilla.serialize
// CLEANUP: when changes are too big, notify the client to reload them. We'll
// have to add support to the client before enabling this. The client would
// reload after this.
// push canvasID "addOpTooBig" tooBigPayload
is the code before changes, and we have the equivalent (just swallow the issue) in the new code.
Since we don't yet serialize it, I figured we didn't need to register the serializer, nor test for how it serializes.
| v<LibExecution.ProgramTypes.Oplist> "complete" testOplist | ||
| v<LibExecution.ProgramTypes.Handler.T> "simple" testHttpHandler | ||
| v<LibExecution.ProgramTypes.Oplist> "complete" ProgramTypes.testOplist | ||
| v<LibExecution.ProgramTypes.Handler.T> "simple" ProgramTypes.testHttpHandler |
There was a problem hiding this comment.
Surely this isn't allowed serialize anymore?
There was a problem hiding this comment.
It seems they are.
LibExecution.ProgramTypes.Handler.Tis used in a canvas-clone test, and the .allow is registered inTests.fsjust for thatLibExecution.ProgramTypes.Oplistis used inCanvas'sloadJsonFromDisk
As I look through other non-ClientTypes types, I seem to find similar edge cases.
| userTypes = testUserTypes | ||
| packageFns = [ testPackageFn ] | ||
| userFns = | ||
| List.map CT2Program.UserFunction.toCT ProgramTypes.testUserFunctions |
There was a problem hiding this comment.
It seems the tests here are sometimes for ClientTypes and sometimes for converted types. It feels like there should be tests for roundtrips from real types, right? Maybe also roundtrips from ClientTypes?
d5d2f3d to
6712b78
Compare
pbiggar
left a comment
There was a problem hiding this comment.
This looks fantastic, nice job!
A couple of small suggestions and we should be good to merge.
| } | ||
| } | ||
|
|
||
| // TODO: these are used outside of analysis (e.g. in APIWorkers) so the type def |
There was a problem hiding this comment.
Unclear action this comment implies?
There was a problem hiding this comment.
The WorkerState module/type isn't just used in Analysis (as implied by its location in AnalysisTypes.res).
It's also used in the "initial load" API payload, and is referenced by APIWorkers file.
Just thinking to move the module definition somewhere else, but I'm not sure where that would be.
|
|
||
| #nowarn "988" | ||
|
|
||
| module Init = |
There was a problem hiding this comment.
I'm unclear the purpose of this. (Is the comment explaining it? If so, that's not clear to me)
There was a problem hiding this comment.
Based on our new standard of initializing serializers in the "binary projects," it felt like an "init function" would be appropriate in the Analysis project. Since the actual initialization has to occur in LibAnalysis, I figured I'd create a little 'wrapper' to handle the initializations. It's probably redudant/useless though?
There was a problem hiding this comment.
I forget why the init is in LibAnalysis actually. I'll try throwing it into Analysis and see what breaks, if anything.
There was a problem hiding this comment.
Ah. Tests.fs calls on LibAnalysis.initSerializers (). I think if I try moving those to Analysis, I get some .net WASM compile issue. I should have read my own comment :)
|
|
||
| let pushNewStaticDeploy (canvasID : CanvasID) (asset : StaticAssets.StaticDeploy) = | ||
| push canvasID "new_static_deploy" (Json.Vanilla.serialize asset) | ||
| let serialized = eventSerializer event |
There was a problem hiding this comment.
Can you move the serialization inside the fireAndForget so that it's done in the background?
| (traceID : CTA.TraceID) | ||
| (traceData : CTA.TraceData.T) | ||
| (traceID : CTAnalysis.TraceID) | ||
| (traceData : CTAnalysis.TraceData) |
There was a problem hiding this comment.
It seems odd that these take clients types for some arguments. I think if we're going to convert from clients types in one place, we should do the conversion for all the arguments in that place.
There was a problem hiding this comment.
I agree. I ended up extracting this mapping out to CT2Analysis, including the bits where we map from PTs to RTs. I think this turned out much nicer.
| @@ -0,0 +1,19 @@ | |||
| module ClientTypes.Converters | |||
|
|
|||
| module STJ = | |||
There was a problem hiding this comment.
Perhaps there's an opportunity to get rid of this. We could argue that the ClientType here is really a string and the conversion functions should do the parsing, rather than rely on this (I feel this particular thing is annoying to learn and keep track of)
There was a problem hiding this comment.
I don't understand what you mean here. Could you please rephrase or briefly chat?
There was a problem hiding this comment.
Never mind - I understand now.
| Json.Vanilla.registerConverter (ClientTypes.Converters.STJ.WorkerStateConverter()) | ||
| Json.Vanilla.allow<CTPusher.Payload.UpdateWorkerStates> "Pusher" | ||
|
|
||
| // allow serialization of types used in Pusher.com payloads |
There was a problem hiding this comment.
(apparently I had created duplicate .allow statements here for pusher.com payloads)
63c5fd7 to
bf4bb75
Compare
C_T won't work. ProgramTypes and PusherTypes, and ApiTypes and AnalysisTypes, 'collide' in abbreviation. So, let's just stick to CT___.
…inits in ExecHost
- I think maintaining a lazy list of data is clearer than a ResizeArray that is added to with a function - Some tests around the persisted output files were trying to test for multiple things at once, so I split them up - Fixes issue: multiple test cases/records were attempting to write to the same file path, namely because of type abbreviations confusing things - Adjust code commentary
bf4bb75 to
95e7612
Compare
|
This has been rebased to latest - ready for another review. |
Changelog:
Our backend passes data back and forth with our client in a few different ways:
ApiServer/UI.fswhich injects JSON intoui.htmldynamicallyPresently, all of this communication is done via JSON payloads, serialized largely by our
Prelude.VanillaSystem.Text.Json serializer. The types infsharp-backendcorresponding to those JSON payloads are spread throughout our codebase, resulting in us serializing "internal" types.That said, there's been some effort in the past to create "client types" in a
ClientTypesproject - the idea is to 'map' between internal types and external types, such that we may safely update either without breaking the communication between the Client and the Server. Generally, I've seen this idea referred to as "DTOs - Data Transfer Objects." Our ClientType definitions/setup is currently quite thin, and only covers Analysis and several APIs.As is, it's unsafe to make many sorts of adjustments to our internal F# types (e.g. removing or renaming fields), as the serialized payload wouldn't be parse-able in our ReScript code, or vice versa.
The intent of this PR is to consolidate most (if not all) of the types used in client-server communication, to reduce/remove the current risks involved in updating internal types.
The specific cause of this work being done is an ongoing effort to "rebrand" our "Patterns" as "Match Patterns," as we'd like to shortly implement "let patterns" and so our current 'branding' would be confusing. Specifically, these existing "patterns" are serialized in a style of "
PBlank," (corresponding to a 'blank' match pattern) and we'd like them to instead be serialized as "MPBlank". Ourclientis set up to handle this change, (our Match Pattern deserializer is able to parse bothMPBlankandPBlank) but in order to finish this work, we need to be able to safely pass the new "MPBlank"-style payloads fromclientto our backend. Our backend's "AddOpV1" API endpoint request payload includes Match Patterns, and the types referenced are "internal" (not client types).So, the motivation behind this PR is to ensure all types referenced by the "AddOpV1" are ClientTypes, where we may safely have both "
MPBlank" and "PBlank"-style definitions for these patterns, and translate those appropriately to a single "MPBlank" case in our "internal" types. (so the rename doesn't bleed into our domain logic). We could have gotten around this by writing a specialized serializer for the MatchPattern type, but moving everything to ClientTypes seems more beneficial long-term.This work involved:
ProgramTypes, Ops, Worker, Secrets, etc,
ClientTypes2ExecutionTypeshandles conversions for our core types defined inLibExecutionClientTypes2BackendTypeshandles types elsewhere, namely inLibBackendwhich is responsible for the surrounding infrastructureSerialization.Tests.fs: reference the new types in testsTestJsonEncoding.res: mostly, file names have simply adjusted due to updates inSerialization.Tests.fsAt this point, the PR is aimed to be a no-op; no functional changes. As far as I can tell, that is the case.
I have some outstanding questions/concerns/todos:
Json.Vanilla.allow<CTApi.Ops.AddOpV1.Request> "ApiServer.AddOps"calls should be made, to explicitly allow serializations of various types. Previously these types were generally 'registered' in the assembly which needed them, but I've been starting to think that this might make more sense in aninitwithinClientTypesitselfClientTypes.Init.initcall toDataTests,ExecHost, andCronCheckerprojects.Serialization.Tests.fsis feeling a bit unwieldy - maybe we should break this down into 2 files, one for client-server serializations, and one for DB-bound serializationsit feels a bit awkward/confusing to me right now that many of the static "Values" in that file are "client types" and many are "internal" types that we map to "serialized types". Maybe it was a mistake for me to update those static Values to ClientTypes?
This is a bit non-exhaustive - there are some tests around our Vanilla serialize still in
Serialization.Tests.fsthat reference types outside ofClientTypes. Likely in another PR, I need to figure out where these are used and what to do about them.