<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ditto</title>
    <description>The latest articles on DEV Community by Ditto (@dittolive).</description>
    <link>https://dev.to/dittolive</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F4430%2F563b92ac-6abf-44cb-95f9-e2441b480dd4.png</url>
      <title>DEV Community: Ditto</title>
      <link>https://dev.to/dittolive</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dittolive"/>
    <language>en</language>
    <item>
      <title>The Future of the Cloud? Make it Optional</title>
      <dc:creator>Ryan Ratner</dc:creator>
      <pubDate>Wed, 13 Jul 2022 16:40:00 +0000</pubDate>
      <link>https://dev.to/dittolive/the-future-of-the-cloud-make-it-optional-1gcf</link>
      <guid>https://dev.to/dittolive/the-future-of-the-cloud-make-it-optional-1gcf</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0a4S6_e3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mmm6ms0z9hjdvu61z7a3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0a4S6_e3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mmm6ms0z9hjdvu61z7a3.png" alt="deskless worker with no internet" width="480" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Today, most apps are &lt;em&gt;cloud-only&lt;/em&gt;. Data must travel halfway around the world to a remote data center just for it to arrive at a device in the same room. These apps become entirely unusable if their data connection is slow or a Wi-Fi router breaks. Cloud-only apps have made it difficult for the "deskless workforce" to get their work done. They require database access on mobile devices in the field, where crucial on-the-ground operations are happening, but their cloud-only applications do not function 100% of the time. Operations halt while employees wait for a mobile phone to reconnect. It isn't a great user experience; it costs businesses money and can even put people in life-threatening situations when real-time data is needed for quick decision-making.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lSNcMbVs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dw6axcrpwcadx50akb6r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lSNcMbVs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dw6axcrpwcadx50akb6r.png" alt="Cloud Outage" width="880" height="463"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  From Cloud-Only to Cloud-Optional
&lt;/h2&gt;

&lt;p&gt;Despite these problems, most applications today are cloud-only. Why? If you want to send your friend a picture of a cat, you can use AirDrop, which sends the data directly between the two devices, &lt;em&gt;peer-to-peer&lt;/em&gt;. So why don't all apps send other kinds of data directly, &lt;em&gt;peer-to-peer&lt;/em&gt;, between devices? Why isn't the cloud optional?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9B_SbCGz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dnga5qopyttpqegvq5pp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9B_SbCGz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dnga5qopyttpqegvq5pp.png" alt="Why can't apps send data directly?" width="880" height="286"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The answer is that it's simply easier to build cloud-only applications. Over the past few decades, investors have funneled billions of dollars into cloud-only databases and tools. Because of this, developers don't have to think about TCP/IP networking, database&lt;br&gt;
partitioning, or on-disk compression whenever they want to update a field in a database table. For years, this existing toolkit of cloud-only tools has made cloud-only applications the fastest way to deploy collaborative software.&lt;/p&gt;

&lt;h2&gt;
  
  
  Peer-to-peer is not new, but it is still hard to use
&lt;/h2&gt;

&lt;p&gt;There are scalable peer-to-peer protocols for data that doesn't change, such as AirDropping cat pictures or torrenting movies and music – BitTorrent accounts for 27% of all upstream Internet traffic&lt;sup id="fnref1"&gt;1&lt;/sup&gt;. But, developers need the reliability of a database for times when that data starts to change at the speed of Slack and requires the precision of an airplane safety checklist. Unfortunately, few engineers in the world can build a peer-to-peer database. It's a new software engineering paradigm that needs more education, tool development, and user experience improvements. Because of this, we haven't had a cloud-optional, peer-to-peer database. &lt;em&gt;Until now.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Ideally, a peer-to-peer database needs to be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;User-friendly.&lt;/strong&gt; Developers are users too! Instead of sending data to a remote server, the application needs to write data to its local database first in the form of &lt;em&gt;changes&lt;/em&gt;, then listen for changes from other devices, and recombine them on the fly. Ditto provides an API on top of these details so developers can focus on their business logic instead of synchronization logic.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cloud-optional.&lt;/strong&gt; Devices can go into dead zones, routers can crash, or cloud services can go down. All devices must see the same query results given the same set of changes, even if the changes arrive in a different order. Ditto's Conflict-Free Replicated Data Types&lt;br&gt;
&lt;a href="https://docs.ditto.live/common/how-it-works/crdt/"&gt;(CRDTs)&lt;/a&gt; provide a consistent view of the data for every device. These data structures are still very much a new topic in computer science research.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Partitioned.&lt;/strong&gt; Mesh networks can generate a tremendous amount of data that can overwhelm small devices if each node aggressively tries to sync every piece of data. Ditto provides a &lt;a href="http://www.ditto.live/products/platform"&gt;Big Peer and a Small Peer SDK&lt;/a&gt; with different replication strategies. The Small Peer is selfish, which means it only synchronizes data it explicitly requests, giving developers complete control over storage and bandwidth usage. The Big Peer is greedy, which means it synchronizes as much as possible.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3DFyF-C5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vgkem54sa0h1hqry0z91.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3DFyF-C5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vgkem54sa0h1hqry0z91.png" alt="Ditto peer-to-peer cloud architecture" width="880" height="842"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Ad-hoc.&lt;/strong&gt; Devices may join and leave the mesh at any time. Despite this churn, all devices should still see and have input on the same data: the same "source of truth." Routing messages between &lt;em&gt;appropriate&lt;/em&gt; nodes becomes a mathematical challenge as the mesh network topology changes over time. Ditto's &lt;a href="https://www.ditto.live/blog/testing-crdts-in-rust-from-theory-to-practice"&gt;novel delta state CRDT&lt;/a&gt; is great for ad-hoc mesh networks because they are flexible and do not rely on having the full history of a database table to write or read the latest value.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Forward-Compatible.&lt;/strong&gt; Since devices update at different times, they need to account for incoming data with different schema. For example, if a device is offline and therefore outdated, it should still be able to read new data and sync. Ditto is causally consistent, providing a reliable order of changes that can be inspected, including incorporating metadata about schema changes over time.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--psU4_2sC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s80htfm2deje6z7qzeg4.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--psU4_2sC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s80htfm2deje6z7qzeg4.jpg" alt="Ditto forward compatibility" width="880" height="471"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A shared set of tools
&lt;/h2&gt;

&lt;p&gt;These challenges are straightforward to explain but bring up a sizable, entangled web of discussions. There are many different ways to design and implement caching, data synchronization, querying, and distributed architecture.&lt;/p&gt;

&lt;p&gt;It is a lot of work for development teams to create a reliable peer-to-peer database that syncs data in a partially connected mesh. Organizations either can't hire the talent to complete the job or don't see the financial benefit of investing in building it themselves. It is a more efficient use of resources to support a database that many different companies use so that each individual company doesn't need to invest in building and maintaining their own cloud-optional database.&lt;/p&gt;

&lt;p&gt;With Ditto, developers have a complete end-to-end cloud-optional solution that combines the best of cloud software with the best of peer-to-peer software. On the surface, the shift from cloud-only to cloud-&lt;em&gt;optional&lt;/em&gt; seems subtle. But this is a more fundamental paradigm shift and provides tremendous business opportunities. Ditto is setting the standard as one of the first CRDT-based peer-to-peer databases that turn the existing plethora of mobile devices into a cloud-optional intelligent edge platform.&lt;/p&gt;

&lt;p&gt;If you are a developer and want to make your cloud optional,&lt;br&gt;
&lt;a href="https://portal.ditto.live/create-account"&gt;get started&lt;/a&gt; today for free. &lt;a href="https://jobs.ashbyhq.com/ditto"&gt;We're hiring&lt;/a&gt;, so look at our available listings and see if you think Ditto might be a good fit for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Footnotes
&lt;/h2&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;&lt;a href="https://www.prnewswire.com/news-releases/sandvine-releases-2019-global-internet-phenomena-report-300914962.html"&gt;The Global Internet Phenomena Report&lt;/a&gt;, Sandvine; 2019. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>cloud</category>
      <category>discuss</category>
      <category>architecture</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Testing CRDTs in Rust, From Theory to Practice</title>
      <dc:creator>Ryan Ratner</dc:creator>
      <pubDate>Wed, 06 Apr 2022 16:24:24 +0000</pubDate>
      <link>https://dev.to/dittolive/testing-crdts-in-rust-from-theory-to-practice-3l39</link>
      <guid>https://dev.to/dittolive/testing-crdts-in-rust-from-theory-to-practice-3l39</guid>
      <description>&lt;p&gt;Ditto is a database that puts theory to practice. Ditto's peer-to-peer&lt;br&gt;
data-structures based on CRDTs are deployed into real-world production&lt;br&gt;
use cases. As we make improvements to the underlying data-structures,&lt;br&gt;
we leverage stateful property-based testing to ensure that Ditto's&lt;br&gt;
core data-structures maintain their robustness and stability. In this&lt;br&gt;
article, we show how this type of testing helped reveal a surprising&lt;br&gt;
problem in an academic paper's optimized CRDT.&lt;/p&gt;
&lt;h2&gt;
  
  
  What Are CRDTs?
