<?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: Patric Genfer</title>
    <description>The latest articles on DEV Community by Patric Genfer (@pgenfer).</description>
    <link>https://dev.to/pgenfer</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%2Fuser%2Fprofile_image%2F1022422%2F19f26d7b-b284-4d8a-9ddc-0a72e9a58363.jpg</url>
      <title>DEV Community: Patric Genfer</title>
      <link>https://dev.to/pgenfer</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pgenfer"/>
    <language>en</language>
    <item>
      <title>Isolating Microservices to Improve their Availability</title>
      <dc:creator>Patric Genfer</dc:creator>
      <pubDate>Tue, 09 Jan 2024 08:30:00 +0000</pubDate>
      <link>https://dev.to/pgenfer/isolating-microservices-to-improve-their-availability-n62</link>
      <guid>https://dev.to/pgenfer/isolating-microservices-to-improve-their-availability-n62</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Designing microservices is always a trade-off between data consistency and service availability. This post shows how data replication and asynchronous communication can enhance service isolation to improve a system’s robustness.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Microservices&lt;/em&gt;, as an architectural paradigm, streamline the development and deployment of complex systems by emphasizing a crucial characteristic: &lt;em&gt;Service Isolation&lt;/em&gt;. This isolation entails each microservice acting as an autonomous unit, encapsulating a distinct part of the business logic. In line with domain-driven design principles, these services also define their own &lt;em&gt;bounded context&lt;/em&gt;, enabling individual development, updates, and maintenance without impacting the wider system.&lt;/p&gt;

&lt;p&gt;However, sometimes, our business cases are too complex to be handled by a single service alone. For solving these more sophisticated use cases, several services must interact and exchange data. Communication then often happens through direct API calls between services, e.g., using synchronous protocols like HTTPS or gRPC.&lt;/p&gt;

&lt;p&gt;This typical interaction pattern creates a strong coupling by making our services dependent. While generally manageable, these dependencies pose a risk: a network error in a low-level service could easily disrupt substantial portions of our system. Moreover, updating interconnected components becomes tough, potentially causing prolonged maintenance downtime due to the ripple effect of a single update across multiple nodes.&lt;/p&gt;

&lt;p&gt;This post will investigate how we could isolate our microservices better and make them more robust against network failures by using asynchronous communication and data redundancy. However, we will also see that improving our system's availability comes with the cost of having temporary data inconsistencies. &lt;/p&gt;

&lt;h2&gt;
  
  
  Our Example System: A Typical Online Shop
&lt;/h2&gt;

&lt;p&gt;As an example system, we will use a small microservice architecture representing a typical online shop backend (and parts of the frontend).&lt;/p&gt;

&lt;p&gt;With our shop, customers can select and order different products. Our focus for today lies only on the checkout process, where customers are presented with a list of their chosen products, their address, and payment method and can then proceed with the actual purchase.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsja4bk7p5u8dh7nse0l8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsja4bk7p5u8dh7nse0l8.png" alt="The checkout page of an online shop, showing the customer's address, their shopping cart and a button to proceed with the checkout." width="800" height="376"&gt;&lt;/a&gt;&lt;/p&gt;
The checkout page of an online shop, showing the customer's address, their shopping cart and a button to proceed with the checkout.



&lt;h3&gt;
  
  
  The Microservice Architecture
&lt;/h3&gt;

&lt;p&gt;Each part of our domain is implemented through a separate service. For instance, the &lt;em&gt;Customer Service&lt;/em&gt; is responsible for storing and managing all customer-relevant master data, like their names, addresses, payment methods, or contact information. Our system uses a &lt;em&gt;shared-nothing&lt;/em&gt; approach, so each service manages its own database others cannot access directly.&lt;/p&gt;

&lt;p&gt;The second service we will focus on is the &lt;em&gt;Order Service&lt;/em&gt;, which implements the whole checkout process, also presenting an overview of the customer address, the payment method, and the chosen products to the user. It requests this information by calling other services directly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flps0ue5fmircljqii4nf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flps0ue5fmircljqii4nf.png" alt="An excerpt of our microservice architecture. The ordering process is implemented as a separate microservice. The user interacts with the service through the checkout-page web frontend." width="749" height="569"&gt;&lt;/a&gt;&lt;/p&gt;

An excerpt of our microservice architecture. The ordering process is implemented as a separate microservice. The user interacts with the service through the checkout-page web frontend.





&lt;p&gt;Processes like shipping, payment, etc., are also extracted into separate services. However, for now we will focus mainly on the &lt;em&gt;Customer&lt;/em&gt; and &lt;em&gt;Order&lt;/em&gt; service.&lt;/p&gt;

&lt;p&gt;Of course, this is just a very minimalistic example architecture. Real-world systems are often much more complex. But for our use case, let's assume this architecture is sufficient.&lt;/p&gt;

&lt;h3&gt;
  
  
  Managing the Checkout Process
&lt;/h3&gt;

&lt;p&gt;While our &lt;em&gt;Order Service&lt;/em&gt; encapsulates most of the logic required for the checkout process, it still needs to collect data like the shipping address or the payment method by calling the &lt;em&gt;Customer Service&lt;/em&gt; directly through a synchronous connection.&lt;/p&gt;

&lt;p&gt;While this communication creates a strong coupling between both services, it guarantees that the address and payment data we present are always up-to-date since the &lt;em&gt;Customer Service&lt;/em&gt; will always provide us with the most recent version of the records stored in its database.&lt;/p&gt;

&lt;p&gt;When the network functions within standard parameters, this solution works well and offers seamless communication. However, network unpredictability, caused by issues like misconfigurations, sudden surges in requests, or potential DDoS attacks, can render servers unreachable. What makes it worse is that the strong coupling we implemented between our services makes our systems even more vulnerable to such network failures:&lt;/p&gt;

&lt;p&gt;Imagine the user has chosen some products and proceeds to the checkout page, where the &lt;em&gt;Order Service&lt;/em&gt; tries to receive the customer's address from the &lt;em&gt;Customer Service&lt;/em&gt;, which, unfortunately, became unresponsive due to a network issue. Instead of showing the correct information, we would rather have to print an error message to the user:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl5vfc1ro0sxi7dg7tua0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl5vfc1ro0sxi7dg7tua0.png" alt="Worst case scenario: We cannot continue the checkout process because the service providing our customer's address is down - Okay, true, the error visualization is a bit striking, but I'm sure you get the idea..." width="800" height="376"&gt;&lt;/a&gt;&lt;/p&gt;
Worst case scenario: We cannot continue the checkout process because the service providing our customer's address is down - Okay, true, the error visualization is a bit striking, but I'm sure you get the idea...





&lt;p&gt;That's really daunting: We used microservices in the first place to make our services more independent and fortify our system against failures, and now, a single service breakdown halts our core business functions. This situation is particularly troubling as it would cost us real money: The customer's purchase was disrupted, potentially leading them to seek the product elsewhere.&lt;/p&gt;

&lt;p&gt;Undoubtedly, the strong interdependence between our services is the root cause of this error. Still, we have to exchange data somehow, so coupling can't be avoided - or is there a better way to transfer information while keeping the coupling to a minimum?&lt;/p&gt;

&lt;p&gt;As you may have guessed, there &lt;em&gt;is&lt;/em&gt; a different approach. But before going into details, we must first dive a bit deeper into distributed systems and their properties - welcome to the &lt;em&gt;CAP theorem&lt;/em&gt;!&lt;/p&gt;

&lt;h2&gt;
  
  
  The CAP Theorem - Trade-Offs in Distributed Systems
&lt;/h2&gt;

&lt;p&gt;The CAP theorem, first postulated by Eric Brewer, is a rule that applies to distributed computer systems. It states that within such a system, it's impossible to guarantee availability and data consistency simultaneously. In case of a network error, one always has to choose between one of the two.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbzwxupmdkdmyafaow30s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbzwxupmdkdmyafaow30s.png" alt="The CAP Theorem describes the trade-offs among Consistency, Availability and Partition Tolerance in Distributed Systems." width="750" height="626"&gt;&lt;/a&gt;&lt;br&gt;The CAP Theorem describes the trade-offs among Consistency, Availability and Partition Tolerance in Distributed Systems.
&lt;br&gt;&lt;/p&gt;

