diff --git a/src/components/Diagrams/index.js b/src/components/Diagrams/index.js index 14fdf22..3c3029b 100644 --- a/src/components/Diagrams/index.js +++ b/src/components/Diagrams/index.js @@ -1,247 +1,388 @@ -export const Websockets = () => { - return +export const Centralized = () => { + return ( +
+ +
+ Centralized architecture with one server and three clients +
+
+ ) } -export const AbsoluteUpdates = () => { - return +export const P2P = () => { + return ( +
+ +
Decentralized architecture with three nodes
+
+ ) } -export const Centralized = () => { - return +export const LongPolling = () => { + return ( +
+ +
+ Client sends request to server that is kept open by server until update + is available +
+
+ ) } -export const ConflictEdits = () => { - return +export const Websockets = () => { + return ( +
+ +
+ Client sends websocket request to server which upgrades connection +
+
+ ) } -export const CRDT = () => { - return +export const WhiteboardDemo = () => { + return ( +
+ +
+
+ ) } -export const DeltaUpdate = () => { - return +export const WhiteboardDemoLatency = () => { + return ( +
+ +
+
+ ) } -export const LongPolling = () => { - return +export const OptimisticUI = () => { + return ( +
+ +
+
+ ) } export const NewClientConnection = () => { - return + return ( +
+ +
+
+ ) } -export const ONMessaging = () => { - return +export const WhiteboardConflict = () => { + return ( +
+ +
+
+ ) } -export const ON2Messaging = () => { - return +export const RelativeUpdate = () => { + return ( +
+ +
Notice how the changes are cumulative
+
+ ) } -export const OptimisticUI = () => { - return +export const AbsoluteUpdates = () => { + return ( +
+ +
Notice how the changes overwrite each other
+
+ ) } -export const OT = () => { - return +export const NoConflictEdits = () => { + return ( +
+ +
Notice how the changes are combined
+
+ ) } -export const P2P = () => { - return +export const ConflictEdits = () => { + return ( +
+ +
Notice how one change is selected by the server
+
+ ) } -export const RelativeUpdate = () => { - return +export const SyncingModel1 = () => { + return ( +
+ +
+
+ ) } -export const StateUpdates = () => { - return +export const SyncingModel3 = () => { + return ( +
+ +
+
+ ) } -export const SyncingModel1 = () => { - return
- -
+export const SyncingModel4 = () => { + return ( +
+ +
+
+ ) } -export const SyncingModel2 = () => { - return
- -
+export const GlobalRooms = () => { + return ( +
+ +
Multiple rooms with different clients
+
+ ) } -export const SyncingModel3 = () => { - return
- -
+export const CloudflareArchitecture = () => { + return ( +
+ +
+
+ ) } -export const SyncingModel4 = () => { - return
- -
+export const StateUpdates = () => { + return ( +
+ +
+
+ ) } -export const TimeDrivenMessages = () => { - return +export const DeltaUpdate = () => { + return ( +
+ +
+
+ ) } -export const WhiteboardConflict = () => { - return +export const SnapshotID = () => { + return ( +
+ +
+
+ ) } -export const WhiteboardDemo = () => { - return +export const ON2Messaging = () => { + return ( +
+ +
+
+ ) } -export const WhiteboardDemoLatency = () => { - return +export const TimeDrivenMessages = () => { + return ( +
+ +
+
+ ) } -export const NoConflictEdits = () => { - return +export const ONMessaging = () => { + return ( +
+ +
+
+ ) } export const Subscriptions = () => { - return + return ( +
+ +
+
+ ) } export const DashboardArchitecture = () => { - return + return ( +
+ +
+
+ ) } -export const CloudflareArchitecture = () => { - return +/*------------------------------------------*/ + +export const CRDT = () => { + return ( + + ) } -export const GlobalRooms = () => { - return +export const OT = () => { + return ( + + ) } -export const SnapshotID = () => { - return +export const SyncingModel2 = () => { + return ( +
+ +
+ ) } export const Template = () => { - return -} \ No newline at end of file + return ( + + ) +} diff --git a/src/components/TodoList/TodoList.js b/src/components/TodoList/TodoList.js index 0e7938b..9cc80a2 100644 --- a/src/components/TodoList/TodoList.js +++ b/src/components/TodoList/TodoList.js @@ -29,7 +29,7 @@ export default function TodoList() { } if (windowWidth < 1094) { return ( -
+
+
diff --git a/src/css/custom.css b/src/css/custom.css index 1885ad6..e0c1e44 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -83,7 +83,7 @@ .footer__title { color: #1d3370; font-weight: 800; - font-size: 1.10rem; + font-size: 1.1rem; } .navbar { @@ -138,7 +138,6 @@ align-items: center; align-self: center; width: 99.5%; - } .col--2 { @@ -149,3 +148,14 @@ --ifm-col-width: calc(60%); margin-right: 15%; } + +figure { + text-align: center; +} + +figcaption { + font-size: 14px; + color: #7c7995; + text-align: center; + line-height: 1.75rem; +} diff --git a/src/pages/case-study.mdx b/src/pages/case-study.mdx index 59309f5..848d78f 100644 --- a/src/pages/case-study.mdx +++ b/src/pages/case-study.mdx @@ -41,6 +41,8 @@ import { SnapshotID, } from '@site/src/components/Diagrams/index.js' +# Case Study + ## Overview Syncosaurus is a React-Javascript developer framework for rapidly building browser-based real-time, collaborative web applications backed by Cloudflare Workers and Durable Objects. @@ -62,7 +64,9 @@ Broadly, real-time collaboration is a term used to describe software or technolo With the rise of [remote work and distributed teams](https://medium.com/@anupamr/distributed-teams-are-the-new-cloud-for-startups-14240a9822d7), apps like these [are becoming critical in the modern work environment](https://northzone.com/2023/06/20/perspectives-the-rise-of-multiplayer-software/). Well-known examples include Google Docs, Figma, and Notion among others: - +
+ +
While [a broader definition](https://www.microsoft.com/en-us/microsoft-365/business-insights-ideas/resources/real-time-collaboration-what-it-is-and-how-it-helps-your-business) of real-time collaboration applications can include video conferencing and messaging software, those types of applications have different technical requirements and will be excluded from the scope of this case study. @@ -73,13 +77,17 @@ At a fundamental level when multiple users are “collaborating” on the same a - Centralized - Decentralized +#### Centralized + +Centralized includes the client-server architecture where client machines send resource requests to a backend server. In the client-server architecture, the server is generally [authoritative](https://medium.com/mighty-bear-games/what-are-server-authoritative-realtime-games-e2463db534d1), meaning it is the source of truth. + -**Centralized** includes the client-server architecture where client machines send resource requests to a backend server. In the client-server architecture, the server is generally [authoritative](https://medium.com/mighty-bear-games/what-are-server-authoritative-realtime-games-e2463db534d1), meaning it is the source of truth. +#### Decentralized - +Decentralized such as the peer-to-peer architecture where every machine (known as a node) is treated equally and responsible for sending and responding to requests for resources. In the peer-to-peer architecture, every machine has equal authority and a consensus must usually be reached to determine the truth. -**Decentralized** such as the peer-to-peer architecture where every machine (known as a node) is treated equally and responsible for sending and responding to requests for resources. In the peer-to-peer architecture, every machine has equal authority and a consensus must usually be reached to determine the truth. + Though [it is possible](https://www.tag1consulting.com/blog/yjs-webrtc-part-1) to build decentralized real-time collaboration web apps, decentralized architectures are notoriously complex. Instead [many existing real-time collaborative apps use a form of the client-server architecture](https://www.figma.com/blog/how-figmas-multiplayer-technology-works/) due to their comparative [efficiency and ease of maintenance](https://fwx.finance/learn/article/centralized-app-vs-dapp). Therefore, we will assume that any application considering Syncosaurus will be using a form of centralized architecture. @@ -103,7 +111,9 @@ The need for real-time depends on the web application and generally apps that mi To illustrate this need and the impact latency can have on a user’s experience, drag the blue slider below and notice how higher latencies cause a noticeable delay in updating the green slider’s position to match: - +
+ +
#### Achieving Real-time Communication @@ -117,7 +127,9 @@ Note that using [streams over HTTP/2](https://getstream.io/blog/communication-pr ##### Long Polling -Long polling is a technique to emulate server push communications via normal HTTP requests. Long polling works like this: +Long polling is a technique to emulate server push communications via normal HTTP requests. + +Long polling works like this: @@ -125,7 +137,9 @@ Although every browser supports long polling, it has high latency compared to ot ##### WebSockets -[WebSockets](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket?retiredLocale=de) is an application layer protocol that provides a [full-duplex](https://www.comms-express.com/infozone/article/half-full-duplex/) communication channel over a single, long-lived connection between the client and server. This means that similar to a phone call, the connection from the client to the server will stay open as long the network is not interrupted and neither the client nor the server actively terminates it. This open connection enables clients and servers to freely exchange data without the overhead of the HTTP request-response cycle, but because it is built on top of [TCP](https://www.ibm.com/docs/ro/aix/7.1?topic=protocols-transmission-control-protocol), it still has guaranteed in-order message delivery. WebSockets work like this: +[WebSockets](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket?retiredLocale=de) is an application layer protocol that provides a [full-duplex](https://www.comms-express.com/infozone/article/half-full-duplex/) communication channel over a single, long-lived connection between the client and server. This means that similar to a phone call, the connection from the client to the server will stay open as long the network is not interrupted and neither the client nor the server actively terminates it. This open connection enables clients and servers to freely exchange data without the overhead of the HTTP request-response cycle, but because it is built on top of [TCP](https://www.ibm.com/docs/ro/aix/7.1?topic=protocols-transmission-control-protocol), it still has guaranteed in-order message delivery. + +WebSockets work like this: @@ -187,7 +201,7 @@ Optimistic UI works like this: -An important implication of implementing optimistic UI is that each client has a local replica of the state that must be kept in sync with the server. Thus using it in our whiteboard app means that it now has distributed state. [Figma uses this same technique](https://www.figma.com/blog/how-figmas-multiplayer-technology-works/) - when a client opens a Figma design file, a copy of the document is sent from the server to the client and it must be kept in sync: +An important implication of implementing optimistic UI is that each client has a local replica of the state that must be kept in sync with the server. Thus using it in our whiteboard app means that it now has distributed state. [Figma uses this same technique](https://www.figma.com/blog/how-figmas-multiplayer-technology-works/) - when a client opens a Figma design file, a copy of the document is sent from the server to the client and it must be kept in sync going forward: @@ -317,7 +331,9 @@ Syncosaurus is a React Javascript client-side SDK with full ready-to-be-deployed Similar to commercial solutions like Liveblocks and Reflect, Syncosaurus exposes a client-side SDK while abstracting the backend logic and handling much of the deployment - the only thing a developer has to do to get the backend deployed is sign up for a Cloudflare account and use our CLI to deploy. However, unlike the commercial solutions, the Syncosaurus framework is free to use and open source so a developer can alter the default backend code if they choose. - +
+ +
## Using Syncosaurus @@ -325,20 +341,20 @@ To better understand Syncosaurus we will explore the syncing model and then walk ### Syncing Model -As mentioned, Syncosaurus uses a real-time syncing model with transactional conflict resolution to keep state consistent across multiple clients: +As mentioned, Syncosaurus uses a real-time syncing model with transactional conflict resolution to keep state consistent across multiple clients. + +When a client makes a change to state, it is immediately applied to the client’s locally and the client places a copy of the intent of the change (i.e. a state mutation) in a pending queue. -- When a client makes a change to state, it is immediately applied to the client’s locally and the client places a copy of the intent of the change (i.e. a state mutation) in a pending queue. +Next, the mutation is sent across the WebSocket connection to the Syncosaurus server. Once the change is made to the authoritative state, the server sends a confirmation update to the client, which the client then uses to update its state to match the server and remove the pending state mutation from the queue. -- Next, the mutation is sent across the WebSocket connection to the Syncosaurus server. Once the change is made to the authoritative state, the server sends a confirmation update to the client, which the client then uses to update its state to match the server and remove the pending state mutation from the queue. +When more than one client is connected to a given room, any mutations from one client are broadcast to all other clients by the server. Because an update from the server is authoritative, all client states are guaranteed to converge. -- When more than one client is connected to a given room, any mutations from one client are broadcast to all other clients by the server. Because an update from the server is authoritative, all client states are guaranteed to converge. - ### Development Now that we understand the fundamental syncing model of Syncosaurus, we will discuss how to use the framework. @@ -518,7 +534,9 @@ We have prepared a simple todo list application to illustrate the mutator and cl Go ahead and enter some todos into Client 1 and watch them appear on Client 2 after a simulated latency of 1 second: - +
+ +
### Deployment @@ -536,12 +554,15 @@ const synco = new Syncosaurus({ After your application is deployed, you can monitor its usage and get help debugging via the analytics dashboard and a live tail log. -The dashboard includes hourly time-series metrics related to errors and usage for each room so you can verify users are able to access and use the application in a bug-free manner +The dashboard includes hourly time-series metrics related to errors and usage for each room so you can verify users are able to access and use your application in a bug-free manner. -dashboard screenshot +
+ dashboard screenshot +
For even more detail, a tail logging session for your deployed backend can be started by running `npx syncosaurus tail` @@ -583,10 +604,13 @@ When choosing a provider to build on, we narrowed in on a couple of options. First, we could have built our serverless edge infrastructure using a combination of AWS Lambda@Edge, Amazon API Gateway, and a persistent storage mechanism like AWS Dynamo DB: -AWS Architecture Diagram +
+ AWS Architecture Diagram +
While this is a valid approach, it does involve provisioning and connecting several components which can increase the complexity of deployment and maintenance. diff --git a/static/img/diagrams/AWS-Lambda-Diagram.png b/static/img/diagrams/AWS-Lambda-Diagram.png index f823c96..4b8307d 100644 Binary files a/static/img/diagrams/AWS-Lambda-Diagram.png and b/static/img/diagrams/AWS-Lambda-Diagram.png differ diff --git a/static/img/favicon.ico b/static/img/favicon.ico deleted file mode 100644 index c180f25..0000000 Binary files a/static/img/favicon.ico and /dev/null differ