&lt;/h2&gt;

&lt;p&gt;CRDTs are data-structures that can be used in distributed systems that&lt;br&gt;
allow relaxed consistency models. They make handling conflicting&lt;br&gt;
updates easier for programmers, and enable the development of&lt;br&gt;
peer-to-peer and&lt;br&gt;
&lt;a href="https://www.inkandswitch.com/local-first/"&gt;"local-first"&lt;/a&gt;&lt;br&gt;
applications. They're the primary data-structure that developers work&lt;br&gt;
with in Ditto's Document Database. CRDTs allow Ditto to integrate&lt;br&gt;
concurrent changes from clients into a single, deterministic, and&lt;br&gt;
meaningful value.&lt;/p&gt;

&lt;p&gt;Over time the words that lend their initials to form the acronym CRDT&lt;br&gt;
have often changed, it is usually accepted that CRDT stands for&lt;br&gt;
Conflict-Free Replicated Datastructure, or in our case Convergent&lt;br&gt;
Replicated Datastructures.&lt;/p&gt;

&lt;p&gt;There has been a lot written about CRDTs, but a good starting place to&lt;br&gt;
learn more is &lt;a href="https://crdt.tech/"&gt;crdt.tech&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ditto uses a &lt;a href="https://docs.ditto.live/how-it-works/crdt"&gt;State-based CRDT&lt;/a&gt;. State-based CRDTs use metadata inside&lt;br&gt;
the data-structure to track causality and determine a value. We&lt;br&gt;
developed a novel type of Delta-State CRDTs for Ditto, which we will&lt;br&gt;
explain in more detail at a later date (stay tuned!) where the&lt;br&gt;
differences between peers is calculated and sent over the network,&lt;br&gt;
rather than sending the full state.&lt;/p&gt;
&lt;h2&gt;
  
  
  Implementing A New CRDT
&lt;/h2&gt;

&lt;p&gt;Ditto's database represents a document as a CRDT map with additional&lt;br&gt;
nested CRDTs for values. Today, the Ditto Document currently only&lt;br&gt;
supports Remove Wins behavior. My current task at Ditto is to create a&lt;br&gt;
new CRDT Map that supports both Add Wins and Remove Wins&lt;br&gt;
behavior. This is a novel data-type which allows the developer to&lt;br&gt;
choose the behavior when performing a remove operation.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Throughout this article I write mainly about Sets, as they are simpler&lt;br&gt;
to describe and reason about, but what is discussed can also be extended to&lt;br&gt;
Maps.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Add Wins and Remove Wins are choices in CRDT Set behavior. Given&lt;br&gt;
concurrent updates to the same element in a Set, where one peer&lt;br&gt;
inserts the element and another removes it, you have a choice: either&lt;br&gt;
the Add Wins and the inserted element is in the Set, or the Remove&lt;br&gt;
Wins and the element is not in the Set. In order to enable Remove Wins&lt;br&gt;
behaviour, State-based CRDT sets tend to use some metadata, called a&lt;br&gt;
tombstone, that marks an element as removed. To understand the&lt;br&gt;
difference between Add Wins and Remove Wins, check out the graphics&lt;br&gt;
below.&lt;/p&gt;
&lt;h2&gt;
  
  
  Add-Wins Behavior
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_IXvxgqJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0abu4feqjhw3fimkv566.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_IXvxgqJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0abu4feqjhw3fimkv566.png" alt="What is Add Wins CRDT set behavior?" width="880" height="402"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Remove-Wins Behavior
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--g7Txy9zi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/asrm0pr5wdz16eyzy19i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--g7Txy9zi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/asrm0pr5wdz16eyzy19i.png" alt="What is Remove Wins CRDT set behavior?" width="880" height="401"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ditto observes this situation often from its customers in&lt;br&gt;
the airline industry. Before takeoff, two flight attendants&lt;br&gt;
with tablets or phones, in this case peer 1 and peer 2,&lt;br&gt;
may download backend data before syncing with each other.&lt;br&gt;
When these two disconnected flight attendants make concurrent,&lt;br&gt;
offline edits to the database before syncing, as shown in the&lt;br&gt;
graphics above, merging of the two devices results in&lt;br&gt;
different behavior depending on the CRDT set behavior.&lt;/p&gt;

&lt;p&gt;Remove Wins behavior can be surprising for some users, however. In general it&lt;br&gt;
makes sense, as it does what it says: given any pair of concurrent add&lt;br&gt;
and remove operations, the remove will win. However, with the&lt;br&gt;
following sequence enacted concurrently by two peers, everyone is&lt;br&gt;
surprised by the result:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Peer 1 adds element X to the set&lt;/li&gt;
&lt;li&gt;Peer 1 removes element X from the set&lt;/li&gt;
&lt;li&gt;Peer 1 re-adds element X to the set&lt;/li&gt;
&lt;li&gt;Peer 2 adds element X to the set&lt;/li&gt;
&lt;li&gt;Peer 2 removes element X from the set&lt;/li&gt;
&lt;li&gt;Peer 2 re-adds element X to the set&lt;/li&gt;
&lt;li&gt;Peer 1 merges with Peer2 and vice versa&lt;/li&gt;
&lt;li&gt;X is &lt;em&gt;not&lt;/em&gt; in the set&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To explain, the remove at step 2 on Peer One is concurrent with the&lt;br&gt;
add (and re-add) on Peer Two. The remove at step 2 on Peer Two is&lt;br&gt;
concurrent with the add (add re-add) on Peer One. When the sites&lt;br&gt;
exchange data and merge into a single value the remove(s) win and the&lt;br&gt;
Set is empty.&lt;/p&gt;

&lt;p&gt;Given this peculiarity, Ditto wants to enrich the types available to&lt;br&gt;
developers and offer both Add Wins and Remove Wins behavior. However,&lt;br&gt;
designing and implementing CRDTs is a difficult task, we have an&lt;br&gt;
impossibly large set of interleaving operations and orderings to test,&lt;br&gt;
and we want to be certain that the result is deterministic across them&lt;br&gt;
all. Property-Based Testing is an essential tool in the armory.&lt;/p&gt;
&lt;h2&gt;
  
  
  What Is Property-Based Testing?
&lt;/h2&gt;

&lt;p&gt;If you have never heard of property-based testing before watch&lt;br&gt;
&lt;a href="https://www.youtube.com/watch?v=hXnS_Xjwk2Y"&gt;this&lt;/a&gt;. John Hughes is&lt;br&gt;
one of the (grand?)daddies (sorry John) of property-based testing. A&lt;br&gt;
stone cold genius, he has probably saved countless millions of&lt;br&gt;
dollars, and maybe even lives since &lt;a href="https://www.cs.tufts.edu/~nr/cs257/archive/john-hughes/quviq-testing.pdf"&gt;inventing Quickcheck with Koen&lt;br&gt;
Claessen&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are lots of descriptions out there of property-based testing. In&lt;br&gt;
general, it means generating the test cases, usually by generating&lt;br&gt;
inputs. The programmer declares the &lt;em&gt;properties&lt;/em&gt; of the thing under&lt;br&gt;
test and the library tries to generate cases that violate those&lt;br&gt;
properties. When it does find some input that violates a property, it&lt;br&gt;
will &lt;em&gt;shrink&lt;/em&gt; the input into a minimal failing case. The classic&lt;br&gt;
example is that for all generated arrays as inputs, the reverse of the&lt;br&gt;
reverse of an array is equal to the original array. This is compared&lt;br&gt;
to unit testing where you make a bunch of arrays (empty?), hand&lt;br&gt;
code the result, and say "expected == actual". Another common example&lt;br&gt;
is round tripping for serialisation/deserialisation libraries. That&lt;br&gt;
the result of serialise then deserialise for &lt;em&gt;any input&lt;/em&gt; is the same&lt;br&gt;
as the original input is a classic property.&lt;/p&gt;
&lt;h2&gt;
  
  
  Stateful Property-Based Testing
&lt;/h2&gt;

&lt;p&gt;My personal favorite way of testing complex things, like distributed&lt;br&gt;
systems or distributed data-structures, is to use EQC's Statem. This&lt;br&gt;
is a stateful property-based testing module, that allows me to model&lt;br&gt;
the system under test, generate a sequence of operations to run on&lt;br&gt;
that system, and check that the system matches the model,&lt;br&gt;
throughout. If some sequence of operations leads to a difference&lt;br&gt;
between the model and the system under test (hereafter SUT),&lt;br&gt;
quickcheck shrinks the operations to a minimal sequence.&lt;/p&gt;

&lt;p&gt;With data-structures (like CRDTs) this is great. We can take an&lt;br&gt;
"obviously correct", but poorly optimised implementation of a CRDT&lt;br&gt;
(like the classic &lt;a href="https://hal.inria.fr/inria-00555588/document"&gt;Observe Remove Set&lt;/a&gt;) and use it as the model, or&lt;br&gt;
specification, for a better implementation, like the &lt;a href="https://lip6.fr/Marc.Shapiro/papers/RR-8083.pdf"&gt;Optimised Add&lt;br&gt;
Wins Set&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We execute the same randomly generated set of operations on both&lt;br&gt;
data-structures, and if there exists a difference between the behavior&lt;br&gt;
of the implementations, quickcheck will tell us, and crucially SHRINK&lt;br&gt;
the list of operations to a minimum. Then it is the programmer's job&lt;br&gt;
to decide if the bug is in the model, the SUT, the test itself, or&lt;br&gt;
some combination thereof.&lt;/p&gt;

