<?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: Sebastien Lato</title>
    <description>The latest articles on DEV Community by Sebastien Lato (@sebastienlato).</description>
    <link>https://dev.to/sebastienlato</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%2F3628025%2F95b780f6-194f-4c29-b5ed-0dd3643f5be8.png</url>
      <title>DEV Community: Sebastien Lato</title>
      <link>https://dev.to/sebastienlato</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sebastienlato"/>
    <language>en</language>
    <item>
      <title>SwiftUI Rate Limiting &amp; Backpressure (Protect Your Backend From Your Own App)</title>
      <dc:creator>Sebastien Lato</dc:creator>
      <pubDate>Sun, 08 Mar 2026 17:13:44 +0000</pubDate>
      <link>https://dev.to/sebastienlato/swiftui-rate-limiting-backpressure-protect-your-backend-from-your-own-app-aha</link>
      <guid>https://dev.to/sebastienlato/swiftui-rate-limiting-backpressure-protect-your-backend-from-your-own-app-aha</guid>
      <description>&lt;p&gt;Most apps call APIs like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchFeed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That works…&lt;/p&gt;

&lt;p&gt;until something triggers many requests at once.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;infinite refresh loops&lt;/li&gt;
&lt;li&gt;aggressive background sync&lt;/li&gt;
&lt;li&gt;multiple views requesting the same data&lt;/li&gt;
&lt;li&gt;retry storms after network recovery&lt;/li&gt;
&lt;li&gt;multiple devices syncing simultaneously&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Suddenly your app becomes the biggest threat to your own backend.&lt;/p&gt;

&lt;p&gt;This post shows how to implement rate limiting and backpressure in SwiftUI so your networking layer is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;backend-safe&lt;/li&gt;
&lt;li&gt;quota-aware&lt;/li&gt;
&lt;li&gt;retry-friendly&lt;/li&gt;
&lt;li&gt;battery-conscious&lt;/li&gt;
&lt;li&gt;production-grade&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🧠 The Core Principle
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;A healthy system controls its request rate.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Even when everything is working, uncontrolled traffic can overload APIs.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧱 1. What Is Rate Limiting?
&lt;/h2&gt;

&lt;p&gt;Rate limiting controls how many requests can occur during a time window.&lt;/p&gt;

&lt;p&gt;Example rule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10 requests per second
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If more requests arrive, they must:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;wait&lt;/li&gt;
&lt;li&gt;queue&lt;/li&gt;
&lt;li&gt;or be rejected&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This protects both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the backend&lt;/li&gt;
&lt;li&gt;the client device&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🧬 2. Token Bucket Model
&lt;/h2&gt;

&lt;p&gt;A common approach is the token bucket algorithm.&lt;/p&gt;

&lt;p&gt;Concept:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Bucket contains tokens
Each request consumes one token
Tokens refill over time
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the bucket is empty:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request must wait
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🧱 3. Simple Rate Limiter Implementation
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;actor&lt;/span&gt; &lt;span class="kt"&gt;RateLimiter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;maxTokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;refillInterval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;TimeInterval&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;lastRefill&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Date&lt;/span&gt;

    &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;maxTokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;refillInterval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;TimeInterval&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;maxTokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;maxTokens&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;refillInterval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;refillInterval&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;maxTokens&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lastRefill&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;acquire&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;refill&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;tokens&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="kt"&gt;Task&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;nanoseconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100_000_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;refill&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;tokens&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;refill&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timeIntervalSince&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lastRefill&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;elapsed&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;refillInterval&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;maxTokens&lt;/span&gt;
            &lt;span class="n"&gt;lastRefill&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures requests happen at a controlled pace.&lt;/p&gt;




&lt;h2&gt;
  
  
  🚦 4. Using the Rate Limiter
&lt;/h2&gt;

&lt;p&gt;Wrap your API calls:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;limiter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;RateLimiter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;maxTokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;refillInterval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;fetchFeed&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;throws&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Feed&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;limiter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;acquire&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchFeed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now your app cannot exceed 5 requests per second.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔄 5. What Is Backpressure?
&lt;/h2&gt;

&lt;p&gt;Backpressure prevents systems from producing more work than they can handle.&lt;/p&gt;

&lt;p&gt;Example scenario:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Scroll view triggers 50 image loads
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without backpressure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;50 network requests instantly
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With backpressure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Requests queue and execute gradually
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This protects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CPU&lt;/li&gt;
&lt;li&gt;memory&lt;/li&gt;
&lt;li&gt;network&lt;/li&gt;
&lt;li&gt;backend APIs&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  📦 6. Request Queue Pattern
&lt;/h2&gt;