&lt;p&gt;If we look at our example architecture from above, we decided implicitly for &lt;em&gt;consistency&lt;/em&gt;: Our customer data is stored at a single location, and all access goes through the &lt;em&gt;Customer Service&lt;/em&gt;. In that way, we guarantee that customer-related information is always up-to-date. However, according to the CAP theorem, by focusing on consistency, we now can no longer guarantee availability when a network error occurs. If we cannot reach the &lt;em&gt;Customer Service&lt;/em&gt; anymore, our checkout process can't continue. The customer data is still consistent - we just cannot reach it!&lt;/p&gt;

&lt;p&gt;So, was it a good choice to go towards consistency and against availability? Well, it depends... While in many cases, consistency is essential, there are often scenarios where availability may be preferable for the price of having potentially inconsistent data. &lt;/p&gt;

&lt;p&gt;Let's see how we would have to change our system to favor availability and what the implications would be.&lt;/p&gt;

&lt;h2&gt;
  
  
  Redesigning our System towards Availability
&lt;/h2&gt;

&lt;p&gt;While our initial design intended to use one service per bounded context, some of our services were more built around entities than actual use cases. Take, for instance, our &lt;em&gt;Customer Service:&lt;/em&gt; Its primary purpose is to manage customer entities, and as such, it's mainly only a gateway to the underlying database. Services like these are also considered &lt;em&gt;entity services&lt;/em&gt;, and can be problematic as they enforce other services to create strong couplings with them.&lt;/p&gt;

&lt;p&gt;Unfortunately, this coupling also drives our architecture towards Consistency and away from Availability. So let's see how we could untie our connections a bit.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Replicating Data
&lt;/h3&gt;

&lt;p&gt;Our &lt;em&gt;Order Service&lt;/em&gt; is obviously not autonomous enough, requiring additional information from other services to complete the checkout task. To address this, we will establish a dedicated database for the &lt;em&gt;Order Service&lt;/em&gt; where all relevant customer data is replicated.&lt;/p&gt;

&lt;p&gt;By doing so, the Order Service will be self-sufficient without relying on external providers. This setup guarantees that even if the Customer Service is inaccessible, our Order Service will remain operational.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7wx9llbwn2hlnw01d8tm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7wx9llbwn2hlnw01d8tm.png" alt="The Order Service replicates the customer's address in its own data store. In that way, all required data is available during checkout, even if the Customer Service is unreachable." width="500" height="680"&gt;&lt;/a&gt;&lt;br&gt;The Order Service replicates the customer's address in its own data store. In that way, all required data is available during checkout, even if the Customer Service is unreachable.
&lt;br&gt;&lt;/p&gt;

&lt;p&gt;But wait, does that mean that we have to store everything twice? And by replicating the data, are we not violating the DRY principle? Well, that's the price we have to pay for better availability. There is no free lunch, not even when developing complex distributed systems. But note that both services do not necessarily have to store the same view of the data. While the &lt;em&gt;Customer Service&lt;/em&gt; may keep a more detailed version, like a primary or secondary address or even a complete history, the &lt;em&gt;Order Service&lt;/em&gt; only needs the most rudimentary information to complete the checkout.&lt;/p&gt;

&lt;p&gt;While our change makes the service self-containded, it has a drawback: &lt;/p&gt;

&lt;p&gt;Since we now store our data at two locations, how do we keep both databases in sync, e.g., when a customer updates their address? &lt;/p&gt;

&lt;p&gt;Well, to solve this, we have to rethink how our service communication works...&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Using Asynchronous Communication
&lt;/h3&gt;

&lt;p&gt;While we could reduce the interconnection between our two services by using a dedicated database for each, we still need some kind of connection to initially transfer the data and keep it in sync later. Our current communication model uses direct synchronous API calls. Again, this creates a strong dependency because the caller needs to know the exact details - like the endpoint address - of the API it wants to call.&lt;/p&gt;

&lt;p&gt;We can reduce the coupling here by switching from the technical level towards a more domain-driven thinking: A user changing their address is an essential action in our system. Hence, we should also consider it a &lt;em&gt;domain event&lt;/em&gt; that needs to be propagated, and services interested in it should get notified about the change and update their state accordingly, &lt;/p&gt;

&lt;p&gt;This is a good use case for applying the asynchronous &lt;a href="https://umlboard.com/design-patterns/publisher-subscriber.html"&gt;Publisher-Subscriber&lt;/a&gt; pattern: All services are connected to a &lt;em&gt;message broker&lt;/em&gt;. In case of an address change, the &lt;em&gt;Consumer Service&lt;/em&gt; publishes a &lt;em&gt;customerAddressChanged&lt;/em&gt; event, and other participants, like the &lt;em&gt;OrderService&lt;/em&gt;, can subscribe to this type of event and get notified when it gets distributed. Upon arrival, they update their database record, ensuring all services have the same consistent dataset again.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frlemvw93i44asro4wu6h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frlemvw93i44asro4wu6h.png" alt="A change in the customer address results in sending a domain event through the Customer Service. The Order service reacts to this event and updates its address data accordingly." width="500" height="488"&gt;&lt;/a&gt;&lt;br&gt;A change in the customer address results in sending a domain event through the Customer Service. The Order service reacts to this event and updates its address data accordingly.
&lt;br&gt;&lt;/p&gt;

&lt;p&gt;The result is not so bad: In case of a (partial) network failure, the user can continue the checkout process, and the data we operate on should remain up-to-date.&lt;/p&gt;

&lt;p&gt;That's definitely an improvement to our initial design. Still, we're not done yet. There is one crucial edge case we have to take care of.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Handling Data Inconsistencies
&lt;/h3&gt;

&lt;p&gt;By improving our system's availability, we unfortunately reduced its consistency. Think, for instance, what would happens if our message broker goes down due to a network error, or, for whatever reason, a recent address change is not propagated correctly to other services?&lt;/p&gt;

&lt;p&gt;In that case, the &lt;em&gt;Order Service&lt;/em&gt; would show an outdated address during checkout. While the service is still reachable, we can not guarantee that its data is consistent with the rest of the system.&lt;/p&gt;

&lt;p&gt;Luckily, we have an easy workaround: We can add a &lt;em&gt;Change Address&lt;/em&gt; button next to the address field, allowing the user to update the information manually if necessary.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn30c9qljitta11i2fgnf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn30c9qljitta11i2fgnf.png" alt="Letting users manually change their address can be a fallback mechanism in case of inconsistencies." width="800" height="376"&gt;&lt;/a&gt;&lt;br&gt;Letting users manually change their address can be a fallback mechanism in case of inconsistencies.
&lt;br&gt;&lt;/p&gt;

&lt;p&gt;Sure, this may be annoying for the user - &lt;em&gt;"I just updated the address in my profile; why does this not show up here?"&lt;/em&gt; - but it's still better than canceling the whole checkout process due to an unreachable service.&lt;/p&gt;