&lt;p&gt;Every time quickcheck finds a failing counter example, you save it,&lt;br&gt;
and implement it as a unit test (for regression). Since the failing case was&lt;br&gt;
generated randomly, you cannot be certain &lt;em&gt;that exact case&lt;/em&gt; will be&lt;br&gt;
generated again.&lt;/p&gt;

&lt;p&gt;It is an amazing tool. I've used it with Rustler to test Rust&lt;br&gt;
code. However, not everyone at Ditto has a licence, and not everyone&lt;br&gt;
at Ditto wants to learn Erlang. Even if I still think EQC statem is&lt;br&gt;
the gold standard, it isn't what I used for this work, but&lt;br&gt;
understanding the idea of generating a series of operations, and&lt;br&gt;
applying them to a model and a "real" implementation is all you need to&lt;br&gt;
take from this section.&lt;/p&gt;
&lt;h2&gt;
  
  
  Poor Man's Rust Statem
&lt;/h2&gt;

&lt;p&gt;A very basic attempt to copy the process of EQC statem would be to generate a&lt;br&gt;
list of operations, and execute them. This process is described in a few places,&lt;br&gt;
For example Tyler Neely's &lt;a href="https://medium.com/@tylerneely/reliable-systems-series-model-based-property-testing-e89a433b360"&gt;post&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In this case I use Rust Proptest to generate a Vector of operations to run on a&lt;br&gt;
CRDT set. &lt;sup id="fnref1"&gt;1&lt;/sup&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  A Model (Specification) and a SUT
&lt;/h2&gt;

&lt;p&gt;As a first step to supporting multiple behaviors in Ditto's document&lt;br&gt;
I looked for prior art or existing literature and found the work of&lt;br&gt;
André Rijo et al, specifically the OAR-Set in Rijo's 2018&lt;br&gt;
&lt;a href="https://run.unl.pt/handle/10362/55171"&gt;dissertation&lt;/a&gt; and the&lt;br&gt;
optimised version in &lt;a href="https://arxiv.org/abs/1903.03487"&gt;this paper&lt;/a&gt;&lt;br&gt;
(also from 2018).&lt;/p&gt;

&lt;p&gt;My idea was to use the Rijo OAR-Set as a model for the Map I was&lt;br&gt;
making.&lt;/p&gt;
&lt;h3&gt;
  
  
  Remove Wins vs Observed Add
&lt;/h3&gt;

&lt;p&gt;In order to get a feel for the Set's semantics I implemented the&lt;br&gt;
OA-Set, the OR-Set, and the OAR-Set, testing them against each&lt;br&gt;
other using the basic model vs SUT approach described above.&lt;/p&gt;

&lt;p&gt;The Observed Add behavior was new to me. It isn't a Remove Wins in&lt;br&gt;
the sense that any remove concurrent with any add always&lt;br&gt;
wins. Instead, the Observed Add allows for the "removal of removes"&lt;br&gt;
that is to say in the case that re-adding something after observing&lt;br&gt;
its removal means that the remove itself will no long have an effect&lt;br&gt;
on some as yet unseen concurrent add. In a true remove wins set you&lt;br&gt;
can end up with the surprising "mutual destruction" of two Peers as&lt;br&gt;
described above who both:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; add element X to the set&lt;/li&gt;
&lt;li&gt; remove element X from the set&lt;/li&gt;
&lt;li&gt; re-add element X to the set&lt;/li&gt;
&lt;li&gt; merge&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In this case, with Observed Add, when Peer One re-inserts they are in&lt;br&gt;
effect saying "I'm removing my remove!" and so it has no effect on&lt;br&gt;
concurrent adds. If you want to learn more about &lt;em&gt;how&lt;/em&gt; this is achieved,&lt;br&gt;
you should &lt;a href="https://arxiv.org/abs/1903.03487"&gt;read the paper&lt;/a&gt;. It is&lt;br&gt;
neat, and I wish I'd known about it before I made the Remove Wins Map!.&lt;/p&gt;

&lt;p&gt;I was surprised by the Observe Add behavior, and wanted to better&lt;br&gt;
understand it. I understand by doing, so I implemented the Optimised&lt;br&gt;
OAR-Set from the paper. Naturally I tested the Optimised version as&lt;br&gt;
the SUT against the unoptimised version as the model. I was surprised&lt;br&gt;
to see they diverged.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Test; A Counter Example
&lt;/h2&gt;

&lt;p&gt;The state of the system is modeled as a &lt;code&gt;BTreeMap&amp;lt;usize, (Model, SUT)&amp;gt;&lt;/code&gt; where&lt;br&gt;
the key &lt;code&gt;usize&lt;/code&gt; is a "peer" in the distributed system.&lt;/p&gt;

&lt;p&gt;The operations that the test performs on the system are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;    &lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;SetOp&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Replicate&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;src_peer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dst_peer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;Insert&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;peer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;elem&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;Remove&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;peer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;elem&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="c1"&gt;// this is the remove wins remove&lt;/span&gt;
        &lt;span class="n"&gt;ObserveRemove&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;peer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;elem&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="c1"&gt;// this is the add wins remove&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The test generates a sequence of operations: a mutation operation happens at a&lt;br&gt;
site, and replication merges the data &lt;em&gt;from&lt;/em&gt; the first site &lt;em&gt;to&lt;/em&gt; the second.&lt;br&gt;
This models the replication of data from one site to another. Without any&lt;br&gt;
distributed systems, networks, databases, replication protocols, threads or&lt;br&gt;
parallelisation, we have a very basic but effective model of a distributed system.&lt;br&gt;
Each operation is a step, or moment in time. We have a single set. We allow&lt;br&gt;
concurrent updates to it. We arbitrarily replicate between pairs of sites.&lt;/p&gt;

&lt;p&gt;After all operations are applied, we merge all sites into a single&lt;br&gt;
value. This is the guarantee of &lt;a href="https://www.allthingsdistributed.com/2007/12/eventually_consistent.html"&gt;eventual&lt;br&gt;
consistency&lt;/a&gt;:&lt;br&gt;
when the system is quiescent (&lt;em&gt;i.e.&lt;/em&gt;, all peers have seen all operations)&lt;br&gt;
there is a single common value. At each merge we check that the model&lt;br&gt;
and SUT match, and at the end, when ALL sites are merged, we check&lt;br&gt;
again that the model and SUT match.&lt;/p&gt;

&lt;p&gt;Proptest found a failing case and kindly shrank the failing case down&lt;br&gt;
to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;shrunk_ops&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;vec!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="c1"&gt;// Peer Zero inserts 0 ⇒ [0]&lt;/span&gt;
    &lt;span class="nn"&gt;SetOp&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Insert&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;peer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;elem&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="c1"&gt;// Peer Zero does a remove wins remove of 0 ⇒ []&lt;/span&gt;
    &lt;span class="nn"&gt;SetOp&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Remove&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;peer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;elem&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="c1"&gt;// Peer Zero replicates to Peer Two ⇒ []&lt;/span&gt;
    &lt;span class="nn"&gt;SetOp&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Replicate&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;src_peer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dst_peer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="c1"&gt;// Peer Two (re)-adds (and has observed the remove wins remove (in OARSet this "removes the remove")) ⇒ [0]&lt;/span&gt;
    &lt;span class="nn"&gt;SetOp&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Insert&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;peer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;elem&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="c1"&gt;// Peer One inserts 0 (it has _not_ seen the earlier remove) ⇒ [0]&lt;/span&gt;
    &lt;span class="nn"&gt;SetOp&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Insert&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;peer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;elem&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="c1"&gt;// Peer Two observe removes 0 ⇒ []&lt;/span&gt;
    &lt;span class="nn"&gt;SetOp&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ObserveRemove&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;peer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;elem&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At some step the test failed because the model value was &lt;code&gt;[0]&lt;/code&gt; and the&lt;br&gt;
SUT was &lt;code&gt;[]&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Difference
&lt;/h2&gt;

&lt;p&gt;The job of the programmer is now to figure out &lt;em&gt;why&lt;/em&gt; the model and the&lt;br&gt;
SUT diverge. In this case I had a hunch, so I augmented my test to&lt;br&gt;
merge the final state in all possible permutations, and this showed&lt;br&gt;
that in some orderings the final sets matched.&lt;/p&gt;