&lt;p&gt;Queue requests instead of executing immediately.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;actor&lt;/span&gt; &lt;span class="kt"&gt;RequestQueue&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[()&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Void&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="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;enqueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;task&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;@escaping&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;processNext&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;processNext&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isEmpty&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeFirst&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="kt"&gt;Task&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;task&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="nf"&gt;processNext&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;This ensures controlled execution.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔋 7. Why Mobile Apps Need Rate Limiting
&lt;/h2&gt;

&lt;p&gt;Mobile environments are unpredictable.&lt;/p&gt;

&lt;p&gt;Common issues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API quotas&lt;/li&gt;
&lt;li&gt;slow cellular connections&lt;/li&gt;
&lt;li&gt;background sync bursts&lt;/li&gt;
&lt;li&gt;multi-tab refresh loops&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without limits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the backend gets flooded&lt;/li&gt;
&lt;li&gt;battery drains faster&lt;/li&gt;
&lt;li&gt;UI becomes unstable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Rate limiting stabilizes the system.&lt;/p&gt;




&lt;h2&gt;
  
  
  🌐 8. Combine With Circuit Breakers
&lt;/h2&gt;

&lt;p&gt;From the previous post:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Circuit Breakers → stop requests during failures
Rate Limiting → control requests during success
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Together they form complete network resilience.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧪 9. Testing Rate Limiting
&lt;/h2&gt;

&lt;p&gt;Test scenarios:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;rapid refresh loops&lt;/li&gt;
&lt;li&gt;large scroll lists triggering network calls&lt;/li&gt;
&lt;li&gt;background sync bursts&lt;/li&gt;
&lt;li&gt;retry storms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Verify that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;request rate remains stable&lt;/li&gt;
&lt;li&gt;the queue processes correctly&lt;/li&gt;
&lt;li&gt;no backend overload occurs&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  ⚠️ 10. Common Anti-Patterns
&lt;/h2&gt;

&lt;p&gt;Avoid:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;unlimited parallel requests&lt;/li&gt;
&lt;li&gt;retrying immediately after failure&lt;/li&gt;
&lt;li&gt;per-view network calls without coordination&lt;/li&gt;
&lt;li&gt;ignoring server rate limits&lt;/li&gt;
&lt;li&gt;not deduplicating identical requests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These cause:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;request storms&lt;/li&gt;
&lt;li&gt;API bans&lt;/li&gt;
&lt;li&gt;battery drain&lt;/li&gt;
&lt;li&gt;backend instability&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🧠 Mental Model
&lt;/h2&gt;

&lt;p&gt;Think:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User Action
 → Request Queue
   → Rate Limiter
     → Network Request
       → API
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Fire every request immediately.”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🚀 Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Rate limiting and backpressure give your app:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;predictable network behavior&lt;/li&gt;
&lt;li&gt;backend protection&lt;/li&gt;
&lt;li&gt;smoother performance&lt;/li&gt;
&lt;li&gt;better battery usage&lt;/li&gt;
&lt;li&gt;production-grade resilience&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the difference between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an app that overwhelms its backend&lt;/li&gt;
&lt;li&gt;and a system that scales safely&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>swiftui</category>
      <category>architecture</category>
      <category>networking</category>
      <category>reliability</category>
    </item>
    <item>
      <title>SwiftUI Circuit Breakers &amp; Network Resilience Patterns (Prevent Cascading Failures)</title>
      <dc:creator>Sebastien Lato</dc:creator>
      <pubDate>Sat, 07 Mar 2026 20:48:10 +0000</pubDate>
      <link>https://dev.to/sebastienlato/swiftui-circuit-breakers-network-resilience-patterns-prevent-cascading-failures-i0o</link>
      <guid>https://dev.to/sebastienlato/swiftui-circuit-breakers-network-resilience-patterns-prevent-cascading-failures-i0o</guid>
      <description>&lt;p&gt;Most apps call APIs like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchUser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That works…&lt;/p&gt;

&lt;p&gt;until the server starts failing.&lt;/p&gt;

&lt;p&gt;Then your app does this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;retries immediately&lt;/li&gt;
&lt;li&gt;sends hundreds of failing requests&lt;/li&gt;
&lt;li&gt;drains battery&lt;/li&gt;
&lt;li&gt;overloads the backend&lt;/li&gt;
&lt;li&gt;slows down the UI&lt;/li&gt;
&lt;li&gt;worsens the outage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is called a cascading failure.&lt;/p&gt;

&lt;p&gt;A production app needs protection mechanisms.&lt;/p&gt;

&lt;p&gt;This post shows how to implement circuit breakers and network resilience patterns in SwiftUI that are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;failure-aware&lt;/li&gt;
&lt;li&gt;backend-friendly&lt;/li&gt;
&lt;li&gt;battery-conscious&lt;/li&gt;
&lt;li&gt;retry-safe&lt;/li&gt;
&lt;li&gt;production-grade&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🧠 The Core Principle
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;When a system is failing, stop making it worse.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Instead of retrying endlessly, the system must detect failures and pause requests.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧱 1. What Is a Circuit Breaker?
&lt;/h2&gt;

&lt;p&gt;A circuit breaker prevents repeated calls to a failing service.&lt;/p&gt;

&lt;p&gt;It has three states:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Closed → Normal operation
Open → Requests blocked
Half-Open → Testing recovery
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Requests Fail
→ Circuit Opens
→ Requests Blocked
→ Recovery Test
→ Circuit Closes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🔌 2. Define Circuit States
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;enum&lt;/span&gt; &lt;span class="kt"&gt;CircuitState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;closed&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;until&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;halfOpen&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Meaning:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Closed → requests allowed&lt;/li&gt;
&lt;li&gt;Open → requests blocked temporarily&lt;/li&gt;
&lt;li&gt;Half-open → allow limited requests to test recovery&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🧬 3. Basic Circuit Breaker Implementation
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;CircuitBreaker&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private(set)&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CircuitState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;closed&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;failureCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;failureThreshold&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;TimeInterval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;recordFailure&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;failureCount&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;failureCount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;failureThreshold&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;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;until&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addingTimeInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timeout&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="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;recordSuccess&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;failureCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;closed&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;Failures accumulate until the circuit opens.&lt;/p&gt;




&lt;h2&gt;
  
  
  🚦 4. Blocking Requests When Circuit Is Open
&lt;/h2&gt;

&lt;p&gt;Before performing a request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;canExecute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;closed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;until&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kt"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;until&lt;/span&gt;

    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;halfOpen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&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;Usage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="n"&gt;breaker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;canExecute&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="k"&gt;throw&lt;/span&gt; &lt;span class="kt"&gt;NetworkError&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;circuitOpen&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This protects the backend.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔄 5. Transition to Half-Open
&lt;/h2&gt;

&lt;p&gt;When timeout expires:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;until&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="kt"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;until&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;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;halfOpen&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only a small number of requests should test recovery.&lt;/p&gt;

&lt;p&gt;If they succeed → close circuit.&lt;/p&gt;

&lt;p&gt;If they fail → reopen.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔋 6. Why Mobile Apps Need Circuit Breakers
&lt;/h2&gt;

&lt;p&gt;Mobile networks are unstable.&lt;/p&gt;

&lt;p&gt;Common failure scenarios:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;server outage&lt;/li&gt;
&lt;li&gt;DNS issues&lt;/li&gt;
&lt;li&gt;TLS failures&lt;/li&gt;
&lt;li&gt;rate limiting&lt;/li&gt;
&lt;li&gt;cellular packet loss&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without circuit breakers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;your app spams the backend&lt;/li&gt;
&lt;li&gt;battery drains rapidly&lt;/li&gt;
&lt;li&gt;retries amplify the outage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Circuit breakers prevent this.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧱 7. Integrating with API Clients
&lt;/h2&gt;

&lt;p&gt;Wrap network calls:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="n"&gt;performRequest&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;:&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;throws&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;T&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;throws&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;T&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="n"&gt;breaker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;canExecute&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="k"&gt;throw&lt;/span&gt; &lt;span class="kt"&gt;NetworkError&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;circuitOpen&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;breaker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;recordSuccess&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;result&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;breaker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;recordFailure&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes resilience automatic.&lt;/p&gt;




&lt;h2&gt;
  
  
  🌐 8. Combine with Retry Strategies
&lt;/h2&gt;

&lt;p&gt;Circuit breakers work with retries.&lt;/p&gt;

&lt;p&gt;Example flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request
→ Failure
→ Retry (exponential backoff)
→ Failure threshold reached
→ Circuit opens
→ Requests paused
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This prevents retry storms.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧪 9. Testing Circuit Breakers
&lt;/h2&gt;

&lt;p&gt;Test scenarios:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;repeated server errors&lt;/li&gt;
&lt;li&gt;timeout storms&lt;/li&gt;
&lt;li&gt;network disconnections&lt;/li&gt;
&lt;li&gt;recovery after outage&lt;/li&gt;
&lt;li&gt;half-open transition&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Verify that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;requests stop during outages&lt;/li&gt;
&lt;li&gt;requests resume after recovery&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  ⚠️ 10. Common Anti-Patterns
&lt;/h2&gt;

&lt;p&gt;Avoid:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;infinite retries&lt;/li&gt;
&lt;li&gt;retrying immediately after failure&lt;/li&gt;
&lt;li&gt;ignoring server rate limits&lt;/li&gt;
&lt;li&gt;retrying while offline&lt;/li&gt;
&lt;li&gt;not tracking failure counts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These cause:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;backend overload&lt;/li&gt;
&lt;li&gt;cascading outages&lt;/li&gt;
&lt;li&gt;battery drain&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🧠 Mental Model
&lt;/h2&gt;

&lt;p&gt;Think:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request
 → Failure Detection
   → Circuit Breaker
     → Pause Requests
       → Recovery Probe
         → Resume Traffic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Just retry again.”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🚀 Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Circuit breakers give your app:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;backend protection&lt;/li&gt;
&lt;li&gt;smarter retry behavior&lt;/li&gt;
&lt;li&gt;reduced battery usage&lt;/li&gt;
&lt;li&gt;resilience during outages&lt;/li&gt;
&lt;li&gt;predictable recovery&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the difference between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a fragile network client&lt;/li&gt;
&lt;li&gt;and a production-grade mobile system&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>swiftui</category>
      <category>architecture</category>
      <category>networking</category>
      <category>reliability</category>
    </item>
    <item>
      <title>SwiftUI Soft Deletes &amp; Data Lifecycle Policies (Designing for Recovery, Compliance &amp; Trust)</title>
      <dc:creator>Sebastien Lato</dc:creator>
      <pubDate>Sat, 28 Feb 2026 20:24:27 +0000</pubDate>
      <link>https://dev.to/sebastienlato/swiftui-soft-deletes-data-lifecycle-policies-designing-for-recovery-compliance-trust-37g4</link>
      <guid>https://dev.to/sebastienlato/swiftui-soft-deletes-data-lifecycle-policies-designing-for-recovery-compliance-trust-37g4</guid>
      <description>&lt;p&gt;Most apps delete data like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That works…&lt;/p&gt;

&lt;p&gt;until you need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;undo after app relaunch&lt;/li&gt;
&lt;li&gt;sync reconciliation&lt;/li&gt;
&lt;li&gt;audit trails&lt;/li&gt;
&lt;li&gt;regulatory compliance (GDPR, retention policies)&lt;/li&gt;
&lt;li&gt;multi-device recovery&lt;/li&gt;
&lt;li&gt;accidental deletion protection&lt;/li&gt;
&lt;li&gt;staged data removal&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At that point, hard deletes become one of the most dangerous operations in your app.&lt;/p&gt;

&lt;p&gt;This post shows how to design soft deletes and data lifecycle policies in SwiftUI that are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;recoverable&lt;/li&gt;
&lt;li&gt;compliant&lt;/li&gt;
&lt;li&gt;sync-safe&lt;/li&gt;
&lt;li&gt;user-friendly&lt;/li&gt;
&lt;li&gt;production-grade&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🧠 The Core Principle
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Deletion is a state — not an action.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Data should transition through lifecycle stages, not disappear instantly.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧱 1. Hard Delete vs Soft Delete
&lt;/h2&gt;

&lt;p&gt;Hard delete:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Soft delete:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deletedAt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Soft delete preserves:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;history&lt;/li&gt;
&lt;li&gt;undo capability&lt;/li&gt;
&lt;li&gt;sync correctness&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🧬 2. Model Deletion as State
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;Todo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Identifiable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Codable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UUID&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;deletedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Date&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;Active item:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="n"&gt;deletedAt&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deleted item:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="n"&gt;deletedAt&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deletion becomes a state transition.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔁 3. Why Soft Deletes Matter in Sync Systems
&lt;/h2&gt;

&lt;p&gt;Without soft deletes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;deletion may not sync correctly&lt;/li&gt;
&lt;li&gt;remote devices may resurrect items&lt;/li&gt;
&lt;li&gt;conflicts become ambiguous&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With soft deletes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;deletion propagates like any update&lt;/li&gt;
&lt;li&gt;reconciliation is deterministic&lt;/li&gt;
&lt;li&gt;restores are possible&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🧱 4. Querying Active Data
&lt;/h2&gt;

&lt;p&gt;Never filter in UI.&lt;/p&gt;

&lt;p&gt;Centralize logic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;activeTodos&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Todo&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;todos&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deletedAt&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;nil&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;Prevents accidental display of deleted items.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔄 5. Undo &amp;amp; Restore Flows
&lt;/h2&gt;

&lt;p&gt;Soft delete enables recovery:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;restore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;inout&lt;/span&gt; &lt;span class="kt"&gt;Todo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deletedAt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;undo after crash&lt;/li&gt;
&lt;li&gt;multi-device restore&lt;/li&gt;
&lt;li&gt;user trust&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  ⚖️ 6. Data Retention Policies
&lt;/h2&gt;

&lt;p&gt;Not all deleted data should live forever.&lt;/p&gt;

&lt;p&gt;Example lifecycle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kt"&gt;Active&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="kt"&gt;Soft&lt;/span&gt; &lt;span class="kt"&gt;Deleted&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="kt"&gt;Archived&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="kt"&gt;Permanently&lt;/span&gt; &lt;span class="kt"&gt;Deleted&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;enum&lt;/span&gt; &lt;span class="kt"&gt;DataLifecycleState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;active&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nf"&gt;deleted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nf"&gt;archived&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This enables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;retention compliance&lt;/li&gt;
&lt;li&gt;storage optimization&lt;/li&gt;
&lt;li&gt;legal requirements&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🌐 7. Soft Deletes &amp;amp; Multi-Device Sync
&lt;/h2&gt;

&lt;p&gt;Scenario:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Device A deletes item&lt;/li&gt;
&lt;li&gt;Device B edits item offline&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With soft deletes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;conflict is detectable&lt;/li&gt;
&lt;li&gt;resolution policy is clear&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without soft deletes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;item may reappear&lt;/li&gt;
&lt;li&gt;state becomes inconsistent&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🧪 8. Testing Deletion Safety
&lt;/h2&gt;

&lt;p&gt;Test scenarios:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;delete → restore → sync&lt;/li&gt;
&lt;li&gt;delete on one device, edit on another&lt;/li&gt;
&lt;li&gt;migration with deleted data&lt;/li&gt;
&lt;li&gt;background sync of deletions&lt;/li&gt;
&lt;li&gt;retention policy execution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Deletion bugs destroy trust.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚠️ 9. Common Anti-Patterns
&lt;/h2&gt;

&lt;p&gt;Avoid:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;immediate hard deletes&lt;/li&gt;
&lt;li&gt;filtering deleted items only in UI&lt;/li&gt;
&lt;li&gt;losing deletion timestamps&lt;/li&gt;
&lt;li&gt;not syncing deletion state&lt;/li&gt;
&lt;li&gt;permanent deletion without retention window&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These lead to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ghost data&lt;/li&gt;
&lt;li&gt;data loss&lt;/li&gt;
&lt;li&gt;inconsistent sync&lt;/li&gt;
&lt;li&gt;compliance risks&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🧠 Mental Model
&lt;/h2&gt;

&lt;p&gt;Think:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Active
 → Soft Deleted
   → Archived
     → Purged
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Delete and forget.”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🚀 Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Soft deletes and lifecycle policies give you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;safer user experience&lt;/li&gt;
&lt;li&gt;reliable sync&lt;/li&gt;
&lt;li&gt;compliance readiness&lt;/li&gt;
&lt;li&gt;recovery from mistakes&lt;/li&gt;
&lt;li&gt;long-term data trust&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the difference between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fragile deletion&lt;/li&gt;
&lt;li&gt;and responsible data stewardship&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>swiftui</category>
      <category>architecture</category>
      <category>data</category>
      <category>reliability</category>
    </item>
    <item>
      <title>SwiftUI Event Sourcing Lite (Audit Trails Without the Complexity)</title>
      <dc:creator>Sebastien Lato</dc:creator>
      <pubDate>Thu, 26 Feb 2026 14:12:39 +0000</pubDate>
      <link>https://dev.to/sebastienlato/swiftui-event-sourcing-lite-audit-trails-without-the-complexity-58e4</link>
      <guid>https://dev.to/sebastienlato/swiftui-event-sourcing-lite-audit-trails-without-the-complexity-58e4</guid>
      <description>&lt;p&gt;Most apps store state like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Alice"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That works…&lt;/p&gt;

&lt;p&gt;until you need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;audit trails&lt;/li&gt;
&lt;li&gt;debugging production issues&lt;/li&gt;
&lt;li&gt;undo beyond a single session&lt;/li&gt;
&lt;li&gt;sync conflict investigation&lt;/li&gt;
&lt;li&gt;regulatory compliance&lt;/li&gt;
&lt;li&gt;“how did we get here?” answers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At that point, storing only the latest state becomes a liability.&lt;/p&gt;

&lt;p&gt;This post shows how to implement Event Sourcing Lite in SwiftUI — a practical approach that gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;history tracking&lt;/li&gt;
&lt;li&gt;reproducible state&lt;/li&gt;
&lt;li&gt;better debugging&lt;/li&gt;
&lt;li&gt;safer sync reconciliation&lt;/li&gt;
&lt;li&gt;production-grade observability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without the complexity of full event-sourced systems.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧠 The Core Principle
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;State is a result of events — not a single snapshot.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you only store the final state, you lose the story.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧱 1. Snapshot vs Event-Based Storage
&lt;/h2&gt;

&lt;p&gt;Traditional model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User {
  id
  name
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Event-based model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UserCreated
UserNameUpdated
UserDeleted
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Current state = replay of events.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧬 2. What Is Event Sourcing Lite?
&lt;/h2&gt;

&lt;p&gt;Full event sourcing replaces your database.&lt;/p&gt;

&lt;p&gt;Event Sourcing Lite:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;keeps your normal database&lt;/li&gt;
&lt;li&gt;adds an append-only event log&lt;/li&gt;
&lt;li&gt;records meaningful changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You get traceability without rewriting your app.&lt;/p&gt;




&lt;h2&gt;
  
  
  📦 3. Define Domain Events
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;enum&lt;/span&gt; &lt;span class="kt"&gt;UserEvent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Codable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nf"&gt;created&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UUID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nf"&gt;nameUpdated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UUID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;newName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nf"&gt;deleted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UUID&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;Events represent facts, not commands.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧱 4. Event Store (Append-Only)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;EventStore&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UserEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// persist to database&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;loadEvents&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;UserEvent&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// fetch events&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;Rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;never update events&lt;/li&gt;
&lt;li&gt;never delete events&lt;/li&gt;
&lt;li&gt;append only&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;History must be immutable.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔄 5. Rebuilding State from Events
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;rebuildUserState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="nv"&gt;events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;UserEvent&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;User&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;User&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;created&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;name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;User&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&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="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;name&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="k"&gt;case&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;nameUpdated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;newName&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;newName&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;deleted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;
        &lt;span class="p"&gt;}&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;user&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This enables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;debugging&lt;/li&gt;
&lt;li&gt;consistency checks&lt;/li&gt;
&lt;li&gt;migration recovery&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🔁 6. Why This Helps Sync &amp;amp; Conflict Resolution
&lt;/h2&gt;

&lt;p&gt;With events:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you can replay changes&lt;/li&gt;
&lt;li&gt;you can merge event streams&lt;/li&gt;
&lt;li&gt;conflicts become visible&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“The state is wrong and we don’t know why.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“This event caused the divergence.”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🧪 7. Debugging Production Issues
&lt;/h2&gt;

&lt;p&gt;When a bug report arrives:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“My data disappeared.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With event logs you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;inspect event timeline&lt;/li&gt;
&lt;li&gt;identify faulty updates&lt;/li&gt;
&lt;li&gt;reproduce the state locally&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without logs, you’re guessing.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧱 8. Event Sourcing Lite + Idempotency
&lt;/h2&gt;

&lt;p&gt;Events must be idempotent.&lt;/p&gt;

&lt;p&gt;Attach operation IDs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;EventEnvelope&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Codable&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Codable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UUID&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Event&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Date&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Prevents duplicate event replay.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚠️ 9. When NOT to Use Event Sourcing Lite
&lt;/h2&gt;

&lt;p&gt;Avoid for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;trivial apps&lt;/li&gt;
&lt;li&gt;ephemeral data&lt;/li&gt;
&lt;li&gt;UI-only state&lt;/li&gt;
&lt;li&gt;high-frequency telemetry&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use it for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;user data&lt;/li&gt;
&lt;li&gt;financial operations&lt;/li&gt;
&lt;li&gt;collaborative edits&lt;/li&gt;
&lt;li&gt;audit requirements&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  ⚠️ 10. Common Anti-Patterns
&lt;/h2&gt;

&lt;p&gt;Avoid:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;storing events without timestamps&lt;/li&gt;
&lt;li&gt;mutating past events&lt;/li&gt;
&lt;li&gt;logging UI noise instead of domain events&lt;/li&gt;
&lt;li&gt;replaying events without idempotency&lt;/li&gt;
&lt;li&gt;mixing commands with events&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Events are facts — not intentions.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧠 Mental Model
&lt;/h2&gt;

&lt;p&gt;Think:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User Action
 → Domain Event
   → Append-Only Log
     → State Projection
       → UI
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Just overwrite the record.”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🚀 Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Event Sourcing Lite gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;audit trails&lt;/li&gt;
&lt;li&gt;reproducible bugs&lt;/li&gt;
&lt;li&gt;safer migrations&lt;/li&gt;
&lt;li&gt;clearer sync reconciliation&lt;/li&gt;
&lt;li&gt;long-term data trust&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the difference between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;guessing what happened&lt;/li&gt;
&lt;li&gt;and knowing exactly why&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>swiftui</category>
      <category>architecture</category>
      <category>data</category>
      <category>debugging</category>
    </item>
    <item>
      <title>SwiftUI Idempotency &amp; Duplicate Prevention (Correctness in Distributed Systems)</title>
      <dc:creator>Sebastien Lato</dc:creator>
      <pubDate>Fri, 13 Feb 2026 18:10:52 +0000</pubDate>
      <link>https://dev.to/sebastienlato/swiftui-idempotency-duplicate-prevention-correctness-in-distributed-systems-1p95</link>
      <guid>https://dev.to/sebastienlato/swiftui-idempotency-duplicate-prevention-correctness-in-distributed-systems-1p95</guid>
      <description>&lt;p&gt;Most apps assume actions happen once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="nf"&gt;submitOrder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That works…&lt;/p&gt;

&lt;p&gt;until you introduce:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;retries after network failure&lt;/li&gt;
&lt;li&gt;background sync&lt;/li&gt;
&lt;li&gt;offline queues&lt;/li&gt;
&lt;li&gt;app relaunch recovery&lt;/li&gt;
&lt;li&gt;migration reprocessing&lt;/li&gt;
&lt;li&gt;slow server responses&lt;/li&gt;
&lt;li&gt;user double taps&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At that point, duplicate operations become one of the most dangerous bugs in your app.&lt;/p&gt;

&lt;p&gt;This post shows how to design idempotent systems in SwiftUI that are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;safe to retry&lt;/li&gt;
&lt;li&gt;duplicate-proof&lt;/li&gt;
&lt;li&gt;crash-resilient&lt;/li&gt;
&lt;li&gt;sync-friendly&lt;/li&gt;
&lt;li&gt;production-grade&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🧠 The Core Principle
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Retrying an operation must not change the result.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If running an action twice creates a different outcome, your system is fragile.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧱 1. What Is Idempotency?
&lt;/h2&gt;

&lt;p&gt;An operation is idempotent if running it multiple times has the same effect as running it once.&lt;/p&gt;

&lt;p&gt;Safe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="nf"&gt;deleteItem&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running twice → item is still deleted.&lt;/p&gt;

&lt;p&gt;Unsafe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="nf"&gt;createOrder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running twice → two orders created.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚠️ 2. Why Mobile Apps Need Idempotency
&lt;/h2&gt;

&lt;p&gt;Mobile environments guarantee retries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;network timeouts trigger retries&lt;/li&gt;
&lt;li&gt;background tasks may run twice&lt;/li&gt;
&lt;li&gt;app relaunch replays queued operations&lt;/li&gt;
&lt;li&gt;sync engines retry failed operations&lt;/li&gt;
&lt;li&gt;users double tap buttons&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without idempotency, you get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;duplicate charges&lt;/li&gt;
&lt;li&gt;duplicated messages&lt;/li&gt;
&lt;li&gt;corrupted state&lt;/li&gt;
&lt;li&gt;inconsistent sync&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🧬 3. Attach an Operation ID to Every Mutation
&lt;/h2&gt;

&lt;p&gt;Every mutation must have a stable identity.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;OperationID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Hashable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Codable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UUID&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example operation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;CreateOrderOperation&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;OperationID&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;OrderPayload&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The ID travels through:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;local queue&lt;/li&gt;
&lt;li&gt;network request&lt;/li&gt;
&lt;li&gt;server processing&lt;/li&gt;
&lt;li&gt;reconciliation&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🌐 4. Server-Side Idempotency Keys
&lt;/h2&gt;

&lt;p&gt;Send the operation ID with requests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST /orders
Idempotency-Key: 8F6C-1234-ABCD
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Server logic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;if key is new → process&lt;/li&gt;
&lt;li&gt;if key exists → return previous result&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This guarantees safe retries.&lt;/p&gt;




&lt;h2&gt;
  
  
  📦 5. Local Deduplication Layer
&lt;/h2&gt;

&lt;p&gt;Prevent duplicate execution locally.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;OperationDeduplicator&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;processed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;OperationID&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;shouldProcess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;OperationID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;processed&lt;/span&gt;&lt;span class="o"&gt;.&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;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inserted&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;Usage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="n"&gt;deduplicator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shouldProcess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Persist processed IDs for crash safety.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔁 6. Idempotency in Sync Queues
&lt;/h2&gt;

&lt;p&gt;With idempotency:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;retries are safe&lt;/li&gt;
&lt;li&gt;crash recovery is safe&lt;/li&gt;
&lt;li&gt;migrations can replay operations&lt;/li&gt;
&lt;li&gt;background sync won’t duplicate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without it, retries corrupt data.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧱 7. Designing Idempotent APIs
&lt;/h2&gt;

&lt;p&gt;Prefer operations that include identity.&lt;/p&gt;

&lt;p&gt;Bad:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kt"&gt;POST&lt;/span&gt; &lt;span class="sr"&gt;/cart/&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Better:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kt"&gt;PUT&lt;/span&gt; &lt;span class="sr"&gt;/cart/items/&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;itemID&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PUT is idempotent by design.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔄 8. Handling Partial Failures
&lt;/h2&gt;

&lt;p&gt;Network failure after server success is common.&lt;/p&gt;

&lt;p&gt;Without idempotency:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;retry creates duplicate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With idempotency:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;retry returns existing result&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your system becomes self-healing.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚠️ 9. Common Anti-Patterns
&lt;/h2&gt;

&lt;p&gt;Avoid:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;relying on timestamps for uniqueness&lt;/li&gt;
&lt;li&gt;generating IDs on retry&lt;/li&gt;
&lt;li&gt;storing processed IDs only in memory&lt;/li&gt;
&lt;li&gt;assuming requests run once&lt;/li&gt;
&lt;li&gt;not propagating IDs to server&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These lead to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;duplicate charges&lt;/li&gt;
&lt;li&gt;duplicated records&lt;/li&gt;
&lt;li&gt;unrecoverable inconsistencies&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🧪 10. Testing Idempotency
&lt;/h2&gt;

&lt;p&gt;Test scenarios:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;retry same operation 10×&lt;/li&gt;
&lt;li&gt;crash before response received&lt;/li&gt;
&lt;li&gt;replay queue after migration&lt;/li&gt;
&lt;li&gt;simulate slow server responses&lt;/li&gt;
&lt;li&gt;double-tap user actions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your system survives these, it’s robust.&lt;/p&gt;




&lt;p&gt;🧠 Mental Model&lt;/p&gt;

&lt;p&gt;Think:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User Action
 → Operation ID
   → Persistent Queue
     → Retry
       → Server Deduplication
         → Consistent Result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“This request will only run once.”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🚀 Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Idempotency gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;safe retries&lt;/li&gt;
&lt;li&gt;reliable sync&lt;/li&gt;
&lt;li&gt;crash resilience&lt;/li&gt;
&lt;li&gt;duplicate prevention&lt;/li&gt;
&lt;li&gt;consistent distributed systems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the difference between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a fragile mobile client&lt;/li&gt;
&lt;li&gt;and a production-grade system&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>swiftui</category>
      <category>architecture</category>
      <category>sync</category>
      <category>reliability</category>
    </item>
    <item>
      <title>SwiftUI Data Migration &amp; Schema Evolution (Safe Upgrades Without Data Loss)</title>
      <dc:creator>Sebastien Lato</dc:creator>
      <pubDate>Thu, 12 Feb 2026 23:24:28 +0000</pubDate>
      <link>https://dev.to/sebastienlato/swiftui-data-migration-schema-evolution-safe-upgrades-without-data-loss-ij2</link>
      <guid>https://dev.to/sebastienlato/swiftui-data-migration-schema-evolution-safe-upgrades-without-data-loss-ij2</guid>
      <description>&lt;p&gt;Most apps ship updates like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kt"&gt;UserDefaults&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;standard&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;forKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"newFlag"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That works…&lt;/p&gt;

&lt;p&gt;until you need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;database schema changes&lt;/li&gt;
&lt;li&gt;new required fields&lt;/li&gt;
&lt;li&gt;offline data preservation&lt;/li&gt;
&lt;li&gt;sync queue compatibility&lt;/li&gt;
&lt;li&gt;multi-version clients in the wild&lt;/li&gt;
&lt;li&gt;rollback safety&lt;/li&gt;
&lt;li&gt;tenant-specific migrations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At that point, data migration becomes one of the riskiest parts of your app.&lt;/p&gt;

&lt;p&gt;This post shows how to design a migration system in SwiftUI that is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;safe&lt;/li&gt;
&lt;li&gt;deterministic&lt;/li&gt;
&lt;li&gt;backward-compatible&lt;/li&gt;
&lt;li&gt;testable&lt;/li&gt;
&lt;li&gt;production-grade&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🧠 The Core Principle
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;App versions change — user data must survive.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Your schema is a contract with every version you’ve ever shipped.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧱 1. Version Your Data Schema
&lt;/h2&gt;

&lt;p&gt;Never rely on implicit structure.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;SchemaVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Codable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Persist current version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;currentSchemaVersion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🧬 2. Migration Engine Lives in Infrastructure
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;MigrationEngine&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;persistence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;PersistenceLayer&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;MigrationStep&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;It does not live in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Views&lt;/li&gt;
&lt;li&gt;ViewModels&lt;/li&gt;
&lt;li&gt;feature modules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Migration is cross-cutting infrastructure.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔁 3. Define Migration Steps
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;MigrationStep&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;migrate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;throws&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Void&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kt"&gt;MigrationStep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;addField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"lastOpenedAt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Date&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;Migrations must be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ordered&lt;/li&gt;
&lt;li&gt;idempotent&lt;/li&gt;
&lt;li&gt;atomic&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  📦 4. Run Migrations at App Launch
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;performMigrations&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;throws&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;storedVersion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;loadVersion&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;migrations&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;storedVersion&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;migrate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;saveVersion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to&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;Run before:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;sync engine starts&lt;/li&gt;
&lt;li&gt;ViewModels load data&lt;/li&gt;
&lt;li&gt;background tasks resume&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  ⚠️ 5. Backward Compatibility Matters
&lt;/h2&gt;

&lt;p&gt;Users may skip versions.&lt;/p&gt;

&lt;p&gt;Migration path must support:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;v1&lt;/span&gt; &lt;span class="s"&gt;→&lt;/span&gt; &lt;span class="s"&gt;v4&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not only:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;v3&lt;/span&gt; &lt;span class="s"&gt;→&lt;/span&gt; &lt;span class="s"&gt;v4&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🔄 6. Migration &amp;amp; Sync Queue Compatibility
&lt;/h2&gt;

&lt;p&gt;If schema changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;queued operations may become invalid&lt;/li&gt;
&lt;li&gt;payload formats may change&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Strategy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="nf"&gt;migrateQueuedOperations&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Never drop user actions silently.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔋 7. Safe Rollback Strategy
&lt;/h2&gt;

&lt;p&gt;App updates fail in the real world.&lt;/p&gt;

&lt;p&gt;Rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;never delete old columns immediately&lt;/li&gt;
&lt;li&gt;support dual-read during transition&lt;/li&gt;
&lt;li&gt;remove fields only after several versions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This prevents catastrophic data loss.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧪 8. Testing Migrations
&lt;/h2&gt;

&lt;p&gt;Test scenarios:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;upgrade from every prior version&lt;/li&gt;
&lt;li&gt;partial migrations&lt;/li&gt;
&lt;li&gt;corrupted data&lt;/li&gt;
&lt;li&gt;interrupted migrations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Migration bugs are permanent.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚠️ 9. Common Migration Anti-Patterns
&lt;/h2&gt;

&lt;p&gt;Avoid:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;destructive schema changes&lt;/li&gt;
&lt;li&gt;silent data resets&lt;/li&gt;
&lt;li&gt;skipping version tracking&lt;/li&gt;
&lt;li&gt;running migrations lazily&lt;/li&gt;
&lt;li&gt;mixing migration with feature logic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These lead to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;data loss&lt;/li&gt;
&lt;li&gt;sync failures&lt;/li&gt;
&lt;li&gt;inconsistent state&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🧠 Mental Model
&lt;/h2&gt;

&lt;p&gt;Think:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Old Schema
 → Migration Engine
   → New Schema
     → Sync Engine
       → App Features
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“We’ll just reset the database.”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🚀 Final Thoughts
&lt;/h2&gt;

&lt;p&gt;A proper migration system gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;safe app upgrades&lt;/li&gt;
&lt;li&gt;preserved user data&lt;/li&gt;
&lt;li&gt;compatibility with old client&lt;/li&gt;
&lt;li&gt;fewer production incidents&lt;/li&gt;
&lt;li&gt;confidence to evolve your schema&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the difference between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a fragile app&lt;/li&gt;
&lt;li&gt;and a long-lived product&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>swiftui</category>
      <category>architecture</category>
      <category>persistence</category>
      <category>data</category>
    </item>
    <item>
      <title>SwiftUI Background Sync Engine Architecture (Reliable, Battery-Aware, Conflict-Safe)</title>
      <dc:creator>Sebastien Lato</dc:creator>
      <pubDate>Wed, 11 Feb 2026 20:06:03 +0000</pubDate>
      <link>https://dev.to/sebastienlato/swiftui-background-sync-engine-architecture-reliable-battery-aware-conflict-safe-2k9f</link>
      <guid>https://dev.to/sebastienlato/swiftui-background-sync-engine-architecture-reliable-battery-aware-conflict-safe-2k9f</guid>
      <description>&lt;p&gt;Most apps implement sync like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="nf"&gt;refreshData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That works…&lt;br&gt;
until you need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;offline edits&lt;/li&gt;
&lt;li&gt;retries&lt;/li&gt;
&lt;li&gt;background execution&lt;/li&gt;
&lt;li&gt;conflict handling&lt;/li&gt;
&lt;li&gt;battery awareness&lt;/li&gt;
&lt;li&gt;exponential backoff&lt;/li&gt;
&lt;li&gt;rate limiting&lt;/li&gt;
&lt;li&gt;cross-tenant sync&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At that point, sync becomes one of the hardest systems in your app.&lt;/p&gt;

&lt;p&gt;This post shows how to design a background sync engine in SwiftUI that is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;reliable&lt;/li&gt;
&lt;li&gt;resilient&lt;/li&gt;
&lt;li&gt;battery-conscious&lt;/li&gt;
&lt;li&gt;conflict-aware&lt;/li&gt;
&lt;li&gt;testable&lt;/li&gt;
&lt;li&gt;production-grade&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  🧠 The Core Principle
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Sync is a state machine — not a network call.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If your sync logic lives in a ViewModel, it’s already fragile.&lt;/p&gt;


&lt;h2&gt;
  
  
  🧱 1. Define Sync States Explicitly
&lt;/h2&gt;

&lt;p&gt;Never treat sync as boolean.&lt;/p&gt;

&lt;p&gt;Bad:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="n"&gt;isSyncing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Correct:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;enum&lt;/span&gt; &lt;span class="kt"&gt;SyncState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;idle&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;scheduled&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;syncing&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nf"&gt;failed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;paused&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The engine transitions between states — predictably.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧬 2. Sync Engine Lives in Infrastructure
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;SyncEngine&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;SyncQueue&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;api&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;APIClient&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;persistence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;PersistenceLayer&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It does not live in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;views &lt;/li&gt;
&lt;li&gt;ViewModels&lt;/li&gt;
&lt;li&gt;feature modules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sync is cross-cutting infrastructure.&lt;/p&gt;




&lt;h2&gt;
  
  
  📦 3. Operation Queue Model
&lt;/h2&gt;

&lt;p&gt;Treat sync work as operations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;SyncOperation&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UUID&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;OperationType&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Data&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;retryCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Operations are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;persisted&lt;/li&gt;
&lt;li&gt;retried&lt;/li&gt;
&lt;li&gt;ordered&lt;/li&gt;
&lt;li&gt;cancellable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Never fire-and-forget.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔁 4. Persistent Sync Queue
&lt;/h2&gt;

&lt;p&gt;Queue must survive:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;app termination&lt;/li&gt;
&lt;li&gt;device reboot&lt;/li&gt;
&lt;li&gt;crashes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Store operations in local database.&lt;/p&gt;

&lt;p&gt;On launch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="nf"&gt;loadPendingOperations&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;resumeProcessing&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🔄 5. Retry Strategy
&lt;/h2&gt;

&lt;p&gt;Never retry infinitely.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;nextRetryDelay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;TimeInterval&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Double&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c1"&gt;// exponential backoff&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;max retry count&lt;/li&gt;
&lt;li&gt;pause on fatal errors&lt;/li&gt;
&lt;li&gt;respect HTTP status codes&lt;/li&gt;
&lt;li&gt;differentiate network vs server errors&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  ⚡ 6. Background Execution Integration
&lt;/h2&gt;

&lt;p&gt;Integrate with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;BGTaskScheduler&lt;/li&gt;
&lt;li&gt;background fetch&lt;/li&gt;
&lt;li&gt;push-triggered sync&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kt"&gt;BGTaskScheduler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sync engine runs independent of UI.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔋 7. Battery &amp;amp; Network Awareness
&lt;/h2&gt;

&lt;p&gt;Before syncing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;check reachability&lt;/li&gt;
&lt;li&gt;check battery level&lt;/li&gt;
&lt;li&gt;check low power mode&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Avoid syncing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;on cellular (if heavy)&lt;/li&gt;
&lt;li&gt;during low battery&lt;/li&gt;
&lt;li&gt;while app inactive unless necessary&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sync must be respectful.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧠 8. Conflict Resolution Integration
&lt;/h2&gt;

&lt;p&gt;When server responds with conflict:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;conflict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;resolveConflict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;local&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;remote&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Resolution strategies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;last-write-wins&lt;/li&gt;
&lt;li&gt;merge fields&lt;/li&gt;
&lt;li&gt;server authority&lt;/li&gt;
&lt;li&gt;manual resolution queue&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Conflict handling must be centralized — not per feature.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧪 9. Testing the Sync Engine
&lt;/h2&gt;

&lt;p&gt;Mock:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API failures&lt;/li&gt;
&lt;li&gt;network loss&lt;/li&gt;
&lt;li&gt;partial success&lt;/li&gt;
&lt;li&gt;background interruptions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Test scenarios:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;retry exhaustion&lt;/li&gt;
&lt;li&gt;queue recovery&lt;/li&gt;
&lt;li&gt;duplicate prevention&lt;/li&gt;
&lt;li&gt;idempotency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sync bugs are subtle and expensive.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚠️ 10. Common Sync Anti-Patterns
&lt;/h2&gt;

&lt;p&gt;Avoid:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;syncing from ViewModels&lt;/li&gt;
&lt;li&gt;not persisting queue&lt;/li&gt;
&lt;li&gt;retrying without backoff&lt;/li&gt;
&lt;li&gt;ignoring conflicts&lt;/li&gt;
&lt;li&gt;not handling app termination&lt;/li&gt;
&lt;li&gt;duplicating operations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These lead to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;data corruption&lt;/li&gt;
&lt;li&gt;server overload&lt;/li&gt;
&lt;li&gt;angry users&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🧠 Mental Model
&lt;/h2&gt;

&lt;p&gt;Think:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Local Change
 → Sync Operation
   → Persistent Queue
     → Retry Engine
       → Server
         → Reconciliation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Just refresh when needed”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🚀 Final Thoughts
&lt;/h2&gt;

&lt;p&gt;A proper background sync engine gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;reliable offline behavior&lt;/li&gt;
&lt;li&gt;predictable data consistency&lt;/li&gt;
&lt;li&gt;fewer production bugs&lt;/li&gt;
&lt;li&gt;better battery efficiency&lt;/li&gt;
&lt;li&gt;scalable architecture&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the difference between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a demo app&lt;/li&gt;
&lt;li&gt;and a real product&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>swiftui</category>
      <category>architecture</category>
      <category>background</category>
      <category>sync</category>
    </item>
    <item>
      <title>SwiftUI Plugin &amp; Extension Architecture (Modular, Extensible Apps)</title>
      <dc:creator>Sebastien Lato</dc:creator>
      <pubDate>Tue, 10 Feb 2026 15:48:45 +0000</pubDate>
      <link>https://dev.to/sebastienlato/swiftui-plugin-extension-architecture-modular-extensible-apps-22fa</link>
      <guid>https://dev.to/sebastienlato/swiftui-plugin-extension-architecture-modular-extensible-apps-22fa</guid>
      <description>&lt;p&gt;Most apps start simple.&lt;/p&gt;

&lt;p&gt;Then they grow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;new features&lt;/li&gt;
&lt;li&gt;optional tools&lt;/li&gt;
&lt;li&gt;enterprise add-ons&lt;/li&gt;
&lt;li&gt;internal modules&lt;/li&gt;
&lt;li&gt;experimental capabilities&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without structure, this leads to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;feature coupling&lt;/li&gt;
&lt;li&gt;massive app containers&lt;/li&gt;
&lt;li&gt;tangled dependencies&lt;/li&gt;
&lt;li&gt;impossible refactors&lt;/li&gt;
&lt;li&gt;slow builds&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This post shows how to design a &lt;strong&gt;plugin &amp;amp; extension architecture&lt;/strong&gt; in SwiftUI that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;keeps features modular&lt;/li&gt;
&lt;li&gt;allows optional capabilities&lt;/li&gt;
&lt;li&gt;scales with teams&lt;/li&gt;
&lt;li&gt;supports future extensibility&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is how large apps stay maintainable.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧠 The Core Principle
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Features should be pluggable, not hardwired.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If removing a feature breaks your app, the architecture is too coupled.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧱 1. Define a Plugin Protocol
&lt;/h2&gt;

&lt;p&gt;Each feature becomes a plugin.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;protocol&lt;/span&gt; &lt;span class="kt"&gt;AppPlugin&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;container&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;AppContainer&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;Plugins:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;declare themselves&lt;/li&gt;
&lt;li&gt;register dependencies&lt;/li&gt;
&lt;li&gt;expose entry points&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🧬 2. Plugin Responsibilities
&lt;/h2&gt;

&lt;p&gt;A plugin may provide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;routes&lt;/li&gt;
&lt;li&gt;services&lt;/li&gt;
&lt;li&gt;feature flags&lt;/li&gt;
&lt;li&gt;background tasks&lt;/li&gt;
&lt;li&gt;UI entry points&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;ChatPlugin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;AppPlugin&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"chat"&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;container&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;AppContainer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;ChatService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;ChatViewModel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="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 app doesn’t need to know how chat works.&lt;/p&gt;




&lt;h2&gt;
  
  
  📦 3. Plugin Registry
&lt;/h2&gt;

&lt;p&gt;Central registry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;PluginRegistry&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private(set)&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;AppPlugin&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="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;plugin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;AppPlugin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;plugins&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plugin&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;At app startup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="n"&gt;registry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;ChatPlugin&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="n"&gt;registry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;AnalyticsPlugin&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="n"&gt;registry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;ProfilePlugin&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then initialize them.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧭 4. Dynamic Feature Availability
&lt;/h2&gt;

&lt;p&gt;Plugins can be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;always-on&lt;/li&gt;
&lt;li&gt;feature-flagged&lt;/li&gt;
&lt;li&gt;role-based&lt;/li&gt;
&lt;li&gt;environment-based&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;flags&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isEnabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;registry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;ChatPlugin&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;Features now become runtime capabilities.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧠 5. Plugin-Based Navigation
&lt;/h2&gt;

&lt;p&gt;Each plugin can expose routes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;protocol&lt;/span&gt; &lt;span class="kt"&gt;RoutablePlugin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;AppPlugin&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;AppRoute&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&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;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kt"&gt;ChatPlugin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;routes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chatList&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chatDetail&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The router builds navigation dynamically.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔐 6. Dependency Isolation
&lt;/h2&gt;

&lt;p&gt;Plugins should:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;depend on shared domain interfaces&lt;/li&gt;
&lt;li&gt;avoid direct cross-plugin imports&lt;/li&gt;
&lt;li&gt;communicate via protocols&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bad:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;ChatPlugin&lt;/span&gt; &lt;span class="s"&gt;→&lt;/span&gt; &lt;span class="s"&gt;ProfilePlugin&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Good:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;ChatPlugin&lt;/span&gt; &lt;span class="s"&gt;→&lt;/span&gt; &lt;span class="s"&gt;UserServiceProtocol&lt;/span&gt;
&lt;span class="s"&gt;ProfilePlugin&lt;/span&gt; &lt;span class="s"&gt;→&lt;/span&gt; &lt;span class="s"&gt;UserServiceProtocol&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Plugins must remain independent.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧪 7. Testing Plugins in Isolation
&lt;/h2&gt;

&lt;p&gt;Because plugins are modular:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;container&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;TestContainer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kt"&gt;ChatPlugin&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;in&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can test:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;plugin features alone&lt;/li&gt;
&lt;li&gt;plugin integrations&lt;/li&gt;
&lt;li&gt;plugin failure modes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This reduces complexity.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔁 8. Plugin Lifecycle
&lt;/h2&gt;

&lt;p&gt;A plugin should support:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;registration&lt;/li&gt;
&lt;li&gt;activation&lt;/li&gt;
&lt;li&gt;deactivation&lt;/li&gt;
&lt;li&gt;cleanup&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example use cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;enterprise modules&lt;/li&gt;
&lt;li&gt;paid features&lt;/li&gt;
&lt;li&gt;experimental tools&lt;/li&gt;
&lt;li&gt;region-based capabilities&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  ⚠️ 9. Common Plugin Architecture Anti-Patterns
&lt;/h2&gt;

&lt;p&gt;Avoid:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;plugins importing each other&lt;/li&gt;
&lt;li&gt;plugins mutating global state&lt;/li&gt;
&lt;li&gt;plugins without clear ownership&lt;/li&gt;
&lt;li&gt;feature flags scattered in views&lt;/li&gt;
&lt;li&gt;plugins that cannot be disabled&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you can’t remove it, it’s not a plugin.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧠 Mental Model
&lt;/h2&gt;

&lt;p&gt;Think:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;App Core
 → Plugin Registry
   → Plugins
     → Services + Routes + Features
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Everything lives in the main app target”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🚀 Final Thoughts
&lt;/h2&gt;

&lt;p&gt;A plugin architecture gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;modular features&lt;/li&gt;
&lt;li&gt;safer refactors&lt;/li&gt;
&lt;li&gt;faster builds&lt;/li&gt;
&lt;li&gt;clearer ownership&lt;/li&gt;
&lt;li&gt;future extensibility&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is how:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;design tool&lt;/li&gt;
&lt;li&gt;enterprise apps&lt;/li&gt;
&lt;li&gt;collaborative platforms&lt;/li&gt;
&lt;li&gt;complex SaaS products&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;stay maintainable over time.&lt;/p&gt;

</description>
      <category>swiftui</category>
      <category>architecture</category>
      <category>plugins</category>
      <category>modular</category>
    </item>
    <item>
      <title>SwiftUI Multi-Tenant Architecture (Accounts, Workspaces, Isolation)</title>
      <dc:creator>Sebastien Lato</dc:creator>
      <pubDate>Mon, 09 Feb 2026 21:18:23 +0000</pubDate>
      <link>https://dev.to/sebastienlato/swiftui-multi-tenant-architecture-accounts-workspaces-isolation-42p2</link>
      <guid>https://dev.to/sebastienlato/swiftui-multi-tenant-architecture-accounts-workspaces-isolation-42p2</guid>
      <description>&lt;p&gt;Single-user apps are simple.&lt;/p&gt;

&lt;p&gt;But real products eventually need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;multiple accounts&lt;/li&gt;
&lt;li&gt;team workspaces&lt;/li&gt;
&lt;li&gt;organization switching&lt;/li&gt;
&lt;li&gt;sandbox vs production data&lt;/li&gt;
&lt;li&gt;different permission scopes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without proper architecture, this leads to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;mixed user data&lt;/li&gt;
&lt;li&gt;cache corruption&lt;/li&gt;
&lt;li&gt;wrong account actions&lt;/li&gt;
&lt;li&gt;impossible bug reports&lt;/li&gt;
&lt;li&gt;“why is this showing someone else’s data?”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This post shows how to design a &lt;strong&gt;multi-tenant architecture&lt;/strong&gt; in SwiftUI that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;isolates user data&lt;/li&gt;
&lt;li&gt;supports account switching&lt;/li&gt;
&lt;li&gt;prevents cross-tenant bugs&lt;/li&gt;
&lt;li&gt;scales to teams and organizations&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🧠 The Core Principle
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Every piece of data must belong to a tenant.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you don’t know which account owns a piece of state, your architecture is already unsafe.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧱 1. Define the Tenant Model
&lt;/h2&gt;

&lt;p&gt;A tenant represents a data boundary.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;Tenant&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UUID&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Role&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;enum&lt;/span&gt; &lt;span class="kt"&gt;Role&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;personal&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This could represent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a user account&lt;/li&gt;
&lt;li&gt;a workspace&lt;/li&gt;
&lt;li&gt;an organization&lt;/li&gt;
&lt;li&gt;a project context&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🧬 2. Tenant-Aware App State
&lt;/h2&gt;

&lt;p&gt;Never store global user data.&lt;/p&gt;

&lt;p&gt;Bad:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;AppState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;User&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;projects&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Project&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;Correct:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;AppState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;activeTenant&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Tenant&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;tenantStores&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;UUID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;TenantStore&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;Each tenant has its own isolated state.&lt;/p&gt;




&lt;h2&gt;
  
  
  📦 3. Tenant Store
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;TenantStore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ObservableObject&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;@Published&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;User&lt;/span&gt;
    &lt;span class="kd"&gt;@Published&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;projects&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Project&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="kd"&gt;@Published&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;TenantSettings&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no cross-tenant leaks&lt;/li&gt;
&lt;li&gt;clean switching&lt;/li&gt;
&lt;li&gt;independent lifecycles&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🔁 4. Account Switching Architecture
&lt;/h2&gt;

&lt;p&gt;Switching tenants should:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Save current state&lt;/li&gt;
&lt;li&gt;Tear down feature stores&lt;/li&gt;
&lt;li&gt;Activate new tenant store&lt;/li&gt;
&lt;li&gt;Rebuild navigation&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;switchTenant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="nv"&gt;tenant&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Tenant&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;activeTenant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tenant&lt;/span&gt;
    &lt;span class="n"&gt;currentStore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tenantStores&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;tenant&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Never reuse old state across tenants.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧭 5. Tenant-Scoped Services
&lt;/h2&gt;

&lt;p&gt;Services must be tenant-aware.&lt;/p&gt;

&lt;p&gt;Bad:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="n"&gt;apiClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchProjects&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Correct:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="n"&gt;apiClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchProjects&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;for&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;tenantID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or inject a tenant-scoped client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kt"&gt;TenantAPIClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;tenantID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;tenant&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every request must carry tenant context.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧠 6. Storage Isolation
&lt;/h2&gt;

&lt;p&gt;Persist data per tenant:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/Storage
  /tenant_1
    projects.db
  /tenant_2
    projects.db
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or use namespaced keys:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="s"&gt;"tenant_&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;_settings"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Never share storage across tenants.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧪 7. Testing Multi-Tenant Flows
&lt;/h2&gt;

&lt;p&gt;You must test:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;switching accounts&lt;/li&gt;
&lt;li&gt;logging out and back in&lt;/li&gt;
&lt;li&gt;background sync per tenant&lt;/li&gt;
&lt;li&gt;cross-tenant isolation&lt;/li&gt;
&lt;li&gt;permission differences&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Multi-tenant bugs are subtle and dangerous.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔐 8. Permission &amp;amp; Role Awareness
&lt;/h2&gt;

&lt;p&gt;Tenants may have different roles.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tenant&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;admin&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;showAdminPanel&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;Never assume capabilities across tenants.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚠️ 9. Common Multi-Tenant Anti-Patterns
&lt;/h2&gt;

&lt;p&gt;Avoid:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;global singletons for user data&lt;/li&gt;
&lt;li&gt;shared caches across tenants&lt;/li&gt;
&lt;li&gt;not clearing state on switch&lt;/li&gt;
&lt;li&gt;hardcoded user assumptions&lt;/li&gt;
&lt;li&gt;mixing personal and workspace data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These cause the worst production bugs.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧠 Mental Model
&lt;/h2&gt;

&lt;p&gt;Think:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;Tenant
 → Tenant Store
   → Tenant Services
     → Tenant Storage
       → Tenant UI
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“There’s just one user”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🚀 Final Thoughts
&lt;/h2&gt;

&lt;p&gt;A proper multi-tenant architecture gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;safe account switching&lt;/li&gt;
&lt;li&gt;clean workspace logic&lt;/li&gt;
&lt;li&gt;better permission handling&lt;/li&gt;
&lt;li&gt;fewer data leaks&lt;/li&gt;
&lt;li&gt;scalable product design&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your app ever needs teams or organizations,&lt;br&gt;
multi-tenant architecture is not optional.&lt;/p&gt;

</description>
      <category>swiftui</category>
      <category>architecture</category>
      <category>multitenant</category>
      <category>accounts</category>
    </item>
    <item>
      <title>SwiftUI Tech Debt Management System (Refactor Without Fear)</title>
      <dc:creator>Sebastien Lato</dc:creator>
      <pubDate>Sat, 07 Feb 2026 17:36:23 +0000</pubDate>
      <link>https://dev.to/sebastienlato/swiftui-tech-debt-management-system-refactor-without-fear-4344</link>
      <guid>https://dev.to/sebastienlato/swiftui-tech-debt-management-system-refactor-without-fear-4344</guid>
      <description>&lt;p&gt;Every successful app accumulates tech debt.&lt;/p&gt;

&lt;p&gt;Not because developers are bad —&lt;br&gt;
but because &lt;strong&gt;software changes faster than plans&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The problem isn’t debt.&lt;br&gt;
The problem is &lt;strong&gt;unmanaged debt&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fear of refactoring&lt;/li&gt;
&lt;li&gt;fragile systems&lt;/li&gt;
&lt;li&gt;frozen code paths&lt;/li&gt;
&lt;li&gt;endless “we’ll fix it later”&lt;/li&gt;
&lt;li&gt;slow velocity over time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This post shows how to design a &lt;strong&gt;tech debt management system&lt;/strong&gt; for SwiftUI that lets you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;move fast without fear&lt;/li&gt;
&lt;li&gt;refactor safely&lt;/li&gt;
&lt;li&gt;make debt visible&lt;/li&gt;
&lt;li&gt;keep architecture healthy for years&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  🧠 The Core Principle
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tech debt is inevitable — unmanaged debt is optional.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You don’t eliminate debt.&lt;br&gt;
You &lt;strong&gt;control it&lt;/strong&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  🧱 1. Make Debt Explicit
&lt;/h2&gt;

&lt;p&gt;Debt that isn’t visible doesn’t exist.&lt;/p&gt;

&lt;p&gt;Define a model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;TechDebtItem&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;area&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;impact&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Impact&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;urgency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Urgency&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Debt must be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;documented&lt;/li&gt;
&lt;li&gt;categorized&lt;/li&gt;
&lt;li&gt;prioritized&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Comments are not enough.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧬 2. Classify Debt Types
&lt;/h2&gt;

&lt;p&gt;Not all debt is equal.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;architectural debt&lt;/li&gt;
&lt;li&gt;performance debt&lt;/li&gt;
&lt;li&gt;test debt&lt;/li&gt;
&lt;li&gt;dependency debt&lt;/li&gt;
&lt;li&gt;UX debt&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Treating all debt the same guarantees bad decisions.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧭 3. Debt Budgets Per Feature
&lt;/h2&gt;

&lt;p&gt;Every feature gets a debt budget.&lt;/p&gt;

&lt;p&gt;Rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;new debt must be declared&lt;/li&gt;
&lt;li&gt;budget must be repaid&lt;/li&gt;
&lt;li&gt;debt does not roll forever&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This turns debt into a conscious tradeoff, not an accident.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧪 4. Protect Refactors with Tests
&lt;/h2&gt;

&lt;p&gt;Refactors require:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;strong unit coverage&lt;/li&gt;
&lt;li&gt;stable integration tests&lt;/li&gt;
&lt;li&gt;clear invariants&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’re afraid to refactor, tests are missing.&lt;/p&gt;

&lt;p&gt;Refactoring fear is a signal, not a failure.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧠 5. Incremental Refactoring Strategy
&lt;/h2&gt;

&lt;p&gt;Never “big bang” refactors.&lt;/p&gt;

&lt;p&gt;Instead:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;isolate old code&lt;/li&gt;
&lt;li&gt;introduce seams&lt;/li&gt;
&lt;li&gt;migrate gradually&lt;/li&gt;
&lt;li&gt;remove dead paths&lt;/li&gt;
&lt;li&gt;ship continuously&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Flags and adapters are your friends.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔁 6. Track Debt Over Time
&lt;/h2&gt;

&lt;p&gt;Debt should:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;trend downward&lt;/li&gt;
&lt;li&gt;spike intentionally&lt;/li&gt;
&lt;li&gt;be reviewed regularly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If debt only grows, architecture is decaying.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚠️ 7. Avoid These Debt Traps
&lt;/h2&gt;

&lt;p&gt;Avoid:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TODO without owner&lt;/li&gt;
&lt;li&gt;“temporary” hacks&lt;/li&gt;
&lt;li&gt;shared dumping grounds&lt;/li&gt;
&lt;li&gt;skipped tests “just this once”&lt;/li&gt;
&lt;li&gt;ignoring architectural violations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Small shortcuts compound quickly.&lt;/p&gt;

&lt;h2&gt;
  
  
  🧠 Mental Model
&lt;/h2&gt;

&lt;p&gt;Think:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;Debt Identified
 → Tracked
   → Budgeted
     → Repaid
       → System Health
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“We’ll clean it up later”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🚀 Final Thoughts
&lt;/h2&gt;

&lt;p&gt;A real tech debt system gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;confidence to change code&lt;/li&gt;
&lt;li&gt;sustainable velocity&lt;/li&gt;
&lt;li&gt;happier teams&lt;/li&gt;
&lt;li&gt;fewer rewrites&lt;/li&gt;
&lt;li&gt;long-lived products&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tech debt isn’t a failure.&lt;br&gt;
Ignoring it is.&lt;/p&gt;

</description>
      <category>swiftui</category>
      <category>architecture</category>
      <category>techdebt</category>
      <category>maintenance</category>
    </item>
    <item>
      <title>SwiftUI Build &amp; CI/CD Architecture (Ship Fast Without Breaking Things)</title>
      <dc:creator>Sebastien Lato</dc:creator>
      <pubDate>Thu, 05 Feb 2026 23:45:46 +0000</pubDate>
      <link>https://dev.to/sebastienlato/swiftui-build-cicd-architecture-ship-fast-without-breaking-things-knf</link>
      <guid>https://dev.to/sebastienlato/swiftui-build-cicd-architecture-ship-fast-without-breaking-things-knf</guid>
      <description>&lt;p&gt;Most iOS teams think CI/CD is “running tests”.&lt;/p&gt;

&lt;p&gt;In reality, it’s the &lt;strong&gt;last line of architectural defense&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;bad dependencies should fail builds&lt;/li&gt;
&lt;li&gt;performance regressions should block merges&lt;/li&gt;
&lt;li&gt;broken localization should never ship&lt;/li&gt;
&lt;li&gt;flaky tests should be visible, not ignored&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This post shows how to design a &lt;strong&gt;production-grade CI/CD architecture&lt;/strong&gt; for SwiftUI that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;enforces quality automatically&lt;/li&gt;
&lt;li&gt;scales with team size&lt;/li&gt;
&lt;li&gt;prevents regressions&lt;/li&gt;
&lt;li&gt;keeps release velocity high&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🧠 The Core Principle
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;If quality is optional, it will be skipped.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;CI/CD exists to make quality &lt;strong&gt;non-optional&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧱 1. CI Is an Architectural Boundary
&lt;/h2&gt;

&lt;p&gt;CI is not tooling glue.&lt;br&gt;
It is an extension of your architecture.&lt;/p&gt;

&lt;p&gt;Anything you &lt;em&gt;care about&lt;/em&gt; must be enforced here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;dependency rules&lt;/li&gt;
&lt;li&gt;test coverage&lt;/li&gt;
&lt;li&gt;performance budgets&lt;/li&gt;
&lt;li&gt;linting&lt;/li&gt;
&lt;li&gt;build reproducibility&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If CI doesn’t fail, the system allowed it.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧬 2. Deterministic Builds
&lt;/h2&gt;

&lt;p&gt;Your build must be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;reproducible&lt;/li&gt;
&lt;li&gt;deterministic&lt;/li&gt;
&lt;li&gt;environment-independent&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;lock dependency versions&lt;/li&gt;
&lt;li&gt;avoid implicit build settings&lt;/li&gt;
&lt;li&gt;avoid local-only scripts&lt;/li&gt;
&lt;li&gt;no manual build steps&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A build that only works on one machine is already broken.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧪 3. Test Pyramid Enforcement
&lt;/h2&gt;

&lt;p&gt;Automate the pyramid:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unit tests → fast, numerous&lt;/li&gt;
&lt;li&gt;Integration tests → focused&lt;/li&gt;
&lt;li&gt;UI tests → minimal, critical paths only&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fail the build if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;unit tests fail&lt;/li&gt;
&lt;li&gt;critical UI flows break&lt;/li&gt;
&lt;li&gt;flaky tests exceed threshold&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;CI should surface instability early.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚡ 4. Performance Regression Gates
&lt;/h2&gt;

&lt;p&gt;Performance is a feature.&lt;/p&gt;

&lt;p&gt;Track:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;app launch time&lt;/li&gt;
&lt;li&gt;critical screen render times&lt;/li&gt;
&lt;li&gt;memory peaks&lt;/li&gt;
&lt;li&gt;scroll performance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Block merges when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;metrics exceed baseline&lt;/li&gt;
&lt;li&gt;regressions cross tolerance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Performance regressions are &lt;strong&gt;bugs&lt;/strong&gt;, not suggestions.&lt;/p&gt;




&lt;h2&gt;
  
  
  🌍 5. Localization &amp;amp; Accessibility Checks
&lt;/h2&gt;

&lt;p&gt;CI should validate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;missing localization keys&lt;/li&gt;
&lt;li&gt;broken RTL layouts (snapshot tests)&lt;/li&gt;
&lt;li&gt;dynamic type scaling&lt;/li&gt;
&lt;li&gt;accessibility labels presence&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If CI doesn’t test this, production users will.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔐 6. Security &amp;amp; Configuration Validation
&lt;/h2&gt;

&lt;p&gt;Validate in CI:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;secrets not committed&lt;/li&gt;
&lt;li&gt;debug flags not enabled&lt;/li&gt;
&lt;li&gt;logging levels correct&lt;/li&gt;
&lt;li&gt;environment config sanity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Never rely on manual review for this.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧭 7. CI Stages (Recommended)
&lt;/h2&gt;

&lt;p&gt;Example pipeline:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Static analysis (lint, format, dependency audit)&lt;/li&gt;
&lt;li&gt;Unit tests&lt;/li&gt;
&lt;li&gt;Integration tests&lt;/li&gt;
&lt;li&gt;Snapshot tests&lt;/li&gt;
&lt;li&gt;Performance checks&lt;/li&gt;
&lt;li&gt;Build artifact validation&lt;/li&gt;
&lt;li&gt;Archive &amp;amp; distribute&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each stage narrows risk.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧠 8. Artifact-Centered Releases
&lt;/h2&gt;

&lt;p&gt;CI should produce:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;signed build artifacts&lt;/li&gt;
&lt;li&gt;versioned archives&lt;/li&gt;
&lt;li&gt;reproducible outputs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Release pipelines consume &lt;strong&gt;artifacts&lt;/strong&gt;, not source.&lt;/p&gt;

&lt;p&gt;This guarantees what you tested is what you ship.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚠️ 9. Common CI/CD Anti-Patterns
&lt;/h2&gt;

&lt;p&gt;Avoid:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;skipping CI for “small changes”&lt;/li&gt;
&lt;li&gt;flaky tests ignored&lt;/li&gt;
&lt;li&gt;manual release steps&lt;/li&gt;
&lt;li&gt;performance checks removed “temporarily”&lt;/li&gt;
&lt;li&gt;CI that only runs on main&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These create silent regressions.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧠 Mental Model
&lt;/h2&gt;

&lt;p&gt;Think:&lt;br&gt;
Code Change&lt;br&gt;
→ CI Enforcement&lt;br&gt;
→ Artifact&lt;br&gt;
→ Release&lt;/p&gt;

&lt;p&gt;Not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“It passed locally”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🚀 Final Thoughts
&lt;/h2&gt;

&lt;p&gt;A strong CI/CD architecture gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;faster releases&lt;/li&gt;
&lt;li&gt;safer refactors&lt;/li&gt;
&lt;li&gt;fewer production incidents&lt;/li&gt;
&lt;li&gt;calmer teams&lt;/li&gt;
&lt;li&gt;real confidence&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;CI/CD is not overhead.&lt;br&gt;
It is &lt;strong&gt;operational architecture&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>swiftui</category>
      <category>cicd</category>
      <category>build</category>
      <category>architecture</category>
    </item>
    <item>
      <title>SwiftUI Dependency Graph Visualization &amp; Auditing (Enforce Architecture, Don’t Trust It)</title>
      <dc:creator>Sebastien Lato</dc:creator>
      <pubDate>Wed, 04 Feb 2026 18:09:42 +0000</pubDate>
      <link>https://dev.to/sebastienlato/swiftui-dependency-graph-visualization-auditing-enforce-architecture-dont-trust-it-137l</link>
      <guid>https://dev.to/sebastienlato/swiftui-dependency-graph-visualization-auditing-enforce-architecture-dont-trust-it-137l</guid>
      <description>&lt;p&gt;Every large SwiftUI codebase eventually says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“We follow clean architecture.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then six months later:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;features import each other&lt;/li&gt;
&lt;li&gt;ViewModels talk to services directly&lt;/li&gt;
&lt;li&gt;infrastructure leaks upward&lt;/li&gt;
&lt;li&gt;cycles appear&lt;/li&gt;
&lt;li&gt;nobody knows what depends on what&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Architecture without &lt;strong&gt;enforcement&lt;/strong&gt; always decays.&lt;/p&gt;

&lt;p&gt;This post shows how to design &lt;strong&gt;dependency graph visualization &amp;amp; auditing&lt;/strong&gt; for SwiftUI apps so your architecture is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;visible&lt;/li&gt;
&lt;li&gt;enforceable&lt;/li&gt;
&lt;li&gt;reviewable&lt;/li&gt;
&lt;li&gt;resilient over time&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🧠 The Core Principle
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Architecture is a graph — not a guideline.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you can’t see your dependency graph, you can’t control it.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧱 1. Define Allowed Dependency Directions
&lt;/h2&gt;

&lt;p&gt;Before tooling, define rules.&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;p&gt;Views&lt;br&gt;
→ ViewModels&lt;br&gt;
→ Domain&lt;br&gt;
→ Infrastructure&lt;/p&gt;

&lt;p&gt;Rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no upward dependencies&lt;/li&gt;
&lt;li&gt;no cross-feature imports&lt;/li&gt;
&lt;li&gt;infrastructure never imports features&lt;/li&gt;
&lt;li&gt;features never import each other directly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Write this down. This is your contract.&lt;/p&gt;


&lt;h2&gt;
  
  
  🧬 2. Treat Modules as Graph Nodes
&lt;/h2&gt;

&lt;p&gt;Each logical module is a node:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Feature modules&lt;/li&gt;
&lt;li&gt;Domain modules&lt;/li&gt;
&lt;li&gt;Infrastructure modules&lt;/li&gt;
&lt;li&gt;Shared utilities&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The graph edges are &lt;strong&gt;imports&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;imports = architectural dependencies&lt;/li&gt;
&lt;li&gt;build errors = enforcement points&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  🔍 3. Generate the Dependency Graph
&lt;/h2&gt;

&lt;p&gt;Use static analysis to extract imports.&lt;/p&gt;

&lt;p&gt;Example approaches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Swift Package Manager manifests&lt;/li&gt;
&lt;li&gt;Xcode build logs&lt;/li&gt;
&lt;li&gt;custom SourceKit parsing&lt;/li&gt;
&lt;li&gt;scripts that scan &lt;code&gt;import&lt;/code&gt; statements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Output a graph format (DOT / JSON):&lt;/p&gt;

&lt;p&gt;FeatureA → Domain&lt;br&gt;
FeatureA → FeatureB ❌&lt;/p&gt;

&lt;p&gt;Visualization exposes reality instantly.&lt;/p&gt;


&lt;h2&gt;
  
  
  🧭 4. Visualize the Graph
&lt;/h2&gt;

&lt;p&gt;Render the graph using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Graphviz&lt;/li&gt;
&lt;li&gt;Mermaid&lt;/li&gt;
&lt;li&gt;custom dashboards&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You should be able to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;see cycles&lt;/li&gt;
&lt;li&gt;spot illegal edges&lt;/li&gt;
&lt;li&gt;identify hotspots&lt;/li&gt;
&lt;li&gt;track growth over time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If architecture can’t be visualized, it can’t be trusted.&lt;/p&gt;


&lt;h2&gt;
  
  
  🧪 5. Automated Dependency Audits
&lt;/h2&gt;

&lt;p&gt;Turn architecture rules into tests.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;fail &lt;span class="k"&gt;if &lt;/span&gt;Feature imports Infrastructure
fail &lt;span class="k"&gt;if &lt;/span&gt;Feature imports Feature
fail &lt;span class="k"&gt;if &lt;/span&gt;cycle detected
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run audits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;locally&lt;/li&gt;
&lt;li&gt;in CI&lt;/li&gt;
&lt;li&gt;on every PR&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Architecture violations should fail builds — not code reviews.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧠 6. Ownership &amp;amp; Boundaries
&lt;/h2&gt;

&lt;p&gt;Every node should have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an owner&lt;/li&gt;
&lt;li&gt;a purpose&lt;/li&gt;
&lt;li&gt;allowed dependencies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When a dependency is added:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ask why&lt;/li&gt;
&lt;li&gt;document it&lt;/li&gt;
&lt;li&gt;review the boundary&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Undefined ownership accelerates decay.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔁 7. Detect Cycles Early
&lt;/h2&gt;

&lt;p&gt;Cycles kill:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;testability&lt;/li&gt;
&lt;li&gt;refactors&lt;/li&gt;
&lt;li&gt;parallel work&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Graph tooling should detect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;direct cycles&lt;/li&gt;
&lt;li&gt;transitive cycles&lt;/li&gt;
&lt;li&gt;hidden cycles via shared utilities&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Break cycles immediately — they only get worse.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚠️ 8. Common Dependency Anti-Patterns
&lt;/h2&gt;

&lt;p&gt;Avoid:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;shared “Utils” modules that import everything&lt;/li&gt;
&lt;li&gt;feature-to-feature imports&lt;/li&gt;
&lt;li&gt;infrastructure leaking into ViewModels&lt;/li&gt;
&lt;li&gt;global singletons as dependency shortcuts&lt;/li&gt;
&lt;li&gt;“temporary” imports that stay forever&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If it’s convenient, it’s probably wrong.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧠 Mental Model
&lt;/h2&gt;

&lt;p&gt;Think:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;Architecture Rules
 → Dependency Graph
   → Automated Audits
     → Build Enforcement
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Let’s try to remember the rules”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🚀 Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Dependency graph auditing gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;enforceable architecture&lt;/li&gt;
&lt;li&gt;fearless refactoring&lt;/li&gt;
&lt;li&gt;faster onboarding&lt;/li&gt;
&lt;li&gt;safer scaling&lt;/li&gt;
&lt;li&gt;long-lived codebases&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Architecture is not documentation.&lt;br&gt;
It is constraint + visibility.&lt;/p&gt;

</description>
      <category>swiftui</category>
      <category>architecture</category>
      <category>dependencies</category>
      <category>tooling</category>
    </item>
  </channel>
</rss>