&lt;p&gt;Of course, before the shipping starts, we somehow have to bring our different addresses in sync again, but this can happen long after the checkout is completed (that's why we speak from eventual consistency here - there will be a point in time when our data is synchronized again, although this may not happen immediately).&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Developing complex distributed systems, especially within microservices, presents a balance between autonomy and interdependence. While synchronous communication ensures data consistency, it creates a strong coupling between services and amplifies vulnerability to network failures.&lt;/p&gt;

&lt;p&gt;Data replication and asynchronous communication can help isolate services and strengthen system resilience but may introduce occasional data inconsistencies. Which approach to choose highly depends on the use case. In our concrete example, there is a clear benefit of being available over being consistent since potential inconsistencies can be corrected manually if necessary. Also, since a customer's address may not change that often, chances that updates are not arriving due to network downtime may be less likely.&lt;/p&gt;

&lt;p&gt;But there are also cases where availability may be more critical: Showing the total order sum requires us to have accurate pricing information at any time. This is a scenario where eventual consistency would not be sufficient enough. The same is true for validating the payment. We must ensure that the payment information our system processes is correct when finishing the order. Otherwise, we cannot continue with the shipping.&lt;/p&gt;

&lt;p&gt;In essence, the optimal choice between consistency and availability always hinges on the specific demands of the concrete domain and business case.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Moving from Electron to Tauri 2</title>
      <dc:creator>Patric Genfer</dc:creator>
      <pubDate>Thu, 27 Apr 2023 19:00:04 +0000</pubDate>
      <link>https://dev.to/pgenfer/moving-from-electron-to-tauri-2-1jbl</link>
      <guid>https://dev.to/pgenfer/moving-from-electron-to-tauri-2-1jbl</guid>
      <description>&lt;p&gt;Part 2: Local Data Storage — Implementing a database backend in Rust for a Tauri application.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Several file-based embedded database wrappers are available for Rust. This article examines some of them and demonstrates how to incorporate them into an application written with Tauri and Rust.&lt;/p&gt;




&lt;p&gt;This is the second post on porting &lt;a href="https://www.umlboard.com"&gt;UMLBoard&lt;/a&gt; to Tauri. While the previous &lt;a href="https://dev.to/pgenfer/moving-from-electron-to-tauri-3791"&gt;article&lt;/a&gt; focused on interprocess communication, this time, we will examine how we could implement the local data storage subsystem with Rust. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;del&gt;Porting inter-process communication to Tauri&lt;/del&gt; (see &lt;a href="https://umlboard.com/blog/"&gt;last post&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Accessing a document-based local data store with Rust (this post!)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Validate the SVG compatibility of different Webview&lt;/li&gt;
&lt;li&gt;Check if Rust has a library for automatic graph layouting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To achieve this, we will first look at some available embedded datastore options for Rust and then see how to integrate them into the &lt;a href="https://github.com/pgenfer/umlboard-tauri-messaging"&gt;prototype&lt;/a&gt; we created in our last post.&lt;/p&gt;

&lt;p&gt;But before we start coding, let’s glimpse at the current status quo:&lt;/p&gt;
&lt;h2&gt;
  
  
  Basic Architecture
&lt;/h2&gt;

&lt;p&gt;The current, Electron-based UMLBoard main process uses a layered architecture split into several application services. Each service accesses the database through a repository for reading and writing user diagrams. The datalayer uses &lt;a href="https://github.com/louischatriot/nedb"&gt;nedb&lt;/a&gt;, (or better, its fork &lt;a href="https://github.com/seald/nedb"&gt;@seald-io/nedb&lt;/a&gt;), an embedded, single-file JSON database.&lt;/p&gt;

&lt;p&gt;This design allows for a better separation between application code and database-specific implementations.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frhwo17wj7tn3pz1l9i38.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frhwo17wj7tn3pz1l9i38.png" alt="UMLBoard's main process uses a layered architecture.&amp;lt;br&amp;gt;
Repositories are used to separate the application logic from the persistence layer." width="800" height="724"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Porting from Typescript to Rust
&lt;/h2&gt;

&lt;p&gt;To port this architecture to Rust, we must reimplement each layer individually. We will do this by defining four subtasks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Find a file-based database implementation in Rust. &lt;/li&gt;
&lt;li&gt;Implement a repository layer between the database and our services.&lt;/li&gt;
&lt;li&gt;Connect the repository with our business logic.&lt;/li&gt;
&lt;li&gt;Integrate everything into our Tauri application.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Following the strategy from our last post, we will go through each step one by one.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Finding a suitable file-based database implementation in Rust.
&lt;/h3&gt;

&lt;p&gt;There are numerous Rust wrappers for both SQL and NoSQL databases. Let's look at some of them.&lt;br&gt;
&lt;small&gt;(Please note this list is by no means complete, so if you think I missed an important one, let me know, and I can add it here.)&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. &lt;a href="https://docs.rs/unqlite/latest/unqlite/"&gt;unqlite&lt;/a&gt;&lt;/strong&gt; A Rust wrapper for the &lt;a href="https://unqlite.org/"&gt;UnQLite&lt;/a&gt; database engine. It looks quite powerful but is not actively developed anymore -- the last commit was a few years ago.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. &lt;a href="https://github.com/PoloDB/PoloDB"&gt;PoloDB&lt;/a&gt;&lt;/strong&gt; A lightweight embedded JSON database. While it's in active development, at the time of writing this (Spring 2023), it doesn't yet support asynchronous data access -- not a huge drawback, given that we're only accessing local files, but let's see what else we have.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. &lt;a href="https://diesel.rs/"&gt;Diesel&lt;/a&gt;&lt;/strong&gt; The de-facto SQL ORM and query builder for Rust. Supports several SQL databases, including SQLite -- unfortunately, the SQLite driver does not yet support asynchronous operations[^1].&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. &lt;a href="https://github.com/SeaQL/sea-orm"&gt;SeaORM&lt;/a&gt;&lt;/strong&gt; Another SQL ORM for Rust with support for SQLite and asynchronous data access, making it a good fit for our needs. Yet, while trying to implement a prototype repository, I realized that defining a generic repository for SeaORM can become quite complex due to the number of required type arguments and constraints.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. &lt;a href="https://bonsaidb.io/"&gt;BonsaiDB&lt;/a&gt;&lt;/strong&gt; A document-based database, currently in alpha but actively developed. Supports local data storage and asynchronous access. Also provides the possibility to implement more complex queries through &lt;em&gt;Views&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. &lt;a href="https://docs.rs/surrealdb/1.0.0-beta.8/surrealdb/index.html"&gt;SurrealDB&lt;/a&gt;&lt;/strong&gt; A wrapper for the &lt;a href="https://surrealdb.com/"&gt;SurrealDB&lt;/a&gt; database engine. Supports local data storage via &lt;a href="https://rocksdb.org/"&gt;RocksDB&lt;/a&gt; and asynchronous operations. &lt;/p&gt;

&lt;p&gt;Among these options, &lt;strong&gt;BonsaiDB&lt;/strong&gt; and &lt;strong&gt;SurrealDB&lt;/strong&gt; look most promising: They support asynchronous data access, don't require a separate process, and have a relatively easy-to-use API. So, let's try to integrate both of them into our application.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Implementing a Repository Layer in Rust
&lt;/h3&gt;

&lt;p&gt;Since we want to test two different database engines, the &lt;a href="https://learn.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/infrastructure-persistence-layer-design"&gt;repository&lt;/a&gt; pattern looks like a good option as it allows us to decouple the database from the application logic. In that way, we can easily switch the underlying database system.&lt;/p&gt;

&lt;p&gt;Defining our repository's behavior in Rust is best achieved with a &lt;a href="https://doc.rust-lang.org/book/ch17-02-trait-objects.html"&gt;trait&lt;/a&gt;. For our proof-of-concept, some default &lt;em&gt;CRUD&lt;/em&gt; operations will be sufficient:&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="c1"&gt;// Trait describing the common behavior of &lt;/span&gt;
&lt;span class="c1"&gt;// a repository. TEntity is the type of&lt;/span&gt;
&lt;span class="c1"&gt;// domain entity handled by this repository.&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;trait&lt;/span&gt; &lt;span class="n"&gt;Repository&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TEntity&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;query_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TEntity&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;query_by_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;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="n"&gt;TEntity&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TEntity&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;TEntity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;edit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TEntity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;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="n"&gt;TEntity&lt;/span&gt;&lt;span class="o"&gt;&amp;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;Our repository is generic over its entity type, allowing us to reuse our implementation for different entities. We need one implementation of this trait for every database engine we want to support. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fetk5j8dn96u5twu86it9.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fetk5j8dn96u5twu86it9.jpg" alt="Using a repository can make it easier to try out different database implementations." width="800" height="406"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's see what the implementation will look like for &lt;em&gt;BonsaiDB&lt;/em&gt; and &lt;em&gt;SurrealDB&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;BonsaiDB&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;First, we declare a struct &lt;em&gt;BonsaiRepository&lt;/em&gt; that holds a reference to BonsaiDB's &lt;code&gt;AsyncDatabase&lt;/code&gt; object we need to interact with our DB.&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="n"&gt;BonsaiRepository&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;'a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TData&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;// gives access to a BonsaiDB database&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt;'a&lt;/span&gt; &lt;span class="n"&gt;AsyncDatabase&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// required as generic type is not (yet) used in the struct&lt;/span&gt;
    &lt;span class="n"&gt;phantom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PhantomData&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TData&lt;/span&gt;&lt;span class="o"&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;Our struct has a generic argument, so we can already specify the entity type upon instance creation. However, since the compiler complains that the type is not used in the struct, we must define a &lt;code&gt;phantom&lt;/code&gt; field to suppress this &lt;a href="https://doc.rust-lang.org/std/marker/struct.PhantomData.html"&gt;error&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But most importantly, we need to implement the &lt;em&gt;Repository&lt;/em&gt; trait for our struct:&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="c1"&gt;// Repository implementation for BonsaiDB database&lt;/span&gt;
&lt;span class="nd"&gt;#[async_trait]&lt;/span&gt;
&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;'a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TData&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Repository&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TData&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;BonsaiRepository&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;'a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TData&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 
    &lt;span class="c1"&gt;// bounds are necessary to comply with BonsaiDB API&lt;/span&gt;
    &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;TData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SerializedCollection&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Contents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TData&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; 
    &lt;span class="n"&gt;Collection&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PrimaryKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="k"&gt;'static&lt;/span&gt;  &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Unpin&lt;/span&gt; &lt;span class="p"&gt;{&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;query_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TData&lt;/span&gt;&lt;span class="o"&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;docs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;TData&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;all_async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.db&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="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;entities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;docs&lt;/span&gt;&lt;span class="nf"&gt;.into_iter&lt;/span&gt;&lt;span class="p"&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;f&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="py"&gt;.contents&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.collect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;entities&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// note that id is not required here, as already part of data&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;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&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;TData&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;new_doc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="nf"&gt;.push_into_async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.db&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="n"&gt;new_doc&lt;/span&gt;&lt;span class="py"&gt;.contents&lt;/span&gt;
    &lt;span class="p"&gt;}&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;edit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TData&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;TData&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;doc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;TData&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;overwrite_async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.db&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="n"&gt;doc&lt;/span&gt;&lt;span class="py"&gt;.contents&lt;/span&gt;
    &lt;span class="p"&gt;}&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;query_by_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;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="n"&gt;TData&lt;/span&gt;&lt;span class="o"&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;doc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;TData&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get_async&lt;/span&gt;&lt;span class="p"&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;self&lt;/span&gt;&lt;span class="py"&gt;.db&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="nf"&gt;.unwrap&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;doc&lt;/span&gt;&lt;span class="py"&gt;.contents&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;Rust doesn't yet support asynchronous trait functions, so we must use the &lt;a href="https://docs.rs/async-trait/latest/async_trait/"&gt;async_trait&lt;/a&gt; here. Our generic type also needs a constraint to signal Rust that we are working with &lt;em&gt;BonsaiDB&lt;/em&gt; entities. These entities consist of a header (which contains meta-data like the id) and the content (holding the domain data). We're going to handle the ids ourselves, so we only need the content object.&lt;/p&gt;