&lt;p&gt;If we start by merging Peer One into Peer Zero, and then merge Peer&lt;br&gt;
Two into the result (&lt;em&gt;i.e.&lt;/em&gt; &lt;code&gt;(Peer Zero &amp;lt;- Peer One) &amp;lt;- Peer Two&lt;/code&gt;) the&lt;br&gt;
divergence happens when Peer Two is merged into the result of the merge of&lt;br&gt;
Peer One into Peer Zero. This is because when we merge Peer One into&lt;br&gt;
Peer Zero, we &lt;em&gt;lose&lt;/em&gt; the &lt;code&gt;0&lt;/code&gt; element from the set. Peer One still has&lt;br&gt;
the Observe Add tombstone (recall that it was &lt;em&gt;Peer Two&lt;/em&gt; that removes&lt;br&gt;
the remove by re-adding &lt;code&gt;0&lt;/code&gt; to the set.) The tombstone at Peer Zero&lt;br&gt;
acts on the element on Peer One.&lt;/p&gt;

&lt;p&gt;In the optimised specification a remove ACTUALLY REMOVES the element;&lt;br&gt;
it is forgotten (that's part of the optimisation). When we then merge&lt;br&gt;
Peer Two, the unoptimised set still has the element &lt;code&gt;0&lt;/code&gt; in the set,&lt;br&gt;
and notices that the tombstone itself was "removed" by the re-add at&lt;br&gt;
Peer Two, and so the add on Peer One becomes concurrent with the&lt;br&gt;
&lt;code&gt;observe remove&lt;/code&gt; on Peer Two (Observe Remove &lt;em&gt;means&lt;/em&gt; Add Wins).&lt;/p&gt;

&lt;p&gt;Well done property testing! The paper passed review and was accepted&lt;br&gt;
in the national informatics conference INForum 2018, but a six-step&lt;br&gt;
run with 3 peers shows the specs don't match. That isn't a critique of&lt;br&gt;
the work or the scientific process, but rather an endorsement of the power&lt;br&gt;
of stateful property-based testing.&lt;/p&gt;

&lt;p&gt;Not only do the specs not match, it also shows that the spec for the&lt;br&gt;
Optimised OAR-Set is not actually a CRDT, as a State-based CRDT's&lt;br&gt;
Merge function must be idempotent, commutative, and associative. For&lt;br&gt;
example, if we merge the Peers in the order &lt;code&gt;(Peer Two &amp;lt;- Peer Zero)&amp;lt;- Peer One&lt;/code&gt;&lt;br&gt;
then the SUT matches the model.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fix?
&lt;/h2&gt;

&lt;p&gt;I wanted to fix it for the sake of the blog post, and for my own&lt;br&gt;
understanding. I'm not sure my fix is great, as it isn't really in the&lt;br&gt;
&lt;em&gt;spirit&lt;/em&gt; of "optimised." My changes are in &lt;code&gt;merge&lt;/code&gt; and &lt;code&gt;lookup&lt;/code&gt; of the&lt;br&gt;
spec.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;merge&lt;/code&gt; we &lt;em&gt;never&lt;/em&gt; remove/forget a value from the &lt;code&gt;Add&lt;/code&gt; set, even&lt;br&gt;
if it appears to be tombstoned by the merge. In &lt;code&gt;lookup&lt;/code&gt; merely being&lt;br&gt;
present in the &lt;code&gt;Add&lt;/code&gt; set with timestamps is no longer enough, the&lt;br&gt;
element must also &lt;em&gt;NOT&lt;/em&gt; be present in the &lt;code&gt;Remove&lt;/code&gt; map. That's it. Now&lt;br&gt;
they match, the property test passes, and the Set &lt;em&gt;is&lt;/em&gt; a CRDT.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I contacted André Rijo and Nunu Preguiça about this work, and they&lt;br&gt;
were generous of their time in discussing it with me, agreeing that&lt;br&gt;
I had found a genuine issue in the paper: that the optimised&lt;br&gt;
specification does not match the OARSet specification.&lt;/p&gt;

&lt;p&gt;Deletes in distributed systems are hard, and the Observed Add semantic&lt;br&gt;
from these papers is a valuable alternative to the harsher Remove Wins&lt;br&gt;
behavior.&lt;/p&gt;

&lt;p&gt;This posts illustrates just how hard it is to make correct CRDTs, and&lt;br&gt;
why tools like Stateful Property-Based Testing are so valuable.&lt;/p&gt;

&lt;p&gt;Look out for the new types coming soon to the Ditto Platform.&lt;/p&gt;


&lt;p&gt;&lt;br&gt;
    &lt;a href="https://docs.ditto.live"&gt;&lt;br&gt;
      Learn more by visiting our Docs&lt;br&gt;
    &lt;/a&gt;&lt;br&gt;
  &lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;There is a &lt;a href="https://github.com/AltSysrq/proptest/pull/257"&gt;PR&lt;/a&gt; open on Proptest that adds EQC Statem-like features to Proptest. 🤞 ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>rust</category>
      <category>crdt</category>
      <category>programming</category>
      <category>testing</category>
    </item>
    <item>
      <title>DO cross the streams! Introducing Combine Support in Ditto - build complex workflows from multiple reactive streams</title>
      <dc:creator>Ryan Ratner</dc:creator>
      <pubDate>Thu, 10 Mar 2022 19:10:39 +0000</pubDate>
      <link>https://dev.to/dittolive/do-cross-the-streams-introducing-combine-support-in-ditto-build-complex-workflows-from-multiple-reactive-streams-2b3f</link>
      <guid>https://dev.to/dittolive/do-cross-the-streams-introducing-combine-support-in-ditto-build-complex-workflows-from-multiple-reactive-streams-2b3f</guid>
      <description>&lt;p&gt;Historically, iOS developers had to use delegates, polling timers, notification centers, and callbacks to build reactive architectures. While these techniques are useful for simple callbacks, they falter when dealing with multiple events in robust event-driven apps. Creating complex chains, combinations, and permutations of multiple reactive streams is incredibly difficult. Thus, reactive libraries like the open source Rx suite &lt;sup id="fnref1"&gt;1&lt;/sup&gt; and Apple's &lt;a href="https://developer.apple.com/documentation/combine"&gt;Combine&lt;/a&gt; were created to solve this exact issue.&lt;/p&gt;

&lt;p&gt;Since version 1.1.1 we have several &lt;code&gt;Combine.Publisher&lt;/code&gt; extension methods on our long-running callback APIs. Notably:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://software.ditto.live/cocoa/DittoSwift/1.1.1/api-reference/Classes/DittoPendingCursorOperation/LiveQueryPublisher.html"&gt;LiveQueryPublisher&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://software.ditto.live/cocoa/DittoSwift/1.1.1/api-reference/Classes/DittoPendingIDSpecificOperation/SingleDocumentLiveQueryPublisher.html"&gt;SingleDocumentLiveQueryPublisher&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://software.ditto.live/cocoa/DittoSwift/1.1.1/api-reference/Classes/Ditto/RemotePeersPublisher.html"&gt;RemotePeersPublisher&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://software.ditto.live/cocoa/DittoSwift/1.1.1/api-reference/Classes/DittoCollection/FetchAttachmentPublisher.html"&gt;FetchAttachmentPublisher&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These new extension methods make it easy to use reactive techniques in your iOS or macOS apps without an additional library. We consider these APIs stable: they are available for iOS versions 13.0+ and macOS 10.15+. Of all the extension methods, you'll probably use &lt;code&gt;LiveQueryPublisher&lt;/code&gt; the most. This Publisher is a method on the &lt;code&gt;DittoPendingCursorOperation&lt;/code&gt; and facilitates most of the synchronization behavior of your app. Here's an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;cancellables&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;AnyCancellables&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;// observe all documents in a collection&lt;/span&gt;
&lt;span class="n"&gt;ditto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"cars"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findAll&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;liveQueryPublisher&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sink&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;// do something with documents and events&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;in&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;cancellables&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// or you can observe documents matching a query&lt;/span&gt;
&lt;span class="n"&gt;ditto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"cars"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"color == $args.color &amp;amp;&amp;amp; mileage &amp;gt; $args.mileage"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s"&gt;"color"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"red"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"mileage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"mileage"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;liveQueryPublisher&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sink&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;// do something with documents and events&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;in&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;cancellables&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Stopping the live query is identical to stopping the publisher:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="n"&gt;cancellable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt; &lt;span class="c1"&gt;// if you have a single cancellable, stop it by setting it to nil&lt;/span&gt;

&lt;span class="n"&gt;cancellables&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeAll&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// removeAll will stop all attached publishers and their respective live queries&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Often, our users love to use &lt;a href="https://developer.apple.com/documentation/swift/codable"&gt;&lt;code&gt;Codable&lt;/code&gt;&lt;/a&gt; with Ditto Documents. Since &lt;code&gt;Codable&lt;/code&gt;s can fail to decode for some reason (type mismatch is the most common example), you may want to handle each decode error individually as they're streamed out. The &lt;code&gt;liveQueryPublisher&lt;/code&gt; gives each emission as an &lt;code&gt;Array&amp;lt;DittoDocument&amp;gt;&lt;/code&gt; each time the query results change.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;Car&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Codable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;
  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;
  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;mileage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Float&lt;/span&gt;
  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;isSold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;ditto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"cars"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findAll&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;liveQueryPublisher&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flatMap&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;docs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;docs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;publisher&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tryMap&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;typed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;as&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Car&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;receiveCompletion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Decoding a document failed: &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;describing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nv"&gt;receiveValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;car&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Car&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Successfully decoded a car &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="kt"&gt;Car&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There may be times where your application would like to collect the final values of only &lt;em&gt;successfully decoded&lt;/em&gt; &lt;code&gt;Car&lt;/code&gt; objects. You may want this functionality if you're not interested in stale or malformed legacy documents. Here we use &lt;code&gt;flatMap&lt;/code&gt; to turn the &lt;code&gt;Publisher&amp;lt;[DittoDocument]&amp;gt;&lt;/code&gt; into a &lt;code&gt;Publisher&amp;lt;DittoDocument&amp;gt;&lt;/code&gt; that will emit for each item in the array. Now for each of the single emitted &lt;code&gt;DittoDocument&lt;/code&gt; we will use &lt;a href="https://developer.apple.com/documentation/combine/publisher/compactmap(_:)"&gt;&lt;code&gt;compactMap&lt;/code&gt;&lt;/a&gt; on the decoding function. Notice the &lt;code&gt;try?&lt;/code&gt; here. If any &lt;code&gt;Car&lt;/code&gt; fails to decode, this function will return a &lt;code&gt;nil&lt;/code&gt;. &lt;code&gt;compactMap&lt;/code&gt; will skip any &lt;code&gt;nil&lt;/code&gt; values. At the end we will use &lt;code&gt;collect&lt;/code&gt; to gather all of the emitted &lt;code&gt;Car&lt;/code&gt; objects and repackage them into an &lt;code&gt;Array&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;cancellable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ditto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"cars"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findAll&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;liveQueryPublisher&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flatMap&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;publisher&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compactMap&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;typed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;as&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Car&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sink&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;cars&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Sucessfully decoded cars: &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;cars&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;. Failed to decode cars were removed from the array."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  SQL &lt;code&gt;JOIN&lt;/code&gt;-like behavior with Ditto using Combine
&lt;/h2&gt;

&lt;p&gt;A question that we get all the time is "How do I perform SQL-like &lt;code&gt;JOIN&lt;/code&gt;s with Ditto"? While Ditto's current interface can't handle relationships like a traditional SQL database, our Combine support can help us achieve the same effect. &lt;sup id="fnref2"&gt;2&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;Let's say you're trying to build a menu view in SwiftUI like the first image shown below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ldEytwGb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7ajsf3ct7ohvrowx92ij.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ldEytwGb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7ajsf3ct7ohvrowx92ij.jpg" alt="Combine menu" width="880" height="1395"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OOuL15hP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wj9usui5pia7ns49zrhj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OOuL15hP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wj9usui5pia7ns49zrhj.png" alt="Image description" width="880" height="1395"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's likely that you'll use a SwiftUI &lt;a href="https://developer.apple.com/documentation/swiftui/list"&gt;&lt;code&gt;List&lt;/code&gt;&lt;/a&gt; with multiple &lt;a href="https://developer.apple.com/documentation/swiftui/section"&gt;&lt;code&gt;Section&lt;/code&gt;s&lt;/a&gt; with the help of &lt;a href="https://developer.apple.com/documentation/swiftui/foreach"&gt;&lt;code&gt;ForEach&lt;/code&gt;&lt;/a&gt;. Assume each document in their respective collection looks like the following:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="c1"&gt;// products&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"chicken-sandwich"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Chicken Sandwich"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"detail"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Grilled chicken, tomatoes, lettuce, and mustard"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"categoryId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"entree"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="c1"&gt;// categories&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"entrees"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Main Courses and Entrees"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;We can create representable &lt;code&gt;Codable&lt;/code&gt;s for each Document type. Notice that we've added &lt;code&gt;Identifiable&lt;/code&gt; to help &lt;code&gt;ForEach&lt;/code&gt; iteration:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Codable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;isOnSale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;extension&lt;/span&gt; &lt;span class="kt"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Identifiable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Codable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;categoryId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;extension&lt;/span&gt; &lt;span class="kt"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Identifiable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Notice that the &lt;code&gt;product&lt;/code&gt; has a &lt;code&gt;categoryId&lt;/code&gt;, this is our foreign key. Linking these foreign keys with our earlier APIs wasn't very straightforward. However, with our new Combine extensions, we can use the &lt;a href="https://developer.apple.com/documentation/combine/just/combinelatest(_:)"&gt;&lt;code&gt;combineLatest&lt;/code&gt;&lt;/a&gt; function to emit a single callback for both the &lt;code&gt;products&lt;/code&gt; and &lt;code&gt;categories&lt;/code&gt; collections.&lt;/p&gt;

&lt;p&gt;First we will need to create a &lt;code&gt;JOIN&lt;/code&gt;-ed struct that will house our nested values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;CategorizedProducts&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// This is the category&lt;/span&gt;
  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Category&lt;/span&gt;
  &lt;span class="c1"&gt;// This is the products filtered by the category above&lt;/span&gt;
  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;products&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// We add `Identifiable` to help `ForEach` iteration.&lt;/span&gt;
&lt;span class="c1"&gt;// Since this is unique by inner category._id property, we ensure&lt;/span&gt;
&lt;span class="c1"&gt;// to return its value&lt;/span&gt;
&lt;span class="kd"&gt;extension&lt;/span&gt; &lt;span class="kt"&gt;CategorizedProducts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Identifiable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To populate &lt;code&gt;CategorizedProducts&lt;/code&gt; we initially need to create our &lt;code&gt;combineLatest&lt;/code&gt; implementation. First we will need to get access to both &lt;code&gt;categories&lt;/code&gt; and &lt;code&gt;products&lt;/code&gt; publishers. We use &lt;code&gt;Codable&lt;/code&gt; to map documents into concrete data types in the &lt;code&gt;.tryMap&lt;/code&gt; operator.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;categoriesPublisher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;categoriesCollection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findAll&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;liveQueryPublisher&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tryMap&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;typed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;as&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Category&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;productsPublisher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;productsCollection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findAll&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;liveQueryPublisher&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tryMap&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;typed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;as&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, we can combine the latest values of each publisher using &lt;code&gt;.combineLatest&lt;/code&gt; and &lt;code&gt;.map&lt;/code&gt;. In the &lt;code&gt;.map&lt;/code&gt; function, we iterate over each category and use it to create a &lt;code&gt;CategorizedProducts&lt;/code&gt; object and filter all products by the &lt;code&gt;categoryId&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;cancellable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;categoriesPublisher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;combineLatest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;productsPublisher&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;categories&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;categories&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;CategorizedProducts&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;filteredProducts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;categoryId&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kt"&gt;CategorizedProducts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;products&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;filteredProducts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sink&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;categorizedProducts&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
      &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"categories with their products"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;categorizedProducts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If any update, insert, or deletions occur to the &lt;code&gt;products&lt;/code&gt; &lt;em&gt;or&lt;/em&gt; &lt;code&gt;categories&lt;/code&gt; collection, you'll always get a new set of &lt;code&gt;categorizedProducts&lt;/code&gt;. To show this menu we can iterate over each &lt;code&gt;categorizedProducts&lt;/code&gt; in SwiftUI like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kt"&gt;List&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;ForEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;categorizedProducts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;categorizedProducts&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="kt"&gt;Section&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;categorizedProducts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kt"&gt;ForEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;categorizedProducts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
        &lt;span class="kt"&gt;VStack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;alignment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;leading&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="kt"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
              &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bold&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
          &lt;span class="kt"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
              &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;font&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;caption&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For an example project using this technique, &lt;a href="https://github.com/getditto/samples/tree/master/combine-menu"&gt;checkout the source code on GitHub here.&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Making SQL &lt;code&gt;JOIN&lt;/code&gt;-like behavior more efficient
&lt;/h3&gt;

&lt;p&gt;Using &lt;code&gt;.combineLatest&lt;/code&gt; as a way to get a SQL JOIN-like can achieve both a reactive API as well as better management of relational models. However, it's important to know that this is just an approximation. Remember, Ditto live queries sync exactly what you tell it to sync from the mesh. The example above will sync &lt;em&gt;all &lt;code&gt;categories&lt;/code&gt; and all &lt;code&gt;products&lt;/code&gt;&lt;/em&gt;. This behavior may be desirable for many use cases but let's take at some ways we can reduce what is synced over the mesh.&lt;/p&gt;

&lt;p&gt;You can use the queries to limit what documents are of interest by specifying a more selective query. Let's say you're only interested in getting &lt;code&gt;CategorizedProducts&lt;/code&gt; where the category is one of &lt;code&gt;"appetizers"&lt;/code&gt;, &lt;code&gt;"entrees"&lt;/code&gt;, or &lt;code&gt;"desserts"&lt;/code&gt;. We could now filter using a &lt;a href="https://docs.ditto.live/concepts/querying#compound---and--or--not--contains-contains"&gt;query&lt;/a&gt; with &lt;code&gt;.find&lt;/code&gt; instead of &lt;code&gt;.findAll&lt;/code&gt; on the &lt;code&gt;categories&lt;/code&gt; collection.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;categoriesPublisher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;categoriesCollection&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"contains([$args.categoryIds], _id)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"categoryId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"appetizers"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"entrees"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"desserts"&lt;/span&gt;&lt;span class="p"&gt;]])&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;liveQueryPublisher&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tryMap&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;typed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;as&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Category&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;productsPublisher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;productsCollection&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"contains([$args.categoryIds], categoryIds)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"categoryId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"appetizers"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"entrees"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"desserts"&lt;/span&gt;&lt;span class="p"&gt;]])&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;liveQueryPublisher&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tryMap&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;typed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;as&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;cancellable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;categoriesPublisher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;combineLatest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;productsPublisher&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;categories&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;categories&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;CategorizedProducts&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;filteredProducts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;categoryId&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kt"&gt;CategorizedProducts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;products&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;filteredProducts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sink&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;categorizedProducts&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
      &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"categories with their products where categoryId are appetizers, entrees, desserts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;categorizedProducts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the device will only sync relevant &lt;code&gt;CategorizedProducts&lt;/code&gt; by the specified category _ids.&lt;/p&gt;

