From f55443be61f40a4dc2bca0e060cd56574e11185f Mon Sep 17 00:00:00 2001 From: Alex Sklar Date: Wed, 24 Apr 2024 08:35:09 -0700 Subject: [PATCH] feat(diag): add remaining diagram first drafts Co-authored-by: Erik Margetis Co-authored-by: Joseph Liang --- src/components/Diagrams/index.js | 40 ++++++++++++ src/pages/case-study.mdx | 31 ++++++--- .../diagrams/diag-Dashboard-Architecture.svg | 21 +++++++ static/img/diagrams/diag-Subscriptions.svg | 63 +++++++++++++++++++ .../diagrams/diag-cloudflare-architecture.svg | 39 ++++++++++++ .../diagrams/diag-cloudflare-connection.svg | 35 +++++++++++ static/img/diagrams/diag-global-rooms.svg | 16 +++++ static/img/diagrams/diag-snapshotId.svg | 27 ++++++++ 8 files changed, 264 insertions(+), 8 deletions(-) create mode 100644 static/img/diagrams/diag-Dashboard-Architecture.svg create mode 100644 static/img/diagrams/diag-Subscriptions.svg create mode 100644 static/img/diagrams/diag-cloudflare-architecture.svg create mode 100644 static/img/diagrams/diag-cloudflare-connection.svg create mode 100644 static/img/diagrams/diag-global-rooms.svg create mode 100644 static/img/diagrams/diag-snapshotId.svg diff --git a/src/components/Diagrams/index.js b/src/components/Diagrams/index.js index b5a2728..14fdf22 100644 --- a/src/components/Diagrams/index.js +++ b/src/components/Diagrams/index.js @@ -198,6 +198,46 @@ export const NoConflictEdits = () => { > } +export const Subscriptions = () => { + return +} + +export const DashboardArchitecture = () => { + return +} + +export const CloudflareArchitecture = () => { + return +} + +export const GlobalRooms = () => { + return +} + +export const SnapshotID = () => { + return +} + export const Template = () => { return +dashboard screenshot For even more detail, a tail logging session for your deployed backend can be started by running `npx syncosaurus tail` @@ -599,7 +614,7 @@ Before we can discuss the architecture of Syncosaurus, it’s important to summa - Multiple concurrent rooms (maintaining WebSocket connections and broadcasting messages) - Persistent storage of documents -[TODO insert graphic of multiple rooms with clients connected and a new client trying to connect to a particular room] + ## Architecture @@ -634,7 +649,7 @@ While this is a valid approach, it does involve provisioning and connecting seve As previously mentioned, we instead ended up choosing a Cloudflare-based architecture consisting of Workers and Durable Objects due to its simplicity and extremely strong fit for our use case. -[TODO insert graphic] + A Worker is an edge-based serverless function on the Cloudflare network. A Durable Object (DO) is a special type of Worker - a globally unique [JavaScript isolate ](https://v8docs.nodesource.com/node-0.8/d5/dda/classv8_1_1_isolate.html)(a unique JS engine) with access to its own private strongly consistent [storage](https://developers.cloudflare.com/durable-objects/api/transactional-storage-api/) and built-in [caching](https://developers.cloudflare.com/durable-objects/reference/in-memory-state/#:~:text=The%20Durable%20Object's%20storage%20has,be%20instantly%20returned%20from%20cache.). @@ -671,7 +686,7 @@ As mentioned, a risk of using delta updates is the heightened potential for miss To do so, we implemented an incrementing `batchID` which is sent by the DO as a property on every update message broadcasted to the clients. Each client tracks the `batchID` of the last message it received from the DO and when it receives a new message, it compares the previous `batchID` against the new one to determine if it missed an update. If a client misses an update, it sends a special request to the DO for a copy of the entire latest state. -[TODO insert diagram of process] + Note that sending only the delta updates missed could reduce the state recovery message size and latency, however, the DO currently does not keep a log of the updates it broadcasts. This is an area for future investigation since it would likely create greater memory and storage demands on the DO. @@ -710,7 +725,7 @@ An additional consideration was reducing unnecessary client-side rendering in Re To make subscriptions fit React’s granular rendering model, we decided to allow developers to write query functions on per component basis that indicate which keys in the local store they want to watch for updates (known as a key “watchlist”). Furthermore, by allowing subscriptions to accept a query callback, the developer can transform data in this callback before returning the value to React to be rendered. Finally, an additional mechanism we put in place to reduce unnecessary re-renders was a check to ensure the result of the query had changed since its last rerender (i.e. memoization), otherwise a new render would not be initiated. -[TODO insert diagram of process] + Though, there may be additional memory overhead with memoizing query results and maintaining a key watchlist, eliminating unnecessary UI re-renders and making subscriptions more robust leads to a snappier UI and gives developers more control over the rendering of their application, respectively. @@ -732,7 +747,7 @@ The Syncosaurus framework supports token-based authentication that allows a deve As demonstrated, Syncosaurus includes an analytics tool to easily view and analyze aggregate and single-room metrics for an application. The dashboard allows a developer to gain insights into usage and debug their application if necessary. The architecture for the analytics dashboard displays data from Cloudflare’s endpoints and visualizes it in a locally running front-end application that pulls from a custom-built GraphQL backend: -[TODO insert graphic] + # Future of Syncosaurus diff --git a/static/img/diagrams/diag-Dashboard-Architecture.svg b/static/img/diagrams/diag-Dashboard-Architecture.svg new file mode 100644 index 0000000..81aab5b --- /dev/null +++ b/static/img/diagrams/diag-Dashboard-Architecture.svg @@ -0,0 +1,21 @@ + +Cloudflare + +Express Backend + +Dashboard Client + +REST API + +GraphQL + +Apollo Server + +Both client app and express backend run locally + + + + + diff --git a/static/img/diagrams/diag-Subscriptions.svg b/static/img/diagrams/diag-Subscriptions.svg new file mode 100644 index 0000000..25d096e --- /dev/null +++ b/static/img/diagrams/diag-Subscriptions.svg @@ -0,0 +1,63 @@ + +tx.get( + +"todo3" + +) + +tx.get( + +"todo2" + +) + +tx.get( + +"todo1" + +) + +tx.scan( + +"todo" + +) + +All Todos + +Nav + +Header + +Todo 3 + +Todo 2 + +Todo 1 + + + +Syncosaurus + +React Application + +Subscriptions + +} + + + +{ + + + +"todo1": + +"todo2": + +"todo3": + + + diff --git a/static/img/diagrams/diag-cloudflare-architecture.svg b/static/img/diagrams/diag-cloudflare-architecture.svg new file mode 100644 index 0000000..bd6713c --- /dev/null +++ b/static/img/diagrams/diag-cloudflare-architecture.svg @@ -0,0 +1,39 @@ + +Room 1 + + + +Transactional + +Storage   + +Worker + + + +Room 2 + +Transactional + +Storage   + +Worker + + + +Individual Room + +persistent storage + + + +Rooms maintain all + +Websockets + +Workers for routing + + + diff --git a/static/img/diagrams/diag-cloudflare-connection.svg b/static/img/diagrams/diag-cloudflare-connection.svg new file mode 100644 index 0000000..68823fe --- /dev/null +++ b/static/img/diagrams/diag-cloudflare-connection.svg @@ -0,0 +1,35 @@ + +maintain all + +Websockets + +for a room + + + +process wss + + +http requests + +Worker + +Durable + +Object + +3. bi-directional WebSocket conn. + + forwarded to Room + + + +2. wss request + +1. wss request + +Client + + + diff --git a/static/img/diagrams/diag-global-rooms.svg b/static/img/diagrams/diag-global-rooms.svg new file mode 100644 index 0000000..47fc212 --- /dev/null +++ b/static/img/diagrams/diag-global-rooms.svg @@ -0,0 +1,16 @@ + +Room 3 + +Room 2 + +Room 1 + + + + + diff --git a/static/img/diagrams/diag-snapshotId.svg b/static/img/diagrams/diag-snapshotId.svg new file mode 100644 index 0000000..459d0cc --- /dev/null +++ b/static/img/diagrams/diag-snapshotId.svg @@ -0,0 +1,27 @@ + +server update + +Receive + +Do nothing + + + +Send init + +request + + + +compare + +snapshotIDs   + +newID != currentID + 1 + +newID == currentID + 1 + + +