&lt;p&gt;Please also note that I skipped error handling for brevity throughout the prototype. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;SurrealDB&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Our SurrealDB implementation works similarly, but this time, we must also provide the database table name as SurrealDB requires it as part of the primary key.&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="n"&gt;SurrealRepository&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;'a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TData&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;// reference to SurrealDB's Database object&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt;'a&lt;/span&gt; &lt;span class="n"&gt;Surreal&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Db&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;// required as generic type not used&lt;/span&gt;
    &lt;span class="n"&gt;phantom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PhantomData&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TData&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;// this is needed by SurrealDB API to identify objects&lt;/span&gt;
    &lt;span class="n"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;'static&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, our trait implementation mainly wraps the underlaying database API:&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="c1"&gt;// Repository implementation for SurrealDB database&lt;/span&gt;
&lt;span class="nd"&gt;#[async_trait]&lt;/span&gt;
&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;'a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TData&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Repository&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TData&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;SurrealRepository&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;'a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TData&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 
&lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;TData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Sync&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Send&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;DeserializeOwned&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;Serialize&lt;/span&gt; &lt;span class="p"&gt;{&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;query_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TData&lt;/span&gt;&lt;span class="o"&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;entities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TData&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.db&lt;/span&gt;&lt;span class="nf"&gt;.select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.table_name&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="n"&gt;entities&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// here we need the id, although its already stored in the data&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;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&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;TData&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;created&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.db&lt;/span&gt;&lt;span class="nf"&gt;.create&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.table_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="nf"&gt;.content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&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="n"&gt;created&lt;/span&gt;
    &lt;span class="p"&gt;}&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;edit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TData&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;TData&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;updated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.db&lt;/span&gt;&lt;span class="nf"&gt;.update&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.table_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="nf"&gt;.content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&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="n"&gt;updated&lt;/span&gt;
    &lt;span class="p"&gt;}&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;query_by_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;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="n"&gt;TData&lt;/span&gt;&lt;span class="o"&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;entity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.db&lt;/span&gt;&lt;span class="nf"&gt;.select&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.table_name&lt;/span&gt;&lt;span class="p"&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;.await&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;entity&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 complete our repository implementation, we need one last thing: An &lt;em&gt;entity&lt;/em&gt; we can store in the database. For this, we use a simplified version of a widespread UML domain type, a &lt;a href="https://www.uml-diagrams.org/classifier.html"&gt;Classifier&lt;/a&gt;.&lt;br&gt;
This is a general type in UML used to describe concepts like a &lt;em&gt;Class&lt;/em&gt;, &lt;em&gt;Interface&lt;/em&gt;, or &lt;em&gt;Datatype&lt;/em&gt;. Our &lt;code&gt;Classifier&lt;/code&gt; struct contains some typical domain properties, but also an &lt;em&gt;_id&lt;/em&gt; field which serves as primary key.&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;#[derive(Debug,&lt;/span&gt; &lt;span class="nd"&gt;Serialize,&lt;/span&gt; &lt;span class="nd"&gt;Deserialize,&lt;/span&gt; &lt;span class="nd"&gt;Default,&lt;/span&gt; &lt;span class="nd"&gt;Collection)]&lt;/span&gt;
&lt;span class="nd"&gt;#[collection(&lt;/span&gt; &lt;span class="c1"&gt;// custom key definition for BonsaiDB&lt;/span&gt;
    &lt;span class="nd"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"classifiers"&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt; 
    &lt;span class="nd"&gt;primary_key&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;String,&lt;/span&gt; 
    &lt;span class="nd"&gt;natural_id&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="nd"&gt;classifier:&lt;/span&gt; &lt;span class="err"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nd"&gt;Classifier&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="nd"&gt;Some(classifier&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nd"&gt;_id&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nd"&gt;clone()))]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Classifier&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Point&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;is_interface&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;custom_dimension&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="n"&gt;Dimension&lt;/span&gt;&lt;span class="o"&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;To tell &lt;em&gt;BonsaiDB&lt;/em&gt; that we manage entity ids through the &lt;code&gt;_id&lt;/code&gt; field, we need to decorate our type with an additional macro.&lt;br&gt;
While having our own ids requires a bit more work on our side, it helps us to abstract database-specific implementations away and makes adding new database engines easier.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. Connect the repository with our business logic
&lt;/h3&gt;

&lt;p&gt;To connect the database backend with our application logic, we inject the &lt;em&gt;Repository&lt;/em&gt; trait into the &lt;em&gt;ClassifierService&lt;/em&gt; and narrow the type argument to &lt;em&gt;Classifier&lt;/em&gt;. The actual implementation type of the repository (and thus, its size) is not known at compile time, so we have to use the &lt;code&gt;dyn&lt;/code&gt; keyword in the declaration.&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="c1"&gt;// classifier service holding a typed repository&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;ClassifierService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// constraints required by Tauri to support multi threading&lt;/span&gt;
    &lt;span class="n"&gt;repository&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Box&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;dyn&lt;/span&gt; &lt;span class="n"&gt;Repository&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Classifier&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Send&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Sync&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;ClassifierService&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;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Box&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;dyn&lt;/span&gt; &lt;span class="n"&gt;Repository&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Classifier&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Send&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Sync&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;Self&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
        &lt;span class="k"&gt;Self&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;repository&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;Since this is only for demonstration purposes, our service will delegate most of the work to the repository without any sophisticated business logic. For managing our entities' primary keys, we rely on the &lt;a href="https://docs.rs/uuid/latest/uuid/"&gt;uuid&lt;/a&gt; crate to generate unique ids.&lt;/p&gt;