&lt;h3&gt;
  
  
  Advanced Scenarios for SQL &lt;code&gt;JOIN&lt;/code&gt;-like behavior
&lt;/h3&gt;

&lt;p&gt;Let's say we have a scenario where we only want to show &lt;code&gt;CategorizedProducts&lt;/code&gt; where the &lt;code&gt;Category.isOnSale == true&lt;/code&gt;. To do a SQL JOIN-like behavior with Ditto and Combine is not as straightforward as it was in the example above. This is because we are querying on a property that &lt;em&gt;only exists&lt;/em&gt; on the &lt;code&gt;categories&lt;/code&gt; collection. Previously we were querying on both the primary key &lt;code&gt;Category._id&lt;/code&gt; and the foreign key &lt;code&gt;Product.categoryId&lt;/code&gt;. To do this is a bit harder than most and requires a decent amount of understanding of all the Combine operators.&lt;/p&gt;

&lt;p&gt;First we will need to create a live query of &lt;code&gt;categories&lt;/code&gt; where the &lt;code&gt;isOnSale == true&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;categoriesPublisher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;categoriesCollection&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"isOnSale == true"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;liveQueryPublisher&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tryMap&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;typed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;as&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Category&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since we don't know the categories that are passed ahead of time to filter the products, we need to filter them &lt;em&gt;after&lt;/em&gt; the values have returned from the categories live query publisher. Once we've received the categories that fit the &lt;code&gt;isOnSale == true&lt;/code&gt; query, we can &lt;em&gt;then create&lt;/em&gt; map it's result into an &lt;code&gt;AnyPublisher&amp;lt;[CategorizedProducts], Never&amp;gt;&lt;/code&gt;. We use &lt;code&gt;AnyPublisher&amp;lt;[CategorizedProducts], Never&amp;gt;&lt;/code&gt; for brevity so that the entire chain isn't convoluted with complex generics.&lt;/p&gt;

