|
| 1 | +# Using A Synchronizer |
| 2 | + |
| 3 | +The synchronizer module framework lets you synchronize MergeableStore data |
| 4 | +between different devices, systems, or subsystems. |
| 5 | + |
| 6 | +It contains the Synchronizer |
| 7 | +interface, describing objects which can be used to synchronize a MergeableStore. |
| 8 | + |
| 9 | +Under the covers, a Synchronizer is actually a very specialized type of |
| 10 | +Persister that _only_ supports MergeableStore objects, and which has a startSync |
| 11 | +method and a stopSync method. |
| 12 | + |
| 13 | +## Types Of Synchronizer |
| 14 | + |
| 15 | +In TinyBase v5.0, there are three types of Synchronizer: |
| 16 | + |
| 17 | +- The WsSynchronizer uses WebSockets to communicate between different systems. |
| 18 | +- The BroadcastChannelSynchronizer uses the browser's BroadcastChannel API to |
| 19 | + communicate between different tabs and workers. |
| 20 | +- The LocalSynchronizer demonstrates synchronization in memory on a single local |
| 21 | + system. |
| 22 | + |
| 23 | +Of course it is also possible to create custom Synchronizer objects if you have |
| 24 | +a transmission medium that allows the synchronization messages to be sent |
| 25 | +reliably between clients. |
| 26 | + |
| 27 | +## Synchronizing with WebSockets |
| 28 | + |
| 29 | +A common pattern for synchronizing over the web is to use WebSockets. This |
| 30 | +allows multiple clients to pass lightweight messages to each other, facilitating |
| 31 | +efficient synchronization. |
| 32 | + |
| 33 | +One thing to understand is that this set up will typically require a server. |
| 34 | +This can be a relatively 'thin server' - it does not need to store data of its |
| 35 | +own - but is needed to keep a list of clients that are being synchronized |
| 36 | +together, and route and broadcast messages between the clients. |
| 37 | + |
| 38 | +TinyBase includes a simple implementation of such a server. You simply need to |
| 39 | +create it, instantiated with a configured WebSocketServer object from the `ws` |
| 40 | +package: |
| 41 | + |
| 42 | +```js |
| 43 | +// On a server machine: |
| 44 | +import {WebSocketServer} from 'ws'; |
| 45 | +import {createWsServer} from 'tinybase/synchronizers/synchronizer-ws-server'; |
| 46 | +const server = createWsServer(new WebSocketServer({port: 8048})); |
| 47 | +``` |
| 48 | + |
| 49 | +This sets up a WsServer object, listening on port 8048. |
| 50 | + |
| 51 | +Each client then needs to create a WsSynchronizer object, instantiated with the |
| 52 | +MergeableStore being synchronized, and a WebSocket configured to connect to the |
| 53 | +aforementioned server: |
| 54 | + |
| 55 | +```js |
| 56 | +// On the first client machine: |
| 57 | +import {WebSocket} from 'ws'; |
| 58 | +import {createMergeableStore} from 'tinybase'; |
| 59 | +import {createWsSynchronizer} from 'tinybase/synchronizers/synchronizer-ws-client'; |
| 60 | + |
| 61 | +const clientStore1 = createMergeableStore(); |
| 62 | +const clientSynchronizer1 = await createWsSynchronizer( |
| 63 | + clientStore1, |
| 64 | + new WebSocket('ws://localhost:8048'), |
| 65 | +); |
| 66 | +``` |
| 67 | + |
| 68 | +This WsSynchronizer can then be started, and data manipulated as normal: |
| 69 | + |
| 70 | +```js |
| 71 | +await clientSynchronizer1.startSync(); |
| 72 | +clientStore1.setCell('pets', 'fido', 'species', 'dog'); |
| 73 | +// ... |
| 74 | +``` |
| 75 | + |
| 76 | +Meanwhile, on another client, an empty MergeableStore and another WsSynchronizer |
| 77 | +can be created and started, connecting to the same server. |
| 78 | + |
| 79 | +```js |
| 80 | +// On the second client machine: |
| 81 | +const clientStore2 = createMergeableStore(); |
| 82 | +const clientSynchronizer2 = await createWsSynchronizer( |
| 83 | + clientStore2, |
| 84 | + new WebSocket('ws://localhost:8048'), |
| 85 | +); |
| 86 | +await clientSynchronizer2.startSync(); |
| 87 | +``` |
| 88 | + |
| 89 | +Once the synchronization is started, the server will broker the messages being |
| 90 | +passed back and forward between the two clients, and the data will be |
| 91 | +synchronized. The empty second MergeableStore will be populated with the data |
| 92 | +from the first: |
| 93 | + |
| 94 | +```js |
| 95 | +// ... |
| 96 | +console.log(clientStore2.getTables()); |
| 97 | +// -> {pets: {fido: {species: 'dog'}}} |
| 98 | +``` |
| 99 | + |
| 100 | +And of course the synchronization is bi-directional: |
| 101 | + |
| 102 | +```js |
| 103 | +clientStore2.setCell('pets', 'felix', 'species', 'cat'); |
| 104 | +console.log(clientStore2.getTables()); |
| 105 | +// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}} |
| 106 | +``` |
| 107 | + |
| 108 | +```js |
| 109 | +// ... |
| 110 | +console.log(clientStore1.getTables()); |
| 111 | +// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}} |
| 112 | +``` |
| 113 | + |
| 114 | +When done, it's important to destroy a WsSynchronizer to close and tidy up the |
| 115 | +client WebSockets: |
| 116 | + |
| 117 | +```js |
| 118 | +clientSynchronizer1.destroy(); |
| 119 | +``` |
| 120 | + |
| 121 | +```js |
| 122 | +clientSynchronizer2.destroy(); |
| 123 | +``` |
| 124 | + |
| 125 | +And, if shut down, the WsServer should also be explicitly destroyed to close its |
| 126 | +listeners: |
| 127 | + |
| 128 | +```js |
| 129 | +server.destroy(); |
| 130 | +``` |
| 131 | + |
| 132 | +## Synchronizing over the browser BroadcastChannel |
| 133 | + |
| 134 | +There may be situations where you need to synchronize data between different |
| 135 | +parts of a browser. For example, you might have a transient in-memory |
| 136 | +MergeableStore driving your UI, but then another instance in a Service Worker |
| 137 | +that can be persisted to (say) IndexedDB or another medium. |
| 138 | + |
| 139 | +To facilitate keeping these in sync, the BroadcastChannelSynchronizer lets you |
| 140 | +synchronize over the browser's BroadcastChannel API, common to each browser |
| 141 | +sub-system. You simply need to provide a distinguishing channel name that can be |
| 142 | +used to identify what the two parts should be using to send and receive |
| 143 | +messages. |
| 144 | + |
| 145 | +For example, in the UI part of your app: |
| 146 | + |
| 147 | +```js |
| 148 | +import {createBroadcastChannelSynchronizer} from 'tinybase/synchronizers/synchronizer-broadcast-channel'; |
| 149 | + |
| 150 | +const frontStore = createMergeableStore(); |
| 151 | +const frontSynchronizer = createBroadcastChannelSynchronizer( |
| 152 | + frontStore, |
| 153 | + 'syncChannel', |
| 154 | +); |
| 155 | +await frontSynchronizer.startSync(); |
| 156 | +``` |
| 157 | + |
| 158 | +And then in the service worker: |
| 159 | + |
| 160 | +```js |
| 161 | +const backStore = createMergeableStore(); |
| 162 | +const backSynchronizer = createBroadcastChannelSynchronizer( |
| 163 | + backStore, |
| 164 | + 'syncChannel', |
| 165 | +); |
| 166 | +await backSynchronizer.startSync(); |
| 167 | +``` |
| 168 | + |
| 169 | +Since they both share the `syncChannel` channel name, the data of the two is now |
| 170 | +synchronized: |
| 171 | + |
| 172 | +```js |
| 173 | +frontStore.setCell('pets', 'fido', 'species', 'dog'); |
| 174 | +``` |
| 175 | + |
| 176 | +```js |
| 177 | +// ... |
| 178 | +console.log(backStore.getTables()); |
| 179 | +// -> {pets: {fido: {species: 'dog'}}} |
| 180 | +``` |
| 181 | + |
| 182 | +And so on! |
| 183 | + |
| 184 | +When finished, these synchronizers should also be explicitly destroyed to ensure |
| 185 | +the channel listeners are cleaned up: |
| 186 | + |
| 187 | +```js |
| 188 | +frontSynchronizer.destroy(); |
| 189 | +``` |
| 190 | + |
| 191 | +```js |
| 192 | +backSynchronizer.destroy(); |
| 193 | +``` |
| 194 | + |
| 195 | +## Wrapping Up |
| 196 | + |
| 197 | +The Synchronizer interface provides an easy way to keep multiple TinyBase |
| 198 | +MergeableStores in sync. The WebSocket and BroadcastChannel options above allow |
| 199 | +for numerous interesting and powerful app architectures - and they are not |
| 200 | +sufficient, consider exploring the createCustomSynchronizer function to develop |
| 201 | +your own! |
| 202 | + |
| 203 | +We now move on to ways in which TinyBase can be used like a database, starting |
| 204 | +with the Using Metrics guide. |
0 commit comments