&lt;p&gt;The following snippet contains only an excerpt, for the complete implementation, please see the &lt;a href="https://github.com/pgenfer/umlboard-tauri-local-storage"&gt;Github repository&lt;/a&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="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;ClassifierService&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;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;create_new_classifier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&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;Classifier&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
        &lt;span class="c1"&gt;// we have to manage the ids on our own, so create a new one here&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Uuid&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new_v4&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.to_string&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;new_classifier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.repository&lt;/span&gt;&lt;span class="nf"&gt;.insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Classifier&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; 
            &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;new_name&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; 
            &lt;span class="n"&gt;is_interface&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
            &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="nn"&gt;Default&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;()&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;id&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;new_classifier&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;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;update_classifier_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&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;Classifier&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;classifier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.repository&lt;/span&gt;&lt;span class="nf"&gt;.query_by_id&lt;/span&gt;&lt;span class="p"&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;.await&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;classifier&lt;/span&gt;&lt;span class="py"&gt;.name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new_name&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="c1"&gt;// we need to copy the id because "edit" owns the containing struct&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;classifier&lt;/span&gt;&lt;span class="py"&gt;._id&lt;/span&gt;&lt;span class="nf"&gt;.clone&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;updated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.repository&lt;/span&gt;&lt;span class="nf"&gt;.edit&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;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;classifier&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;updated&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;h3&gt;
  
  
  4. Integrate everything into our Tauri application
&lt;/h3&gt;

&lt;p&gt;Let’s move on to the last part, where we will assemble everything together to get our application up and running.&lt;/p&gt;

&lt;p&gt;For this prototype, we will focus only on two simple use cases:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;(1)&lt;/strong&gt; On application startup, the main process sends all available classifiers to the webview (a new classifier will automatically be created if none exists), and&lt;br&gt;
&lt;strong&gt;(2)&lt;/strong&gt; editing the classifier's name via the edit field will update the entity in the database.&lt;/p&gt;

&lt;p&gt;See the following diagram for the complete workflow:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F75svihmn538lk7hvzala.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F75svihmn538lk7hvzala.jpg" alt="Program flow when user starts the application and edits a classifier." width="800" height="304"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The second use case may sound familiar: We already implemented it partially in our last post, but without persisting the changes to a database backend. &lt;/p&gt;

&lt;p&gt;Last time, our service implemented an &lt;em&gt;ActionHandler&lt;/em&gt; trait to handle incoming actions from the webview. While this approach worked, it was limited to only a single type of action, the &lt;em&gt;ClassifierActions&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;This time, we have more than one action type: &lt;em&gt;ApplicationActions&lt;/em&gt; that control the overall program flow and &lt;em&gt;ClassifierActions&lt;/em&gt; for behavior specific to classifier entities.&lt;/p&gt;

&lt;p&gt;To handle both types uniformly, we split our trait into a non-generic &lt;em&gt;ActionDispatcher&lt;/em&gt; responsible for routing actions to their corresponding handler, and an &lt;em&gt;ActionHandler&lt;/em&gt; with the actual domain logic. &lt;/p&gt;