&lt;p&gt;Once the category's publisher emits data, we retrieve an array of &lt;code&gt;categoryIds: [String]&lt;/code&gt; to feed it to the &lt;code&gt;products&lt;/code&gt; live query publisher by filtering on the &lt;code&gt;categoryId&lt;/code&gt; foreign key property. Next, &lt;em&gt;inside of the first categories publisher's map&lt;/em&gt;, we will use our &lt;code&gt;.combineLatest&lt;/code&gt; technique to map and filter the &lt;code&gt;CategorizedProducts&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The most important function in this chain is the &lt;a href="https://developer.apple.com/documentation/combine/publisher/switchtolatest()-453ht"&gt;&lt;code&gt;switchToLatest&lt;/code&gt;&lt;/a&gt; right before the &lt;code&gt;.sink&lt;/code&gt;. While it can be tricky for your eyes to follow, the &lt;code&gt;switchToLatest&lt;/code&gt; will &lt;em&gt;dispose of any publishers&lt;/em&gt; if the top level publisher changes. This is a critical operator because we absolutely want to dispose the product live queries if the categories change. Categories may change if a new &lt;code&gt;isOnSale&lt;/code&gt; is added, an existing category that has &lt;code&gt;isOnSale&lt;/code&gt; becomes &lt;code&gt;false&lt;/code&gt;, or is deleted. Without &lt;code&gt;switchToLatest&lt;/code&gt; we will get mismatched &lt;code&gt;products&lt;/code&gt; from previous categories.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;categoriesPublisher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;categoriesCollection&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"isOnSale == true"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;liveQueryPublisher&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tryMap&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;typed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;as&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Category&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;


&lt;span class="n"&gt;categoriesPublisher&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="n"&gt;categories&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;AnyPublisher&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;CategorizedProducts&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="kt"&gt;Never&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
        &lt;span class="c1"&gt;// retrieve the categoryIds for all that were on sale.&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;categoryIds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;categories&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;productsPublisher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;productsCollection&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"contains($args.categoryIds, categoryId)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"categoryIds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;categoryIds&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;liveQueryPublisher&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tryMap&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;typed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;as&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="kt"&gt;Just&lt;/span&gt;&lt;span class="p"&gt;([])&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eraseToAnyPublisher&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="c1"&gt;// we now create CategorizedProducts from the filtered categories and filtered products&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kt"&gt;Just&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;categories&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;combineLatest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;productsPublisher&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;categories&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;CategorizedProducts&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;categories&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;CategorizedProducts&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
                    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;filteredProducts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;categoryId&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
                    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kt"&gt;CategorizedProducts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;products&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;filteredProducts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eraseToAnyPublisher&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;switchToLatest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// extremely important so that we dispose of the products publisher if the categories change&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="kt"&gt;Just&lt;/span&gt;&lt;span class="p"&gt;([])&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sink&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;categorizedProducts&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
      &lt;span class="c1"&gt;// completely filtered categorizedProducts&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now if any category is added, removed, or updated to match &lt;code&gt;isOnSale == true&lt;/code&gt;, we will instantly retrieve a new set of &lt;code&gt;CategorizedProducts&lt;/code&gt; with live queries specifically limited to the matched &lt;code&gt;Category._id&lt;/code&gt;&lt;br&gt;
As you can see this is &lt;em&gt;very complex&lt;/em&gt; and really shows off the power of Combine with Ditto. We understand that the last example is a very complex and verbose code sample to achieve SQL &lt;code&gt;JOIN&lt;/code&gt;-like behavior and we are working hard on adding native support directly within Ditto.&lt;/p&gt;

&lt;p&gt;We're extremely excited to see all the new iOS and macOS applications that leverage Combine with Ditto. Building reactive applications has always been a core tenet of Ditto's design philosophy. Now with Combine, you can have incredible control over Ditto's live query system to build complex and robust event-driven apps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Footnotes
&lt;/h2&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;Rx is suite of libraries that really gained a tremendous foothold for designing Reactive streams. There are libraries for JavaScript - &lt;a href="https://rxjs.dev/"&gt;RxJS&lt;/a&gt;, Swift - &lt;a href="https://github.com/ReactiveX/RxSwift"&gt;RxSwift&lt;/a&gt;, Java - &lt;a href="https://github.com/ReactiveX/RxJava"&gt;RxJava&lt;/a&gt;, Rx .NET - &lt;a href="https://github.com/dotnet/reactive"&gt;Reactive Extensions&lt;/a&gt; and more. If you're building Ditto applications beyond Swift, you should take a look at these libraries. Most of the operators available in Apple's Combine framework have equivalent functions. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;We get so many requests for relationships, aggregates, syncing specific fields and more that we're planning on adding this functionality directly in an upcoming iteration of our Ditto query language. This will likely &lt;em&gt;look&lt;/em&gt; like SQL but will have some changes to accommodate working with Ditto's distributed database semantics. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>swift</category>
      <category>ios</category>
    </item>
    <item>
      <title>Mocking Time in Async Rust</title>
      <dc:creator>Ryan Ratner</dc:creator>
      <pubDate>Tue, 15 Feb 2022 19:01:09 +0000</pubDate>
      <link>https://dev.to/dittolive/mocking-time-in-async-rust-1b4</link>
      <guid>https://dev.to/dittolive/mocking-time-in-async-rust-1b4</guid>
      <description>&lt;p&gt;We love async Rust at &lt;a href="https://www.ditto.live"&gt;Ditto&lt;/a&gt;. We were early adopters, using futures and streams in our networking code long before async/await became stable. Our product is a perfect use case. It's I/O-heavy, there are numerous peer-to-peer connections synchronizing data concurrently, and we need to use all cores efficiently on low-powered devices like mobile phones.&lt;/p&gt;

&lt;p&gt;A common challenge in async is writing good tests. Take a simple example: below we create a hypothetical &lt;code&gt;Connection&lt;/code&gt;. If there is no activity for 10 seconds it should time out internally and close its events channel. We can write a test verifying this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[tokio::test]&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;test_timeout_occurs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Create the Connection&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Connection&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// Wait for a while&lt;/span&gt;
    &lt;span class="nf"&gt;delay_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_secs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="c1"&gt;// The receive channel should have closed&lt;/span&gt;
    &lt;span class="nd"&gt;assert_eq!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="nf"&gt;.try_recv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.unwrap_err&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nn"&gt;TryRecvError&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Disconnected&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One problem with this test is that it always takes 11 seconds to execute: 10 seconds to wait for the timeout plus a safety factor. Slow tests are a huge hassle for both developers and CI/CD. Imagine if it was 30 minutes instead. Your CI runs would take &lt;em&gt;at least&lt;/em&gt; 30 minutes, all because of this one test!&lt;/p&gt;

&lt;p&gt;A related challenge is testing for the &lt;em&gt;absence&lt;/em&gt; of a timer expiry. Here we ensure the events channel is still open at 5 seconds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[tokio::test]&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;test_timeout_does_not_occur&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Create the Connection&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Connection&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// Wait less than in the other test&lt;/span&gt;
    &lt;span class="nf"&gt;delay_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_secs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="c1"&gt;// The receive channel should still be open (but no value)&lt;/span&gt;
    &lt;span class="nd"&gt;assert_eq!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="nf"&gt;.try_recv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.unwrap_err&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nn"&gt;TryRecvError&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This test is also slow, weighing in at 5 seconds, but there is a more insidious problem. Imagine our &lt;code&gt;Connection&lt;/code&gt; had a bug which caused it to close the channel after only 4 seconds. Would this test catch it? Probably… but not necessarily.&lt;/p&gt;

&lt;p&gt;When that internal 4-second timer expires a series of steps must occur. A tokio worker thread must be unparked and poll the task which was waiting on that timer. Having noticed that the timer has expired it drops the events sender, which indicates to the receiving end that the channel is closed. It's only a small amount of work but it's not instantaneous, and it has to run concurrently with the main test task.&lt;/p&gt;

&lt;p&gt;If you run this on your high-end development machine (which is what Rust developers use) then that expiry-and-channel-close process will occur in a matter of milliseconds. By the time the test task performs the assertion 1 second later, the channel will almost certainly be closed and the bug will be detected. On a heavily-loaded CI server, however, there may be fierce contention between threads. Processing the inner timeout might be delayed by 1–2 real-world seconds and the test would appear to pass. The previous test has the same problem in reverse; if the inner timeout gets delayed it will fail, even though the code being tested is correct.&lt;/p&gt;

&lt;p&gt;These tests are non-deterministic and will result in flaky CI: intermittent build failures where you're not sure whether you can trust the results. This is suboptimal.&lt;/p&gt;

&lt;p&gt;Conventional wisdom says that you shouldn't structure your code this way. Any logic you want to test should be expressed as synchronous code which is fully deterministic and abstracted away from the system clock. This is okay for small unit tests but it becomes a headache in larger integration tests. Some of our business logic is tied up in async behavior. Why can't we test that, the way our customers will experience it? If we test only the sync parts we're losing coverage.&lt;/p&gt;

&lt;p&gt;What we want is a way to test our time-driven code with fewer compromises.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It should be possible to test async code "as-is" in unit tests.&lt;/li&gt;
&lt;li&gt;In a test context, passage of time should be abstracted so that tests execute near-instantly no matter how long the delays are.&lt;/li&gt;
&lt;li&gt;Tests should be fully deterministic: an earlier timer always gets to work to completion before a timer scheduled later.&lt;/li&gt;
&lt;li&gt;As a unit test advances mock time, &lt;code&gt;Instant::now()&lt;/code&gt; should return the correct intermediate time during the triggering of each timer.&lt;/li&gt;
&lt;li&gt;The processing work attached to a timer should allow new timers to be registered along the way.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Ditto has built an internal library to address all of these problems. This crate, &lt;code&gt;ditto_time&lt;/code&gt;, abstracts over &lt;code&gt;std::time&lt;/code&gt; and the tokio timer functions. Here is the first test again, except now using this library.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[test]&lt;/span&gt;
&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;test_timeout_occurs_fast&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time_control&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_guard&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;register_new_control&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;rt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;build_instrumented_runtime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;time_control&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;rt&lt;/span&gt;&lt;span class="nf"&gt;.block_on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Connection&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;time_control&lt;/span&gt;&lt;span class="nf"&gt;.advance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_secs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;----&lt;/span&gt;
        &lt;span class="nd"&gt;assert_eq!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="nf"&gt;.try_recv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.unwrap_err&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nn"&gt;TryRecvError&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Disconnected&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This test is reliable and completes immediately. First there is a little boilerplate which ensures the code under test will use mock timers instead of real ones. For now, focus on the call to &lt;code&gt;time_control.advance()&lt;/code&gt;. Notice that it moves time ahead precisely 10 seconds. Then on the next line, the channel is guaranteed to be closed.&lt;/p&gt;

&lt;p&gt;This is the most difficult part. It is not sufficient to trigger the timer. Somehow we have to know when the code which &lt;em&gt;received&lt;/em&gt; that timer event has finished doing all of its associated work. This isn't possible using conventional timer futures. We don't know when the associated task will get scheduled, let alone when it's finished. Therefore the futures in &lt;code&gt;ditto_time&lt;/code&gt; are different—they output a &lt;code&gt;FrozenTimeControlGuard&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;_guard&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;ditto_time&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;delay_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_secs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="c1"&gt;// handle timer event&lt;/span&gt;
    &lt;span class="c1"&gt;// guard dropped; time can advance&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It turns out that in almost all situations, timer-driven code does all of the relevant processing in a single block immediately following the &lt;code&gt;await&lt;/code&gt;. Therefore all we have to do is create a binding like &lt;code&gt;_guard&lt;/code&gt;, and now we have a value that can notify us when the end of the block has been reached—and therefore time can continue. In unusual cases the developer can preserve the guard as long as needed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nf"&gt;FrozenTimeControlGuard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nn"&gt;oneshot&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Sender&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In unit test code the guard contains a oneshot sender. On drop the channel closes and the call to &lt;code&gt;advance()&lt;/code&gt; knows that it can move on to the next timer. &lt;code&gt;advance()&lt;/code&gt; is an async loop over all the currently-registered timers, triggering them one at a time and waiting for the corresponding channel to close. In non-test code there is no channel at all—no extra processing is required.&lt;/p&gt;

&lt;p&gt;The last trick is correctly selecting real vs mock timers, and ensuring that they are associated with the correct &lt;code&gt;TimeControl&lt;/code&gt;. Rust runs tests in parallel so there may be many tokio runtimes executing tests simultaneously. If we tried to use global storage, different tests will clobber each other.&lt;/p&gt;

&lt;p&gt;The solution is thread-local storage (TLS). Tokio offers &lt;a href="https://docs.rs/tokio/1.16.1/tokio/runtime/struct.Builder.html#method.on_thread_start"&gt;the ability to run code when each worker thread is started&lt;/a&gt;, which is an opportunity to store an &lt;code&gt;Arc&amp;lt;TimeControl&amp;gt;&lt;/code&gt;. This is the purpose of the &lt;code&gt;build_instrumented_runtime&lt;/code&gt; boilerplate in the test. When the code under test calls &lt;code&gt;delay_for()&lt;/code&gt; it will dynamically create a &lt;code&gt;Delay&lt;/code&gt; future of the correct variant:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;thread_local!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;MOCK_CONTROL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RefCell&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Arc&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TimeControl&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;RefCell&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;delay_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;time&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Delay&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;MOCK_CONTROL&lt;/span&gt;&lt;span class="nf"&gt;.with&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="nf"&gt;.borrow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.as_ref&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;control&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;deadline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;control&lt;/span&gt;&lt;span class="nf"&gt;.now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nn"&gt;Delay&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new_mock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;control&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;deadline&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nb"&gt;None&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Delay&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Real&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Box&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;pin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;tokio&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;time&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;))),&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With all that explanation, let's review the test in full.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[test]&lt;/span&gt;
&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;test_timeout_occurs_fast&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time_control&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_guard&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;register_new_control&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;rt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;build_instrumented_runtime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;time_control&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;rt&lt;/span&gt;&lt;span class="nf"&gt;.block_on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Connection&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;time_control&lt;/span&gt;&lt;span class="nf"&gt;.advance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_secs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nd"&gt;assert_eq!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="nf"&gt;.try_recv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.unwrap_err&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nn"&gt;TryRecvError&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Disconnected&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;A &lt;code&gt;TimeControl&lt;/code&gt; instance is created that the test will use to advance time, and will also collect registrations of mock timers.&lt;/li&gt;
&lt;li&gt;A custom &lt;code&gt;Runtime&lt;/code&gt; is created which injects the &lt;code&gt;Arc&amp;lt;TimeControl&amp;gt;&lt;/code&gt; into TLS.&lt;/li&gt;
&lt;li&gt;Inside that runtime, the &lt;code&gt;Connection&lt;/code&gt; is created and a 10-second mock timer is registered.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;advance()&lt;/code&gt; is called with a 10 second duration. This will advance to 10 seconds, permitting all of the code inside &lt;code&gt;Connection&lt;/code&gt; to run. The channel gets closed.&lt;/li&gt;
&lt;li&gt;The channel is definitely closed and reports &lt;code&gt;Disconnected&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The second test can be adapted similarly. If the channel closes in 5 seconds or less, we're guaranteed to catch it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[test]&lt;/span&gt;
&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;test_timeout_does_not_occur_fast&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time_control&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_guard&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;register_new_control&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;rt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;build_instrumented_runtime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;time_control&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;rt&lt;/span&gt;&lt;span class="nf"&gt;.block_on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Connection&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;time_control&lt;/span&gt;&lt;span class="nf"&gt;.advance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_secs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nd"&gt;assert_eq!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="nf"&gt;.try_recv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.unwrap_err&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nn"&gt;TryRecvError&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This technique has enabled Ditto to build fast-running unit tests on exactly the same async code our customers use, with only modest code modifications to account for the guards. Testing gives us the confidence to include business logic directly in our async layer, avoiding the overhead of structuring timer-driven code into sync and async components. We believe async Rust has a bright future and our customers are already seeing the benefits.&lt;/p&gt;