&lt;p&gt;Our service must implement both traits, first the dispatcher&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="c1"&gt;// Dispatcher logic to choose the correct handler &lt;/span&gt;
&lt;span class="c1"&gt;// depending on the action's domain&lt;/span&gt;
&lt;span class="nd"&gt;#[async_trait]&lt;/span&gt;
&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;ActionDispatcher&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;ClassifierService&lt;/span&gt; &lt;span class="p"&gt;{&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;dispatch_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Value&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;Value&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;CLASSIFIER_DOMAIN&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nn"&gt;ActionHandler&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ClassifierAction&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;convert_and_handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&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="k"&gt;else&lt;/span&gt;  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;APPLICATION_DOMAIN&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nn"&gt;ActionHandler&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ApplicationAction&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;convert_and_handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&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="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// this should normally not happen, either &lt;/span&gt;
            &lt;span class="c1"&gt;// throw an error or change return type to Option&lt;/span&gt;
            &lt;span class="nd"&gt;todo!&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;and then the &lt;em&gt;ActionHandler&lt;/em&gt; for every type of action we want to support:&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="c1"&gt;// handling of classifier related actions&lt;/span&gt;
&lt;span class="nd"&gt;#[async_trait]&lt;/span&gt;
&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;ActionHandler&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ClassifierAction&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;ClassifierService&lt;/span&gt; &lt;span class="p"&gt;{&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;handle_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ClassifierAction&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;ClassifierAction&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;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// rename the entity and return the new entity state&lt;/span&gt;
            &lt;span class="nn"&gt;ClassifierAction&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;RenameClassifier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&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;classifier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="nf"&gt;.update_classifier_name&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;data&lt;/span&gt;&lt;span class="py"&gt;.id&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;data&lt;/span&gt;&lt;span class="py"&gt;.new_name&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="nn"&gt;ClassifierAction&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;ClassifierRenamed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;EditNameDto&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;classifier&lt;/span&gt;&lt;span class="py"&gt;._id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;classifier&lt;/span&gt;&lt;span class="py"&gt;.name&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="c1"&gt;// cancel the rename operation by returning the original name&lt;/span&gt;
            &lt;span class="nn"&gt;ClassifierAction&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;CancelClassifierRename&lt;/span&gt;&lt;span class="p"&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;=&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;classifier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="nf"&gt;.get_by_id&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;id&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="nn"&gt;ClassifierAction&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;ClassifierRenameCanceled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;EditNameDto&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;classifier&lt;/span&gt;&lt;span class="py"&gt;.name&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="c1"&gt;// return error if we don't know how to handle the action&lt;/span&gt;
            &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;ClassifierAction&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ClassifierRenameError&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&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="c1"&gt;// handling of actions related to application workflow&lt;/span&gt;
&lt;span class="nd"&gt;#[async_trait]&lt;/span&gt;
&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;ActionHandler&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ApplicationAction&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;ClassifierService&lt;/span&gt; &lt;span class="p"&gt;{&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;handle_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ApplicationAction&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;ApplicationAction&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;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nn"&gt;ApplicationAction&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ApplicationReady&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// implementation omitted&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;ApplicationAction&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ApplicationLoadError&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&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;The dispatcher's &lt;em&gt;ActionHandler&lt;/em&gt; calling syntax seems odd but is needed to specify the correct trait implementation.&lt;/p&gt;

&lt;p&gt;With the &lt;em&gt;ActionDispatcher&lt;/em&gt; trait, we can now define our Tauri app state. It contains only a dictionary where we store one dispatcher per action domain. Consequently, our &lt;em&gt;ClassifierService&lt;/em&gt; must be registered twice since it can handle actions of two domains. &lt;/p&gt;

&lt;p&gt;Since the dictionary owns its values, we use an &lt;em&gt;Atomic Reference Counter&lt;/em&gt; (&lt;a href="https://doc.rust-lang.org/std/sync/struct.Arc.html"&gt;Arc&lt;/a&gt;) to store our service references.&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="c1"&gt;// the application context our Tauri Commands will have access to&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;ApplicationContext&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;action_dispatchers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;HashMap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&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="k"&gt;dyn&lt;/span&gt; &lt;span class="n"&gt;ActionDispatcher&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Sync&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Send&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// initialize the application context with our action dispatchers&lt;/span&gt;
&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;ApplicationContext&lt;/span&gt; &lt;span class="p"&gt;{&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;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;Self&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
        &lt;span class="c1"&gt;// create our database and repository&lt;/span&gt;
        &lt;span class="c1"&gt;// note: to use BonsaiDB instead, replace the database and repository&lt;/span&gt;
        &lt;span class="c1"&gt;// here with the corresponding implementation&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;surreal_db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Surreal&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;new&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"umlboard.db"&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="n"&gt;surreal_db&lt;/span&gt;&lt;span class="nf"&gt;.use_ns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"uml_ns"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.use_db&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"uml_db"&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="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;repository&lt;/span&gt; &lt;span class="o"&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;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;SurrealRepository&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="nn"&gt;Box&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="n"&gt;surreal_db&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;"classifiers"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

        &lt;span class="c1"&gt;// create the classifier application service&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Arc&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="nn"&gt;ClassifierService&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="n"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="c1"&gt;// setup our action dispatcher map and add the service for each&lt;/span&gt;
        &lt;span class="c1"&gt;// domain it can handle&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;dispatchers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; 
            &lt;span class="n"&gt;HashMap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&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="k"&gt;dyn&lt;/span&gt; &lt;span class="n"&gt;ActionDispatcher&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Sync&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Send&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 
            &lt;span class="nn"&gt;HashMap&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="n"&gt;dispatchers&lt;/span&gt;&lt;span class="nf"&gt;.insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CLASSIFIER_DOMAIN&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
        &lt;span class="n"&gt;dispatchers&lt;/span&gt;&lt;span class="nf"&gt;.insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;APPLICATION_DOMAIN&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
        &lt;span class="k"&gt;Self&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;action_dispatchers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;dispatchers&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;The last part is easy again: We update our Tauri command and choose the correct dispatcher depending on the incoming action:&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;#[tauri::command]&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;ipc_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IpcMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;'_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ApplicationContext&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IpcMessage&lt;/span&gt;&lt;span class="p"&gt;,&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="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;dispatcher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="py"&gt;.action_dispatchers&lt;/span&gt;&lt;span class="nf"&gt;.get&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;message&lt;/span&gt;&lt;span class="py"&gt;.domain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&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;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dispatcher&lt;/span&gt;&lt;span class="nf"&gt;.dispatch_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="py"&gt;.domain&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="py"&gt;.action&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="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IpcMessage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="py"&gt;.domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;response&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;Et voilá: &lt;br&gt;
It was quite some work, but everything fits together now:&lt;br&gt;
We can load our entities from the database, let the user change an entity's name, and persist the changes to our database so that they are still available the next time the application starts.&lt;/p&gt;

&lt;p&gt;Jobs done!&lt;/p&gt;

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

&lt;p&gt;In this post, we created a proof-of-concept for adding a database backend for maintaining our application state to a Tauri app. Using a repository trait, we were able to decouple our application logic from our database, letting us switch between different database backends.&lt;/p&gt;

&lt;p&gt;However, our repository interface is rather minimalist, but more complex scenarios would definitely require a more advanced query API. But that's something for another post...&lt;/p&gt;

&lt;p&gt;Also, migrating from an old domain model to a new one would be another good topic for a follow-up article. &lt;/p&gt;

&lt;p&gt;What about you? Have you already worked with Tauri and Rust?&lt;br&gt;
Please share your experience in the comments or via Twitter &lt;a href="https://twitter.com/UMLBoard"&gt;@umlboard&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Image of a local database generated with &lt;a href="https://www.bing.com/create"&gt;Bing Image Creator&lt;/a&gt;.&lt;/em&gt;&lt;br&gt;
&lt;em&gt;Source code for this project is available on &lt;a href="https://github.com/pgenfer/umlboard-tauri-local-storage"&gt;Github&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://umlboard.com/blog/moving-from-electron-to-tauri-2/"&gt;https://umlboard.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>rust</category>
      <category>database</category>
      <category>surrealdb</category>
      <category>bonsaidb</category>
    </item>
    <item>
      <title>Moving From Electron to Tauri</title>
      <dc:creator>Patric Genfer</dc:creator>
      <pubDate>Tue, 07 Feb 2023 13:50:29 +0000</pubDate>
      <link>https://dev.to/pgenfer/moving-from-electron-to-tauri-3791</link>
      <guid>https://dev.to/pgenfer/moving-from-electron-to-tauri-3791</guid>
      <description>&lt;p&gt;Part 1: Interprocess Communication — porting a Typescript-based message system of an Electron App to Tauri and Rust.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Tauri is a Rust-based Electron alternative. &lt;br&gt;
However, porting an existing Electron application to Tauri requires some work. This post explains how UMLBoard's message system used for inter-process communication could be ported to Rust without much effort.&lt;/p&gt;



&lt;p&gt;Many people would agree that native apps - if done right - provide better user experience and performance compared to hybrid or cross-platform solutions.&lt;/p&gt;

&lt;p&gt;However, having separate apps also means maintaining different code bases, each written in its own language.&lt;br&gt;
Keeping them all in sync is quite a lot of work for a single developer - usually more than what we've planned for our side projects.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.electronjs.org/de/" rel="noopener noreferrer"&gt;Electron&lt;/a&gt; framework is a compromise: It provides a convenient way to write platform-independent applications using a web-based technology stack most people are familiar with. Also, the framework is very mature and actively supported by a large community.&lt;/p&gt;

&lt;p&gt;But nevertheless, it’s only a compromise:&lt;br&gt;
Its platform-independence comes with the cost of larger binaries and higher memory consumption compared to native apps. Take, for instance, UMLBoard's macOS binaries:&lt;br&gt;
The universal platform package ends up with a total size of 250 MB - that's a massive number for a &lt;em&gt;lightweight&lt;/em&gt; drawing tool...&lt;/p&gt;

&lt;p&gt;Still, alternatives with the same level of maturity as Electron are relatively rare. &lt;a href="https://tauri.app/" rel="noopener noreferrer"&gt;Tauri&lt;/a&gt;, however, is one of these alternatives that looks very promising.&lt;/p&gt;
&lt;h2&gt;
  
  
  A First look at Tauri
&lt;/h2&gt;

&lt;p&gt;While Electron and Tauri share some similarities - like using separate processes for their core and rendering logic - they follow divergent philosophies regarding bundle size.&lt;/p&gt;

&lt;p&gt;Instead of deplyoing your application with a complete browser frontend, Tauri relies on the built-in Webviews the underlying operating systems provide, resulting in much smaller applications. Despite that, Tauri uses &lt;a href="https://www.rust-lang.org/" rel="noopener noreferrer"&gt;Rust&lt;/a&gt; as the language of choice for its Core process, resulting in better performance compared to Electron's node.js backend. &lt;/p&gt;

&lt;p&gt;While porting UMLBoard from Electron to Rust won't happen overnight, exploring how some of its core concepts could be translated from TypeScript to Rust would still be interesting.&lt;/p&gt;

&lt;p&gt;The following list contains some crucial features of UMLBoard. Some of them are real show-stoppers in case they don't work.&lt;br&gt;
A possible port would have to deal with these issues first.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[x] Porting the inter-process communication to Tauri (this post here!)&lt;/li&gt;
&lt;li&gt;[ ] Accessing a document-based local data store with Rust&lt;/li&gt;
&lt;li&gt;[ ] Validate the SVG compatibility of different Webview&lt;/li&gt;
&lt;li&gt;[ ] Check if Rust has a library for automatic graph layouting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The remaining post is dedicated to the first bullet point: &lt;br&gt;
We will investigate how UMLBoard’s existing inter-process communication could be ported to Tauri. The other topics may be the subjects of further articles.&lt;/p&gt;

&lt;p&gt;Ok, but enough said, let's start!&lt;/p&gt;
&lt;h2&gt;
  
  
  Sending Messages between Electron Processes
&lt;/h2&gt;

&lt;p&gt;UMLBoard's current implementation uses a React front end with &lt;a href="https://redux.js.org/" rel="noopener noreferrer"&gt;Redux&lt;/a&gt; state management.&lt;br&gt;
Every user interaction dispatches an action that a reducer translates into a change resulting in a new front end state.&lt;/p&gt;

&lt;p&gt;If, for instance, a user starts editing a classifier's name, a &lt;em&gt;renamingClassifier&lt;/em&gt; action gets dispatched. The &lt;em&gt;classifier&lt;/em&gt; reducer reacts to this action and updates the classifier's name, triggering a rerender of the component. &lt;br&gt;
So far, this is all standard Redux behavior.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg8reffye7uqh92i8r1hl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg8reffye7uqh92i8r1hl.png" alt="A user input dispatches a redux action that leads to a state update in the front end." width="800" height="243"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;b&gt;A user input dispatches a redux action that leads to a state update in the front end.&lt;/b&gt;



&lt;p&gt;&lt;br&gt;&lt;br&gt;
But UMLBoard even goes one step further and uses the same technique for sending notifications to Electron's main process.&lt;/p&gt;

&lt;p&gt;Taking our previous example, when the user hits the ENTER key, a &lt;em&gt;renameClassifier&lt;/em&gt; action is dispatched, indicating that the user finished editing. &lt;/p&gt;

&lt;p&gt;This time, however, the action gets processed by a custom middleware instead of a reducer. The middleware opens an IPC channel and sends the action directly to the main process.&lt;br&gt;
There, a previously registered handler reacts to the incoming action and processes it. It updates the domain model accordingly and persists the new state to the local datastore.&lt;/p&gt;

&lt;p&gt;If all that goes well, a response action is sent back on the same channel. The middleware receives the response and dispatches it like a regular action. This keeps the front-end state in sync again with the domain state.&lt;/p&gt;

&lt;p&gt;See the following diagram for an overview of this process:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F45pkrdcfr5jhr5r03g7g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F45pkrdcfr5jhr5r03g7g.png" alt="Inter Process Communication between renderer and main process in UMLBoard." width="800" height="436"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;b&gt;Inter Process Communication between renderer and main process in UMLBoard.&lt;/b&gt;



&lt;p&gt;&lt;br&gt;&lt;br&gt;
It might look a bit odd to extend  Redux to the main process, but from the view of a lazy developer like me, it has some benefits:&lt;/p&gt;

&lt;p&gt;Since both mechanisms, Redux and IPC, rely on plain serializable JSON objects, everything that goes through a Redux dispatcher can also go through an IPC channel. &lt;br&gt;
This is very convenient as it means we can reuse our actions and their payloads without writing additional data conversions or DTO objects.&lt;br&gt;
We also don't have to write any custom dispatching logic. We only need a simple middleware to connect the Redux front end with the IPC channel.&lt;/p&gt;

&lt;p&gt;This action-based messaging system is the backbone of UMLBoard's process communication, so let's see how we can achieve this in Tauri...&lt;/p&gt;
&lt;h2&gt;
  
  
  Porting to Tauri
&lt;/h2&gt;

&lt;p&gt;For our proof-of-concept, we will create a small demo application in Tauri. The app will use a React/Redux front end with a single text field. Pressing a button will send the changes to the backend (Tauri's Core process).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnbefdyhgxwc99is35oe6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnbefdyhgxwc99is35oe6.png" alt="The example app we're going to implement." width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;b&gt;The example app we're going to implement.&lt;/b&gt;



&lt;p&gt;&lt;br&gt;&lt;br&gt;
We are only interested in inter-process communication, so we will skip all state tracking in the core process (this will be part of a future blog entry...). That's why our &lt;em&gt;Cancel&lt;/em&gt; method behaves a bit weird as it will always restore the original class name. But for proving our concept, this should be sufficient.&lt;/p&gt;

&lt;p&gt;We basically have to implement four tasks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Declare Rust equivalents of our Redux actions&lt;/li&gt;
&lt;li&gt;Sending actions from Webview to Core process&lt;/li&gt;
&lt;li&gt;Handling incoming actions in the Core process&lt;/li&gt;
&lt;li&gt;Sending response actions back to Webview&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's go through the implementation step by step.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Declare Rust equivalents of Redux actions&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Redux actions are plain JSON objects with a string identifying their &lt;em&gt;type&lt;/em&gt; and a field holding their payload. &lt;/p&gt;

&lt;p&gt;Rust has a similar concept we could use to mimic this behavior, the &lt;a href="https://doc.rust-lang.org/book/ch06-01-defining-an-enum.html" rel="noopener noreferrer"&gt;Enum&lt;/a&gt; type. Enums in Rust are more powerful than in other languages because they allow storing additional data for each variant.&lt;/p&gt;

&lt;p&gt;In that way, we could define our Redux actions as a single Enum, where each variant represents an individual type of action.&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;#[derive(Serialize,&lt;/span&gt; &lt;span class="nd"&gt;Deserialize,&lt;/span&gt; &lt;span class="nd"&gt;Display)]&lt;/span&gt;
&lt;span class="nd"&gt;#[serde(rename_all(serialize=&lt;/span&gt;&lt;span class="s"&gt;"camelCase"&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt; &lt;span class="nd"&gt;deserialize=&lt;/span&gt;&lt;span class="s"&gt;"camelCase"&lt;/span&gt;&lt;span class="nd"&gt;))]&lt;/span&gt;
&lt;span class="nd"&gt;#[serde(tag&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt; &lt;span class="nd"&gt;content&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"payload"&lt;/span&gt;&lt;span class="nd"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;ClassiferAction&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// received from webview when user changed name&lt;/span&gt;
  &lt;span class="nf"&gt;RenameClassifier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EditNameDto&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="c1"&gt;// user canceled name change operation&lt;/span&gt;
  &lt;span class="n"&gt;CancelClassifierRename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// response action after successful change&lt;/span&gt;
  &lt;span class="nf"&gt;ClassifierRenamed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EditNameDto&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="c1"&gt;// response action for cancel operation&lt;/span&gt;
  &lt;span class="nf"&gt;ClassifierRenameCanceled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EditNameDto&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="c1"&gt;// error response&lt;/span&gt;
  &lt;span class="n"&gt;ClassifierRenameError&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To convert our Redux action into a Rust Enum and vice versa, we can use Rust's &lt;code&gt;serde&lt;/code&gt; macro: We specify that the variant's name should be serialized into a &lt;em&gt;type&lt;/em&gt; field and its data into a field called &lt;em&gt;payload&lt;/em&gt;. This corresponds precisely to the scheme we use to define our Redux actions.&lt;/p&gt;

&lt;p&gt;But we can even go one step further by using the &lt;a href="https://docs.rs/ts-rs/latest/ts_rs/#" rel="noopener noreferrer"&gt;ts-rs&lt;/a&gt; crate. This library can generate the TypeScript interfaces for the action payloads straight from our Rust code. We don't have to write a single line of TypeScript code for this. &lt;br&gt;
That's really neat!&lt;/p&gt;

&lt;p&gt;Decorating our Rust struct with the relevant macros&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;#[derive(TS)]&lt;/span&gt;
&lt;span class="nd"&gt;#[ts(export,&lt;/span&gt; &lt;span class="nd"&gt;rename_all=&lt;/span&gt;&lt;span class="s"&gt;"camelCase"&lt;/span&gt;&lt;span class="nd"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;EditNameDto&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;new_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;gives us the following auto-generated TypeScript interface for our action payloads:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;EditNameDto&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;newName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ok, we have the correct data types on both edges of our communication channel, let's now see how we can send data between them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Sending actions from Webview to Core process&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Interprocess communication in Tauri is done through &lt;em&gt;commands&lt;/em&gt;. These commands are implemented as Rust functions and can be called within the Webviews using the &lt;a href="https://tauri.app/v1/guides/features/command/" rel="noopener noreferrer"&gt;invoke&lt;/a&gt; API.&lt;/p&gt;

&lt;p&gt;One problem we face is that the &lt;em&gt;Redux Toolkit&lt;/em&gt; generates the type for identifying an action by concatenating the name of the slice where the action is defined with the action's name. &lt;br&gt;
In our case, the resulting type would therefore be &lt;em&gt;classifier/renameClassifier&lt;/em&gt; instead of just &lt;em&gt;renameClassifier&lt;/em&gt;. This first part, &lt;em&gt;classifier&lt;/em&gt;, is also called the &lt;em&gt;domain&lt;/em&gt; to which this action belongs.&lt;/p&gt;

&lt;p&gt;Unfortunately, this naming convention does not work for Rust, as it would result in invalid names for our Enum options.&lt;br&gt;
We can avoid this by separating the domain from the action type and wrapping everything up in an additional object, the &lt;em&gt;IpcMessage&lt;/em&gt;, before submitting.&lt;/p&gt;

&lt;p&gt;See the following diagram for the complete invocation process.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd7u3bl1qn1nhuah4unn7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd7u3bl1qn1nhuah4unn7.png" alt="Invoking a Tauri command from the Webview process." width="800" height="819"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;b&gt;Invoking a Tauri command from the Webview process.&lt;/b&gt;



&lt;p&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;3. Handling incoming actions in the Core process&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;On the backend side, we must also define a Rust struct for our &lt;em&gt;IpcMessage&lt;/em&gt;. Since we don't know the concrete type of the payload yet, we keep it stored as a JSON value and parse it later when 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="c1"&gt;// data structure to store incoming messages&lt;/span&gt;
&lt;span class="nd"&gt;#[derive(Deserialize,&lt;/span&gt; &lt;span class="nd"&gt;Serialize)]&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;IpcMessage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Value&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can now define the signature of the method for our Tauri command. Our function, &lt;em&gt;ipc_message&lt;/em&gt;, will receive an &lt;em&gt;IpcMessage&lt;/em&gt;, processes it somehow, and at the end, returns another &lt;em&gt;IpcMessage&lt;/em&gt; as a response.&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;#[tauri::command]&lt;/span&gt;
&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;ipc_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IpcMessage&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;IpcMessage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// TODO: implement&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ok, but what would the actual implementation look like?&lt;/p&gt;

&lt;p&gt;The function should take the domain from the message, see if a handler is registered for this domain and if yes, call the corresponding handler with the action stored inside our &lt;em&gt;IpcMessage&lt;/em&gt;. Since we will have many different domains and handlers later, it makes sense to minimize the implementation effort by extracting common behavior into a separate &lt;em&gt;ActionHandler&lt;/em&gt; trait.&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="c1"&gt;// trait that must be implemented by every domain service&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;trait&lt;/span&gt; &lt;span class="n"&gt;ActionHandler&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// specifies the domain actions this trait can handle&lt;/span&gt;
    &lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;TAction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;DeserializeOwned&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;Serialize&lt;/span&gt; &lt;span class="o"&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;fmt&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Display&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// the domain for which this handler is responsible&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// must be implemented by derived structs&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;handle_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;Self&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;TAction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; 
    &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;Self&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;TAction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;serde_json&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Error&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;// boiler plate code for converting actions to and from json  &lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;receive_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json_action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; 
    &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;serde_json&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Error&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;// convert json to action&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;incoming&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;Self&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;TAction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;serde_json&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json_action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="c1"&gt;// call action specific handler&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="nf"&gt;.handle_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;incoming&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="c1"&gt;// convert response to json&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;response_json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;serde_json&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;to_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response_json&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;The trait uses the &lt;em&gt;TemplateMethod&lt;/em&gt; design pattern: The &lt;em&gt;receive_action&lt;/em&gt; specifies the general workflow for converting the action. The &lt;em&gt;handle_action&lt;/em&gt; method contains the actual logic for processing a specific action.&lt;/p&gt;

&lt;p&gt;In our case, a &lt;em&gt;ClassifierService&lt;/em&gt; could be responsible for processing all actions of the domain &lt;em&gt;classifier&lt;/em&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="c1"&gt;// ClassifierService handles all classifier specific actions&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;ClassifierService&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;ClassifierService&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;update_classifier_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&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="p"&gt;{&lt;/span&gt;
        &lt;span class="cm"&gt;/* TODO: implement domain logic here */&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;ActionHandler&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;ClassifierService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;TActionType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ClassifierAction&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;CLASSIFIER_DOMAIN&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;handle_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;Self&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;TActionType&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; 
    &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;Self&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;TActionType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;serde_json&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Error&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;// here happens the domain logic&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nn"&gt;ClassifierAction&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;RenameClassifier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&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="c1"&gt;// update data store&lt;/span&gt;
                &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="nf"&gt;.update_classifier_name&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;data&lt;/span&gt;&lt;span class="py"&gt;.new_name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="nn"&gt;ClassifierAction&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;ClassifierRenamed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="nn"&gt;ClassifierAction&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;CancelClassifierRename&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt;
                &lt;span class="c1"&gt;// user has canceled, return previous name&lt;/span&gt;
                &lt;span class="c1"&gt;// here we just return an example text&lt;/span&gt;
                &lt;span class="nn"&gt;ClassifierAction&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;ClassifierRenameCanceled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;EditNameDto&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;new_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Old Classname"&lt;/span&gt;&lt;span class="nf"&gt;.to_string&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="c1"&gt;// if front end sends different actions, something went wrong&lt;/span&gt;
            &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;ClassifierAction&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ClassifierRenameError&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&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;&lt;strong&gt;4. Sending response actions back to Webview&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We're almost done. We have the signature of our Tauri command and the code we need to handle an action and generate a response. If we glue everything together, our final &lt;em&gt;ipc_message&lt;/em&gt; function may look like the following snippet:&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;#[tauri::command]&lt;/span&gt;
&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;ipc_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IpcMessage&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;IpcMessage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// This code is just for demonstration purposes.&lt;/span&gt;
    &lt;span class="c1"&gt;// In a real scenario, this would be done during application startup.&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ClassifierService&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;handlers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;HashMap&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="n"&gt;handlers&lt;/span&gt;&lt;span class="nf"&gt;.insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="nf"&gt;.domain&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;service&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// this is were our actual command begins&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;message_handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;handlers&lt;/span&gt;&lt;span class="nf"&gt;.get&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;message&lt;/span&gt;&lt;span class="py"&gt;.domain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&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;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;message_handler&lt;/span&gt;&lt;span class="nf"&gt;.receive_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="py"&gt;.action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;IpcMessage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;message_handler&lt;/span&gt;&lt;span class="nf"&gt;.domain&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;response&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;Please note that the service creation and registration code are only for demonstration purposes. In an actual application, we would instead use a &lt;a href="https://tauri.app/v1/guides/features/command#accessing-managed-state" rel="noopener noreferrer"&gt;managed state&lt;/a&gt; to store our action handlers during application startup.&lt;/p&gt;

&lt;p&gt;We also omitted the error handling here to keep the code simple. However, there are quite some scenarios we should check, e.g., what should happen if no handler is found, or how should we proceed if parsing an action into an enum goes wrong, etc.&lt;/p&gt;

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

&lt;p&gt;Our proof-of-concept was successful! Sure, some parts of the implementation can be tweaked, but porting UMLBoard's IPC messaging from Electron/TypeScript to Tauri/Rust is definitely manageable.&lt;/p&gt;

&lt;p&gt;Rust's enums are an elegant and type-safe way to implement our message system. We only have to ensure that potential serialization errors are handled when converting JSON objects into our enum variants.&lt;/p&gt;

&lt;p&gt;In the next post in this series, we will try to use a document-based local database to store our domain model.&lt;br&gt;
Hopefully, by then, I'll finally understand how the borrow checker works...&lt;/p&gt;

&lt;p&gt;&lt;br&gt;&lt;br&gt;
What's your opinion on that? Have you already worked with Tauri and Rust, and what were your experiences? Please share your thoughts in the comments or via Twitter &lt;a href="https://twitter.com/UMLBoard" rel="noopener noreferrer"&gt;@umlboard&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;br&gt;&lt;br&gt;
Title Image from &lt;a href="https://www.wallpaperflare.com/brown-r-logo-rust-code-logo-programming-language-wallpaper-198887" rel="noopener noreferrer"&gt;Wallpaper Flare&lt;/a&gt;.&lt;br&gt;
&lt;em&gt;Source code for this project is available on &lt;a href="https://github.com/pgenfer/umlboard-tauri-messaging" rel="noopener noreferrer"&gt;Github&lt;/a&gt;.&lt;/em&gt;&lt;br&gt;
&lt;em&gt;Originally published at &lt;a href="https://www.umlboard.com/blog/moving-from-electron-to-tauri-1/" rel="noopener noreferrer"&gt;https://www.umlboard.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devto</category>
      <category>announcement</category>
      <category>offers</category>
    </item>
  </channel>
</rss>