&lt;p&gt;Visit us at &lt;a href="http://www.ditto.live"&gt;www.ditto.live&lt;/a&gt; for more content&lt;/p&gt;

</description>
      <category>rust</category>
      <category>programming</category>
    </item>
    <item>
      <title>A database always capable of reading or writing information: Ditto Local Store</title>
      <dc:creator>Ryan Ratner</dc:creator>
      <pubDate>Tue, 08 Feb 2022 22:45:44 +0000</pubDate>
      <link>https://dev.to/dittolive/a-database-always-capable-of-reading-or-writing-information-ditto-local-store-478c</link>
      <guid>https://dev.to/dittolive/a-database-always-capable-of-reading-or-writing-information-ditto-local-store-478c</guid>
      <description>&lt;p&gt;If you aren’t familiar with &lt;a href="https://www.ditto.live"&gt;Ditto&lt;/a&gt;, we are a real-time database for mobile, web, IoT, and server apps that can magically sync data with or even without the internet.&lt;/p&gt;

&lt;p&gt;But what's really interesting about our database is that you can use Ditto in your apps as a regular document database. When we first created Ditto, we took three parallel tracks to building our entire system:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The database portion&lt;/strong&gt;: the primary way that customers use Ditto today&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Replication and CRDTs&lt;/strong&gt;: how customers can mutate data in a distributed way and allow for automatic conflict resolution&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The mesh network&lt;/strong&gt;: how devices can talk to each other in a heterogenous way&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Parts 2 and 3 only work if you call &lt;code&gt;ditto.tryStartSync()&lt;/code&gt;. However, we wanted to design a system where the &lt;strong&gt;database portion was always capable of reading or writing information&lt;/strong&gt;. That means if your application never sets up any &lt;a href="https://docs.ditto.live/advanced/platform-permissions/ios-platform-permissions"&gt;network configuration permissions&lt;/a&gt; or calls &lt;code&gt;ditto.tryStartSync()&lt;/code&gt;, the instance will &lt;em&gt;only act as a local database&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The usage is incredibly simple. Here's an example using Swift&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;val&lt;/span&gt; &lt;span class="nx"&gt;ditto&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Ditto&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;// inserting a document&lt;/span&gt;
&lt;span class="nx"&gt;ditto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;people&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;upsert&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Alice&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;score&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;21&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="c1"&gt;// finding documents&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;docs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ditto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;people&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name == $args.name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Alice&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and on Android&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;val&lt;/span&gt; &lt;span class="nx"&gt;ditto&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Ditto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;androidDependencies&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// inserting a document&lt;/span&gt;
&lt;span class="nx"&gt;ditto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;people&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;upsert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;mapOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Alice&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;score&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="mi"&gt;21&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// finding documents&lt;/span&gt;
&lt;span class="nx"&gt;val&lt;/span&gt; &lt;span class="nx"&gt;docs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ditto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;people&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name == $args.name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mapOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Alice&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For more information on our APIs and other supported programming languages checkout the &lt;a href="https://docs.ditto.live/concepts/overview"&gt;concepts section of our documentation&lt;/a&gt;. All of the data &lt;code&gt;store&lt;/code&gt; operations will work exactly the same as a syncing instance of Ditto. That means your application is completely capable of running a live query on a local instance of Ditto as well.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;ditto&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;  &lt;span class="nx"&gt;Ditto&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;liveQueries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ditto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cars&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;findAll&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;documents&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
  &lt;span class="nx"&gt;updateUI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Taking advantage of a local-only Ditto instance in the same app as a syncing Ditto instance
&lt;/h2&gt;

&lt;p&gt;Each ditto instance is self-contained. That means that &lt;em&gt;an app can run multiple ditto instances at the same time&lt;/em&gt;. All you need to do is to take a bit of care&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// constructing a local only instance&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;dittoLocalOnly&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Ditto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/foo1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// constructing a syncing version of Ditto&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;dittoSyncing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Ditto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;identity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onlinePlayground&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;appID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my-cool-app&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/foo2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nx"&gt;dittoSyncing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tryStartSync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;// notice that the path values are different to ensure that there are no file system collisions.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why would you want a syncing instance and a non-syncing instance in the same app? There could be many reasons. Perhaps, your application may need to simply store data locally without replication, still using the same interface of a regular Ditto instance that you're accustomed to, while at the same time requiring to sync another instance. Syncing Ditto instances can control specific data that syncs or does not sync between devices, but some users may want the added simplicity of managing multiple Ditto instances with distinct functionality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Looking Ahead
&lt;/h2&gt;

&lt;p&gt;Ditto is an end-to-end platform product that can sync data regardless of connectivity from the edge to the cloud. That said, this is really our bread and butter, so a lot of our engineering efforts are focused on the performance of the replication, the mesh network, and our Big Peer (that lives in the cloud). This means that a lot of comparable database features aren't up to par with, say, something like &lt;a href="https://www.sqlite.org/index.html"&gt;SQLite&lt;/a&gt; or &lt;a href="https://realm.io/"&gt;Realm&lt;/a&gt;. But don't worry, we are definitely getting there!&lt;/p&gt;

&lt;p&gt;What are we lacking right now?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No indexing - Currently, we don't have any functionality for indexing your data. Don't worry, we are architecting this functionality as we speak.&lt;/li&gt;
&lt;li&gt;Non-CRDT serializatation - Remember we built a &lt;em&gt;syncing-database&lt;/em&gt;, so every document is a fully composable nested CRDT. Even if you use it as a local database, we still create and serialize a CRDT to disc.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What about JavaScript?
&lt;/h3&gt;

&lt;p&gt;Currently, running Ditto as a local database using our JavaScript SDK comes with some caveats. Our web experience currently is in-memory only. That means if you were to use the npm package &lt;a href="https://www.npmjs.com/package/@dittolive/ditto"&gt;&lt;code&gt;@dittolive/ditto&lt;/code&gt;&lt;/a&gt; in the web browser, your bundler will use our WebAssembly version. As a result, nothing in the store is persisted and a simple refresh of the web page will have all data evicted. We are actively working on a backing storage interface using the &lt;a href="https://web.dev/file-system-access/"&gt;File System Access API for modern browsers&lt;/a&gt; and a fallback to &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API"&gt;IndexedDB&lt;/a&gt; this year.&lt;/p&gt;

&lt;p&gt;The good news is that today you &lt;em&gt;can&lt;/em&gt; get persistence if you are using our JS library in &lt;a href="https://www.electronjs.org/"&gt;Electron&lt;/a&gt; or &lt;a href="https://nodejs.org/en/"&gt;NodeJS&lt;/a&gt;. If you're curious why: unlike the browser, these platforms actually have native API access. So, when running Ditto in NodeJS or Electron environments, the NPM package will use a compiled native library as it's backing storage layer, and you will actually see Ditto files created!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--N39d4qyX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yw0eumerhcpbp1wdseeh.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--N39d4qyX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yw0eumerhcpbp1wdseeh.jpg" alt="NodeJS with Ditto Files" width="880" height="550"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A real app using Ditto as a local database - Vaxx
&lt;/h2&gt;

&lt;p&gt;We wanted to test out how it would be to use Ditto as a local database in an app that would actually provide some use to the world. Since some cities have mandated that restaurants must check for vaccination records to receive service, we noticed that a lot of customers were endlessly scrolling on their phones to pull up a picture of their records. This was wasting a lot of the maitre d's time and slowing down seating. So we thought, what if we could create an app with a simple (yet edgy) name that would allow customers to &lt;em&gt;immediately&lt;/em&gt; pull up their vaccine card? Well, with Ditto this is actually quite simple. We built it in just about 1 day and released it to the app store! Vaxx is running on Ditto, using it only as a local database, and doesn't sync anything at all. Vaxx is currently enjoying 55,000 active users with no promotion at all and still growing! In addition, if you just search for "Vaccine Card" in the U.S. Apple App Store, it's the first result.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://apps.apple.com/us/app/vaxx/id1580848435"&gt;Download Vaxx here for iOS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/getditto/vaxx"&gt;Check out Source Code on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TlXfVnOg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6off6qqtbgtvcvk1wxym.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TlXfVnOg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6off6qqtbgtvcvk1wxym.jpg" alt="Vaxx for iPhone" width="880" height="587"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We hope to create an Android version sometime in the coming months, so stay tuned!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>database</category>
      <category>ios</category>
      <category>android</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
