<?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: Chris</title>
    <description>The latest articles on DEV Community by Chris (@criscmd).</description>
    <link>https://dev.to/criscmd</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F844017%2F83fd4977-3a5d-4125-bf9e-c82f66c00690.jpg</url>
      <title>DEV Community: Chris</title>
      <link>https://dev.to/criscmd</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/criscmd"/>
    <language>en</language>
    <item>
      <title>I Didn't Need Microservices. I Needed One of Their Superpowers</title>
      <dc:creator>Chris</dc:creator>
      <pubDate>Thu, 13 Aug 2026 02:05:23 +0000</pubDate>
      <link>https://dev.to/criscmd/i-didnt-need-microservices-i-needed-one-of-their-superpowers-1peg</link>
      <guid>https://dev.to/criscmd/i-didnt-need-microservices-i-needed-one-of-their-superpowers-1peg</guid>
      <description>&lt;p&gt;Everyone tells you not to build microservices until you have the org chart for it. They're right. I'm basically a one-person team on this codebase. No domain teams, no payments-vs-inventory boundaries, one shared database.&lt;/p&gt;

&lt;p&gt;And I still split a service out of my monolith last month.&lt;/p&gt;

&lt;p&gt;Not because I needed microservices, but because I needed exactly &lt;em&gt;one&lt;/em&gt; of their superpowers. It turns out you can take that superpower without taking the whole religion. The pattern has a boring name, &lt;strong&gt;technical partitioning&lt;/strong&gt;, and it's been the most fun I've had architecting in a while.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: one feature with a completely different metabolism
&lt;/h2&gt;

&lt;p&gt;The product is a visitor-analytics app built on Streamlit. It's a great fit for what it does: analysts pick locations (POIs), the app renders reports. Python, Postgres, Redis cache, done.&lt;/p&gt;

&lt;p&gt;Then the requirement arrived: &lt;strong&gt;typeahead search over ~1.2 million POIs&lt;/strong&gt;. Per keystroke. With fuzzy matching, alias handling (Japanese chain names have &lt;em&gt;many&lt;/em&gt; spellings), and ranking.&lt;/p&gt;

&lt;p&gt;Think about what that means inside a Streamlit app:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The search index wants to live &lt;strong&gt;in memory&lt;/strong&gt; (~270MB resident once built) and answer in single-digit milliseconds.&lt;/li&gt;
&lt;li&gt;Streamlit's execution model re-runs your script on every interaction. It's wonderful for dashboards and absolutely hostile to "hold a giant hot data structure and answer 20 queries a second."&lt;/li&gt;
&lt;li&gt;And here's the kicker: when the app gets busy and we scale horizontally, every replica would drag that 270MB index along, for a feature that has &lt;em&gt;nothing to do with&lt;/em&gt; why we're scaling.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last point is the Scale Cube in one sentence. Horizontal scaling (X-axis) clones your &lt;em&gt;entire&lt;/em&gt; application. Functional decomposition (Y-axis) splits it so you only multiply the part that's hot. Most of us reach for X by reflex: add another instance, tune the autoscaler, move on. It works until the thing you're cloning is mostly dead weight.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I actually wanted from microservices
&lt;/h2&gt;

&lt;p&gt;When I listed what I wanted, it was embarrassingly short:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A different runtime.&lt;/strong&gt; This workload wants a compiled language and an in-memory index. I wanted Rust for this, and I didn't want to justify rewriting anything else.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Independent sizing.&lt;/strong&gt; Search needs memory and CPU with low latency. The app needs neither in the same shape. I wanted two knobs, not one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Independent failure.&lt;/strong&gt; If search dies, reports should still render.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Independent deploys.&lt;/strong&gt; Shipping a ranking tweak shouldn't redeploy the app.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Notice what's &lt;em&gt;not&lt;/em&gt; on the list: separate teams, separate repos, separate databases, event buses, service meshes, distributed sagas. All the parts of microservices that hurt? I didn't need any of them.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Software Architecture: The Hard Parts&lt;/em&gt; calls the legitimate reasons to split a service "granularity disintegrators": service scope, code volatility, &lt;strong&gt;scalability &amp;amp; throughput&lt;/strong&gt;, &lt;strong&gt;fault tolerance&lt;/strong&gt;, security, extensibility. My case checked three of six, and none of them were "it's a different business domain." That's the tell that this is a &lt;em&gt;technical&lt;/em&gt; partition, not a domain one. Knowing which kind you're doing keeps you honest about what you owe in return.&lt;/p&gt;

&lt;h2&gt;
  
  
  The shape: one repo, one cluster, two metabolisms
&lt;/h2&gt;

&lt;p&gt;So here's what "microservice flexibility without the microservices" ended up looking like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;repo/
├── app/            # Streamlit monolith (Python), untouched
└── api/            # Rust workspace
    ├── crates/infra     # Redis + Postgres clients. Technology, no domain.
    └── crates/catalog   # The deployable: search + (future) POI-catalog features
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Same repo.&lt;/strong&gt; A cross-cutting change (like when the frontend's search payload contract changed) is one commit, one review. No multi-repo choreography.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Same ECS cluster, separate service.&lt;/strong&gt; A cluster is a namespace, not an isolation boundary. Isolation lives one level down: the search service gets its own task definition, its own security groups, its own (narrower) IAM role, its own instance sizing. On the AWS bill, the hot path and the cozy path finally show up as separate line items.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Same database.&lt;/strong&gt; Heresy in microservice-land, completely fine here. Mark Richards would call this &lt;em&gt;service-based architecture&lt;/em&gt;: a small number of coarse-grained deployables, shared data, ops far simpler than a fleet.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The data flow is deliberately boring: &lt;strong&gt;Postgres → Redis → container memory.&lt;/strong&gt; A publisher batch job snapshots the catalog into Redis (gzipped, chunked, blue-green keys); the Rust service loads it once at boot and serves searches purely from memory. Redis stays out of the request path. Boot takes ~20 seconds; queries take 3-5ms.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that keeps it from becoming theater
&lt;/h2&gt;

&lt;p&gt;"Clean architecture" gets a bad name because most implementations are theater: interfaces with one implementation, DTOs mapped to identical DTOs, five layers guarding nothing. Two rules kept this one surgical:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Abstract technology, not domain.&lt;/strong&gt; &lt;code&gt;crates/infra&lt;/code&gt; knows how to pool Postgres connections, do TLS, stream a million rows, and chunk Redis blobs. It does not know what a POI is. The SQL lives in the catalog's repository layer. When I later had to add TLS for the managed Postgres (it rejects plaintext; ask me how I know), the fix touched &lt;em&gt;one file&lt;/em&gt; in &lt;code&gt;infra&lt;/code&gt;, and every current and future consumer got encrypted connections for free. Change-size versus effect-size: that's the only abstraction metric I trust.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Let the compiler enforce the layering.&lt;/strong&gt; The rule is "service code never imports the web framework or the Redis client." In most codebases that rule lives in a wiki and erodes one expedient import at a time. In a Cargo workspace, the service crate simply doesn't have those dependencies in scope, so violating the architecture is a &lt;em&gt;compile error&lt;/em&gt;. Cheapest architecture cop money can buy.&lt;/p&gt;

&lt;p&gt;And one boundary decision I'd defend hard: the service is named &lt;code&gt;catalog&lt;/code&gt;, not &lt;code&gt;search&lt;/code&gt;. Search is the first feature, but the deployable is a bounded context: reference data about places, and everything that operates on it. Future features land as siblings inside it instead of spawning &lt;code&gt;search-service-2&lt;/code&gt;. The &lt;em&gt;reason&lt;/em&gt; for the split was technical; the &lt;em&gt;boundary&lt;/em&gt; still follows a domain seam. That's the combination that ages well. A partition cut purely along "fast stuff goes here" becomes a &lt;code&gt;misc-performance&lt;/code&gt; junk drawer within a year.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure is a feature
&lt;/h2&gt;

&lt;p&gt;Because search is now a network dependency, the frontend treats it like one. A cheap health check (cached ~30s) decides which UI to render: the API-backed free-text search, or the original database-driven picker as an automatic fallback. Kill the search container and the app quietly downgrades within seconds; bring it back and it upgrades again. Nobody files a ticket.&lt;/p&gt;

&lt;p&gt;The same idea gates the rollout. The frontend code shipped &lt;em&gt;before&lt;/em&gt; any infrastructure existed. With the API URL unset, it's inert and renders the legacy path. Activation is two environment variables on the app's task definition. Merge order stopped mattering. Deploys became boring. Boring is the goal.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bill
&lt;/h2&gt;

&lt;p&gt;Honesty section: you do pay for this.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One more deployable: image build, service, target group, health checks, log group.&lt;/li&gt;
&lt;li&gt;Snapshot publishing needs an operational answer (ours: a batch job, plus a service restart until hot-reload lands).&lt;/li&gt;
&lt;li&gt;You now own cross-service &lt;em&gt;contracts&lt;/em&gt;. Ours is a small JSON payload, and byte-level serialization details (looking at you, &lt;code&gt;serde_json&lt;/code&gt; vs Python's &lt;code&gt;json.dumps&lt;/code&gt; separators) become things you actually think about.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's real overhead. It's also a fraction of what full microservices cost, because we skipped the expensive parts: no distributed data, no inter-service call graphs, no team coordination tax.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;When you're resource-tight, notice &lt;em&gt;which axis&lt;/em&gt; you're reaching for. Horizontal scaling clones everything; if one feature has a different metabolism, a functional split multiplies only what's hot.&lt;/li&gt;
&lt;li&gt;You can adopt microservice &lt;em&gt;flexibility&lt;/em&gt; (runtime freedom, independent sizing, failure isolation, independent deploys) without adopting microservice &lt;em&gt;architecture&lt;/em&gt;. One repo, one cluster, shared database, two deployables.&lt;/li&gt;
&lt;li&gt;Split for technical reasons if you must, but &lt;strong&gt;draw the boundary on a domain seam&lt;/strong&gt; anyway. Name the service after what it's about, not what it's fast at.&lt;/li&gt;
&lt;li&gt;Abstract technology, never domain. And if you can, make the compiler enforce your layering instead of a wiki page.&lt;/li&gt;
&lt;li&gt;Build the fallback before you build the infrastructure. A new dependency you can survive losing is a dependency you can ship calmly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Further reading, the sources behind the ideas: &lt;a href="https://microservices.io/articles/scalecube.html" rel="noopener noreferrer"&gt;The Scale Cube&lt;/a&gt; (&lt;a href="https://akfpartners.com/growth-blog/scale-cube" rel="noopener noreferrer"&gt;AKF original&lt;/a&gt;), the granularity chapter of &lt;em&gt;Software Architecture: The Hard Parts&lt;/em&gt; (&lt;a href="https://www.thoughtworks.com/content/dam/thoughtworks/documents/books/bk_software_architecture_hard_parts_ch7_en.pdf" rel="noopener noreferrer"&gt;free chapter PDF&lt;/a&gt;), and &lt;a href="https://software-architecture-guild.com/guide/architecture/styles/service-based/" rel="noopener noreferrer"&gt;service-based architecture&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>architecture</category>
      <category>python</category>
      <category>microservices</category>
    </item>
    <item>
      <title>title: Mutex vs RwLock vs ArcSwap. A when-to-use-what memo</title>
      <dc:creator>Chris</dc:creator>
      <pubDate>Fri, 07 Aug 2026 06:04:25 +0000</pubDate>
      <link>https://dev.to/criscmd/title-mutex-vs-rwlock-vs-arcswap-a-when-to-use-what-memo-2ajg</link>
      <guid>https://dev.to/criscmd/title-mutex-vs-rwlock-vs-arcswap-a-when-to-use-what-memo-2ajg</guid>
      <description>&lt;p&gt;This is a memo to future me, for when I get rusty (yes).&lt;/p&gt;

&lt;p&gt;Coming from TypeScript and Python, shared-state concurrency is a topic we literally never had to deal with — the runtime dealt with it for us. Rust hands you the steering wheel, and in exchange makes the worst crash a compile error. This is my map of the territory: what a data race actually is, when to use &lt;code&gt;Mutex&lt;/code&gt; vs &lt;code&gt;RwLock&lt;/code&gt; vs &lt;code&gt;ArcSwap&lt;/code&gt;, and why I shipped &lt;code&gt;ArcSwap&lt;/code&gt; in a recent project.&lt;/p&gt;




&lt;h2&gt;
  
  
  The bug we never had to think about
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;data race&lt;/strong&gt; is: two threads touch the same memory, at least one of them writes, and nothing synchronizes them. The result isn't "sometimes wrong" — it's undefined behavior. Lost updates, torn values, and the compiler optimizing your code under the assumption that races can't happen, so "it looks fine on my machine" means nothing.&lt;/p&gt;

&lt;p&gt;The classic minimal case — two threads doing &lt;code&gt;count += 1&lt;/code&gt;. That's a read, a modify, and a write; interleave two of them and an increment vanishes.&lt;/p&gt;

&lt;p&gt;Why we never saw this in our world: Python has the GIL — one thread executes bytecode at a time. JavaScript is one thread with an event loop. We had &lt;strong&gt;race conditions&lt;/strong&gt; — two &lt;code&gt;await&lt;/code&gt;s interleaving around a check-then-act — but never &lt;strong&gt;data races&lt;/strong&gt;, because there was never true simultaneous memory access.&lt;/p&gt;

&lt;p&gt;That distinction matters, so to be precise about what Rust does and doesn't do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data race&lt;/strong&gt; — memory-level, UB. Rust makes this &lt;em&gt;fail to compile&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Race condition&lt;/strong&gt; — logic-level interleaving bug. Still entirely possible in Rust. The borrow checker is not a logic checker.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Rust's actual trick
&lt;/h2&gt;

&lt;p&gt;Rust's borrow rule is: any number of readers &lt;strong&gt;or&lt;/strong&gt; exactly one writer — &lt;code&gt;&amp;amp;T&lt;/code&gt; xor &lt;code&gt;&amp;amp;mut T&lt;/code&gt;. That's usually explained as a memory-safety rule, but look again: it's literally the definition of data-race-freedom, enforced at compile time. &lt;code&gt;Send&lt;/code&gt; and &lt;code&gt;Sync&lt;/code&gt; extend the same rule across threads.&lt;/p&gt;

&lt;p&gt;So when you genuinely need shared mutable state, you need something that &lt;em&gt;restores&lt;/em&gt; that rule at runtime. That's all a &lt;code&gt;Mutex&amp;lt;T&amp;gt;&lt;/code&gt; is — &lt;strong&gt;a queue to get a &lt;code&gt;&amp;amp;mut T&lt;/code&gt;&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Mutex&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Stats&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="nf"&gt;.lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// runtime-checked exclusive borrow&lt;/span&gt;
&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="py"&gt;.hits&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="c1"&gt;// guard drops → unlocked → next thread's turn&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unlike most languages, the mutex &lt;strong&gt;owns the data&lt;/strong&gt;. You can't reach the &lt;code&gt;Stats&lt;/code&gt; without going through &lt;code&gt;lock()&lt;/code&gt;, and unlock is &lt;code&gt;Drop&lt;/code&gt;, so you can't forget it. Two entire bug classes gone by construction.&lt;/p&gt;

&lt;p&gt;(That &lt;code&gt;.unwrap()&lt;/code&gt; is policy, not laziness: a poisoned mutex means another thread panicked mid-update and the data may be half-written. Crashing beats trusting it.)&lt;/p&gt;

&lt;p&gt;What the compiler &lt;em&gt;can't&lt;/em&gt; choose for you is which tool. That's the rest of this memo.&lt;/p&gt;




&lt;h2&gt;
  
  
  Mutex — the default
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Use when:&lt;/strong&gt; shared state, any read/write mix, short critical sections. Which is most of the time.&lt;/p&gt;

&lt;p&gt;An uncontended &lt;code&gt;lock()&lt;/code&gt; costs on the order of tens of nanoseconds — it is almost never your bottleneck. The craft is in how you hold it, not whether you use it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Compute outside the lock, mutate inside it.&lt;/li&gt;
&lt;li&gt;Never hold a guard across IO, an &lt;code&gt;.await&lt;/code&gt;, or a call into code you don't own.&lt;/li&gt;
&lt;li&gt;Drop guards early — an explicit &lt;code&gt;drop(guard)&lt;/code&gt; or a scoped block reads as intent.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Avoid when:&lt;/strong&gt; reads vastly outnumber writes &lt;em&gt;and&lt;/em&gt; measurably contend — that's the next rung. Not before you've measured.&lt;/p&gt;




&lt;h2&gt;
  
  
  RwLock — parallel readers, with fine print
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Use when:&lt;/strong&gt; many readers, rare writers, and — the part everyone skips — read sections &lt;strong&gt;long enough to actually overlap&lt;/strong&gt;. A reader scanning a structure for microseconds while other readers do the same: that's the RwLock use case.&lt;/p&gt;

&lt;p&gt;The fine print that makes it a sidegrade more often than people admit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Acquiring a read lock still performs an atomic read-modify-write on the shared lock word. On a hot path, that one cache line ping-pongs across every core — short, frequent reads can be &lt;strong&gt;slower than a plain Mutex&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Writer fairness in &lt;code&gt;std&lt;/code&gt; is OS-dependent; writers can starve under heavy read load.&lt;/li&gt;
&lt;li&gt;Consistency only exists &lt;em&gt;while you hold the guard&lt;/em&gt;. Re-lock, and the world may have changed between.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Avoid when:&lt;/strong&gt; reads are short and hot (Mutex wins), or when writes replace the whole value anyway — because then there's a tool with no lock at all.&lt;/p&gt;




&lt;h2&gt;
  
  
  ArcSwap — replace, don't mutate
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Use when:&lt;/strong&gt; read-mostly data whose updates are &lt;strong&gt;wholesale replacements&lt;/strong&gt; — nobody ever edits one field in place; a new complete version supersedes the old one. Config reloads, routing tables, in-memory indexes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;arc_swap&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ArcSwap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;CONFIG&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ArcSwap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// reader — no lock, effectively wait-free, coherent snapshot&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CONFIG&lt;/span&gt;&lt;span class="nf"&gt;.load&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nf"&gt;serve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// writer — build a complete new value off to the side, swap once&lt;/span&gt;
&lt;span class="n"&gt;CONFIG&lt;/span&gt;&lt;span class="nf"&gt;.store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Arc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next_config&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Readers get an &lt;code&gt;Arc&lt;/code&gt; snapshot: always a &lt;em&gt;complete&lt;/em&gt; version, never a half-updated one. Writers pay the full rebuild cost, but nobody waits for them. In-flight readers keep the old version alive until they're done with it; new readers see the new one immediately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Avoid when:&lt;/strong&gt; updates are small and frequent relative to the value (rebuilding the world to change one field is silly), or writers need read-modify-write semantics (two concurrent build-and-swap writers will lose one update — that path needs &lt;code&gt;rcu()&lt;/code&gt; or a lock again).&lt;/p&gt;




&lt;h2&gt;
  
  
  Why I shipped ArcSwap this time
&lt;/h2&gt;

&lt;p&gt;Real case from a recent project: an in-memory search index, rebuilt whenever the upstream data changes (a version counter tells us when). The requirements, written out honestly:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Queried on every request.&lt;/strong&gt; The read path is the hot path; reads must be as close to free as possible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every query must see a coherent index.&lt;/strong&gt; Half-rebuilt is worse than stale.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rebuilds are slow&lt;/strong&gt; — normalizing thousands of rows takes real milliseconds. Queries cannot wait behind that.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Updates are rare and wholesale.&lt;/strong&gt; Nothing ever edits one entry in place.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Score the candidates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mutex&lt;/strong&gt; — serializes every query. Dead on requirement 1.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RwLock, mutating in place&lt;/strong&gt; — the write lock is held for the entire rebuild. Dead on 3, and every query still pays the lock-word ping-pong from the fine print above.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RwLock, build-then-swap&lt;/strong&gt; — build the new index off to the side, take the write lock only to swap the value. Closer! The write lock is now held for nanoseconds. But readers &lt;em&gt;still&lt;/em&gt; pay an atomic RMW per query, and at that point you've hand-implemented half of ArcSwap with the slow half left in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ArcSwap&lt;/strong&gt; — a query is a pointer load; the swap is instant; a slow query that started before the swap simply finishes on the old index while new queries use the new one. Every requirement, no residue.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Requirement 4 is what makes it clean. ArcSwap isn't "a better RwLock" — it's the right shape &lt;em&gt;because the data's lifecycle is replace-not-mutate&lt;/em&gt;. If my index needed in-place edits, this whole analysis flips.&lt;/p&gt;




&lt;h2&gt;
  
  
  The actually interesting part: who frees the old one?
&lt;/h2&gt;

&lt;p&gt;Here's the question that hooked me on this topic. After the swap, a slow query is still reading the &lt;em&gt;old&lt;/em&gt; index. When is it safe to free it?&lt;/p&gt;

&lt;p&gt;This is the &lt;strong&gt;memory reclamation problem&lt;/strong&gt;, and it's the real hard part of lock-free reading — not the swap, the &lt;em&gt;free&lt;/em&gt;. You can't free while an invisible reader might still hold a pointer, and by definition lock-free readers don't announce themselves.&lt;/p&gt;

&lt;p&gt;Every ecosystem has an answer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GC languages&lt;/strong&gt; — the collector solves it invisibly. This is why lock-free structures are "easy" in Java, and why we never learned any of this in Python/TS: &lt;strong&gt;the GC was quietly doing reclamation for us the whole time.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RCU&lt;/strong&gt; (Linux kernel) — wait until every CPU passes a quiescent state, then free.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hazard pointers&lt;/strong&gt; — readers publish "I'm holding this" before dereferencing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Epoch-based reclamation&lt;/strong&gt; (&lt;code&gt;crossbeam&lt;/code&gt;) — generation stamps; free when every thread has moved past the old epoch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Arc&lt;/code&gt;&lt;/strong&gt; — a reference count. The last reader to drop the snapshot frees it. Deterministic, no GC pauses, no epochs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last one is ArcSwap's answer, and it's worth sitting with: Rust solves the reclamation problem with the &lt;em&gt;same ownership system&lt;/em&gt; that made the data race a compile error. The slow query owns a share of the old index; when the last share drops, the memory goes. It's ownership all the way down.&lt;/p&gt;




&lt;h2&gt;
  
  
  Async footnote
&lt;/h2&gt;

&lt;p&gt;Two things worth pinning, because the folklore is wrong:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;std::sync::Mutex&lt;/code&gt; is fine — usually &lt;em&gt;preferred&lt;/em&gt; — in async code for short critical sections. Tokio's own docs say so. &lt;code&gt;tokio::sync::Mutex&lt;/code&gt; exists for one job: holding a lock &lt;strong&gt;across an &lt;code&gt;.await&lt;/code&gt;&lt;/strong&gt;. And wanting that is usually a design smell — restructure to lock-copy-drop, await, re-lock. (Caveat: that changes semantics — state can move between the two locks. If the whole span genuinely must be exclusive, &lt;em&gt;that's&lt;/em&gt; when the async mutex earns its place.)&lt;/li&gt;
&lt;li&gt;ArcSwap is especially pleasant in async: &lt;code&gt;load_full()&lt;/code&gt; gives you an owned &lt;code&gt;Arc&lt;/code&gt; that crosses &lt;code&gt;.await&lt;/code&gt; freely. No guard, no &lt;code&gt;Send&lt;/code&gt; drama — the snapshot is just a value.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The memo itself
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situation&lt;/th&gt;
&lt;th&gt;Reach for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Shared state, mixed read/write, short ops&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Mutex&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Long read sections, rare writes, &lt;em&gt;measured&lt;/em&gt; contention&lt;/td&gt;
&lt;td&gt;&lt;code&gt;RwLock&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Read-mostly + wholesale replacement + hot read path&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ArcSwap&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Counter, flag, version number&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;AtomicU64&lt;/code&gt; / &lt;code&gt;AtomicBool&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Must hold exclusivity across &lt;code&gt;.await&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;tokio::sync::Mutex&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;It's actually a pipeline, not shared state&lt;/td&gt;
&lt;td&gt;channels / ownership&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The ladder underneath the table: don't share; share frozen; &lt;code&gt;Mutex&lt;/code&gt;; and everything above that gets earned with a measurement, not a vibe.&lt;/p&gt;

&lt;p&gt;Future me: you probably want the Mutex.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>architecture</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>I Bolted a Rust Sidecar onto a Streamlit App, and the Reason Was Memory, Not Speed</title>
      <dc:creator>Chris</dc:creator>
      <pubDate>Tue, 04 Aug 2026 07:10:52 +0000</pubDate>
      <link>https://dev.to/criscmd/i-bolted-a-rust-sidecar-onto-a-streamlit-app-and-the-reason-was-memory-not-speed-d6</link>
      <guid>https://dev.to/criscmd/i-bolted-a-rust-sidecar-onto-a-streamlit-app-and-the-reason-was-memory-not-speed-d6</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;A Streamlit dashboard for retail foot traffic analysis needs typeahead search over about 1.2 million store locations in Japan. Streamlit's execution model makes per-keystroke interaction structurally awkward, and the search itself needs Japanese text normalization plus partial matching that a simple substring filter cannot express.&lt;/p&gt;

&lt;p&gt;The fix is a small Rust service running beside the Streamlit container, serving search from an in-memory index.&lt;/p&gt;

&lt;p&gt;The decisive reason for Rust was not raw speed. It was that a single Rust process can put a 210 MB index on the heap and let every CPU core read it concurrently. Node and Python cannot, and that turns into a multiple of the memory bill. Everything else in the design follows from that one fact.&lt;/p&gt;




&lt;h2&gt;
  
  
  The app and the data model
&lt;/h2&gt;

&lt;p&gt;The product is an internal analytics dashboard for retail location intelligence. A user picks store locations and gets a report about who visits them: catchment areas, travel time isochrones, visitor demographics, and comparisons over time. It is built in Streamlit, which lets a small team ship data-heavy tooling in Python without a frontend build pipeline.&lt;/p&gt;

&lt;p&gt;The data model has two levels, and they matter for everything downstream.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;chain&lt;/strong&gt; is a brand or operator. A &lt;strong&gt;POI&lt;/strong&gt; (point of interest) is one physical location belonging to that chain. So a convenience store brand is a chain, and each of its several thousand branches is a POI. There are roughly 11,000 chains and roughly 1.2 million POIs.&lt;/p&gt;

&lt;p&gt;The display label for a location joins the two:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;That separator is presentation only. It is not part of the data and, as it turns out, it must not be part of the search text either.&lt;/p&gt;

&lt;p&gt;The selection UI mirrors the hierarchy. Pick one or more chains, then pick locations within them, then render the report. That two step flow is where the trouble starts.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Streamlit's execution model does to interactivity
&lt;/h2&gt;

&lt;p&gt;Streamlit's core idea is that your script is the UI. On every interaction it reruns the script from the top, and whatever widget calls execute produce the current view. It is a genuinely elegant model for dashboards, and it removes an enormous amount of frontend work.&lt;/p&gt;

&lt;p&gt;It also means there is no cheap way to handle "the user typed one character."&lt;/p&gt;

&lt;p&gt;A rerun re-executes the whole script. Caching helps, and the usual toolkit is &lt;code&gt;@st.cache_data&lt;/code&gt;, &lt;code&gt;st.session_state&lt;/code&gt;, and fragments to limit what re-renders. But the mental model is still full script execution per interaction, and a per keystroke rerun over a report heavy page is not something you want to attempt.&lt;/p&gt;

&lt;p&gt;The escape hatch is a custom component, which runs as JavaScript in an iframe and talks to Python over a message bridge. That works well, with one catch: sending a value back to Python triggers a rerun. So a component that filters a list has two options. Either it round trips to Python on every keystroke, incurring a rerun each time, or it receives all its options up front and filters entirely client side.&lt;/p&gt;

&lt;p&gt;The second option is the only viable one, and it sets a hard ceiling: &lt;strong&gt;every candidate the user might search has to be in the browser before they type.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That is why the UI is two step. Sending 1.2 million options to the browser is not an option, so users must narrow by chain first. To keep even that tolerable, the app builds several layers of caching in front of the database: a shared Redis layer holding the chain list with a one day TTL, plus per chain caches of the location lists, so that a coordinator round trip to the Postgres cluster happens at most once per chain.&lt;/p&gt;

&lt;p&gt;All of that work is compensating for a constraint that has nothing to do with the data and everything to do with where the filtering happens.&lt;/p&gt;




&lt;h2&gt;
  
  
  Three ways the search itself is hard
&lt;/h2&gt;

&lt;p&gt;Even setting the delivery problem aside, the matching is not trivial.&lt;/p&gt;

&lt;h3&gt;
  
  
  Users do not think chain first
&lt;/h3&gt;

&lt;p&gt;The two step flow assumes you know the brand before you know the store. Real usage is often the reverse. Someone wants "the locations in this district," across brands. The hierarchy in the data model got baked into the interaction model, and it should not have been.&lt;/p&gt;

&lt;h3&gt;
  
  
  Japanese text has many spellings for one string
&lt;/h3&gt;

&lt;p&gt;A single name can appear in katakana or hiragana, full width or half width, with historical or modern kanji forms, and with Unicode variation selectors attached. These are entirely different byte sequences that a human reads as identical.&lt;/p&gt;

&lt;p&gt;Unicode NFKC normalization handles width and compatibility forms. It does not handle the kanji variants, which are common in Japanese business names. So the normalization pipeline runs NFKC first, then a Japanese transliteration library for script folding, historical character forms, iteration marks, and variation selector removal, then lowercasing and punctuation stripping.&lt;/p&gt;

&lt;p&gt;The important property is that normalization is &lt;strong&gt;additive, not destructive&lt;/strong&gt;. The original text is kept for display, and the normalized text is stored as a separate derived column. Normalization is lossy and one way, so there is no reverse function. You match against the derived column and you display the original.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Corpus&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// shown to the user, never modified&lt;/span&gt;
    &lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// normalized, matched against, never shown&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the same relationship a database has with its indexes. Use the index to find the row, then read the row.&lt;/p&gt;

&lt;h3&gt;
  
  
  Substring matching answers the wrong question
&lt;/h3&gt;

&lt;p&gt;Here is the case that rules out a simple filter. Because a location name almost never repeats its own chain name (measured at 99.6% of records), the searchable text has to be the chain and the location concatenated, so that a query spanning both still matches.&lt;/p&gt;

&lt;p&gt;But then a user types the brand plus a district, and the stored record has extra words in the middle. The query is genuinely not a substring of the record. &lt;code&gt;haystack.contains(query)&lt;/code&gt; returns false, and no amount of optimization fixes that, because "does this contain that" is the wrong question.&lt;/p&gt;

&lt;p&gt;The right question is "how much of this query does this record cover," which requires breaking the query into pieces and scoring partial matches.&lt;/p&gt;




&lt;h2&gt;
  
  
  The constraint that picked the language
&lt;/h2&gt;

&lt;p&gt;The index has to live in memory. Roughly 210 MB for the text plus the postings, based on the layout described below, loaded once at startup and then serving every keystroke without touching Redis or the database.&lt;/p&gt;

&lt;p&gt;The question that decided everything: what happens when you want more than one CPU core?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Node runs JavaScript on one thread, so one core.&lt;/strong&gt; Four cores means four processes, and processes do not share heaps. That is four copies of the index. &lt;code&gt;worker_threads&lt;/code&gt; does not rescue this, because each worker gets its own V8 isolate with its own heap. The only shareable thing is a &lt;code&gt;SharedArrayBuffer&lt;/code&gt;, which holds raw bytes and no objects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Python threads do share a heap, but the GIL means only one runs bytecode at a time.&lt;/strong&gt; Sharing without parallelism. The standard fix is &lt;code&gt;multiprocessing&lt;/code&gt;, which lands right back at N isolated heaps. Free threaded Python changes this and is worth watching, but it was not an option here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rust runs N OS threads inside one process, and threads share an address space.&lt;/strong&gt; One index, every core reading it concurrently, no locks required because nothing mutates after load.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Runtime&lt;/th&gt;
&lt;th&gt;Shares one heap&lt;/th&gt;
&lt;th&gt;Uses N cores&lt;/th&gt;
&lt;th&gt;Index copies on 4 cores&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Node, cluster&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Node, worker_threads&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Python, threads&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;1, serialized&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Python, multiprocessing&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rust, tokio&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;At 210 MB per copy that is the difference between provisioning around 1 GB and around 2 GB per container, plus four independent cold start loads on every deploy instead of one.&lt;/p&gt;

&lt;p&gt;That is the argument. Not that Rust is fast. That one process can hold one index and let every core read it.&lt;/p&gt;




&lt;h2&gt;
  
  
  How the service is built
&lt;/h2&gt;

&lt;p&gt;Three layers and one rule.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;controller   HTTP only. Parse query params, call the service, serialize.
service      Pure logic. Imports no web framework and no storage client.
repository   I/O. Fetches bytes, returns plain domain types.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rule is that &lt;code&gt;service&lt;/code&gt; never imports the web framework or the storage driver. That single constraint is what keeps storage swappable, and it is worth more than any amount of interface ceremony.&lt;/p&gt;

&lt;p&gt;Data flows one direction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Postgres (source of truth)
   |  batch job builds a versioned snapshot
   v
Redis (distribution, not a request path dependency)
   |  loaded once at boot, and on refresh
   v
Process memory (answers every request)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Redis is a delivery mechanism, not a cache in front of queries. Once a process has loaded, Redis can go down and search keeps working. Snapshots publish blue green: write every chunk of the new version, verify, then flip a single pointer key. A reader sees the old version entirely or the new one entirely, never a mix.&lt;/p&gt;

&lt;p&gt;The in memory layout is columnar rather than an array of structs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// not this&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Record&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// this&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Corpus&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// deduplicated, referenced by index&lt;/span&gt;
    &lt;span class="n"&gt;group_idx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;u32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;// which group each record belongs to&lt;/span&gt;
    &lt;span class="n"&gt;names&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&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;keys&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;      &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&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;search&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// normalized&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;"Record 7" does not exist as an object. It is the number 7, used to index every array. Two consequences.&lt;/p&gt;

&lt;p&gt;Deduplicating the group name costs 4 bytes per record instead of a full string. With 11,000 distinct groups across 1.2 million records, that is roughly 4.8 MB instead of roughly 65 MB for identical information.&lt;/p&gt;

&lt;p&gt;Search runs entirely on integers. A candidate set of 300 is 2.4 KB of numbers, not 300 objects. Strings are touched only for the handful of results actually returned, and the response payload is built on demand rather than stored per record.&lt;/p&gt;




&lt;h2&gt;
  
  
  The index
&lt;/h2&gt;

&lt;p&gt;Japanese has no spaces, so splitting on whitespace is not available. Morphological analysis works but needs a dictionary measured in tens of megabytes. The alternative is n-grams, and bigrams are the practical choice: one character is not selective enough, three means short queries match nothing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"alphabeta"  -&amp;gt;  al  lp  ph  ha  ab  be  et  ta
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Invert it, so each bigram maps to the records containing it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="s2"&gt;"ph"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;91&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2043&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;"ha"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;512&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A query then looks up its own bigrams, tallies how many each record matched, weights rare bigrams above common ones with standard IDF (&lt;code&gt;ln(N / df)&lt;/code&gt;), keeps the top few hundred, and reranks those with more expensive checks. Partial coverage gets partial credit, which is exactly what substring matching could not express.&lt;/p&gt;

&lt;p&gt;The layout matters more than the algorithm. A hash map from bigram to a vector of IDs means one heap allocation per bigram, plus hash overhead, plus per vector capacity slack, which measures at roughly 4 to 5 times the memory of the alternative.&lt;/p&gt;

&lt;p&gt;The alternative is CSR, borrowed from sparse matrix code. Concatenate every posting list into one flat array and keep an array of offsets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;offsets:  [ 0,        3,          7,     8,      12 ]
postings: [ 7,91,2043 | 5,7,88,90 | 3 | 12,44,55,67 ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A posting list becomes a slice, &lt;code&gt;&amp;amp;postings[offsets[b]..offsets[b+1]]&lt;/code&gt;. Two allocations for the whole index instead of hundreds of thousands, and every list is contiguous so hardware prefetching works. Build it by counting occurrences, prefix summing the counts into offsets, allocating exactly once, then filling. That is counting sort, and it is the entire trick.&lt;/p&gt;




&lt;h2&gt;
  
  
  Concurrency, measured
&lt;/h2&gt;

&lt;p&gt;Tokio is an M:N scheduler. Many cheap tasks are multiplexed onto a small number of OS worker threads, one per core by default. Each &lt;code&gt;.await&lt;/code&gt; is a yield point, and between yield points a task owns its worker outright.&lt;/p&gt;

&lt;p&gt;Which means &lt;strong&gt;async provides no parallelism for CPU bound work.&lt;/strong&gt; Parallelism comes from having N worker threads. A handler that never awaits is a synchronous function wearing an async hat.&lt;/p&gt;

&lt;p&gt;Pinning a runtime to a fixed worker count and giving it 8 tasks, each needing 200 ms of pure CPU:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;worker_threads( 1) -&amp;gt; 1600 ms
worker_threads( 2) -&amp;gt;  800 ms
worker_threads( 4) -&amp;gt;  400 ms
worker_threads( 8) -&amp;gt;  200 ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Perfectly linear. Note that one worker is essentially the Node model, and no amount of &lt;code&gt;async&lt;/code&gt; beats 1600 ms with one pair of hands.&lt;/p&gt;

&lt;p&gt;The matching trap is blocking a worker. The same 8 requests, three handler styles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CPU bound directly on the worker    1645 ms
offloaded via spawn_blocking         412 ms
real awaited IO                      413 ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rule that falls out: if it does not &lt;code&gt;.await&lt;/code&gt; and takes more than about a millisecond, it does not belong on a worker thread. The index build takes about 14 seconds, so it goes to the blocking pool. Blocking a worker for 14 seconds on a 4 core box costs 25% of capacity, health checks included.&lt;/p&gt;

&lt;p&gt;The shared state itself is an atomically reference counted pointer, which is what makes the one copy design work. Handing it to a request costs a pointer and a counter bump rather than the data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;clone the shared pointer      11.46 ns
deep copy the same payload    51.7 ms   (1M strings)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Roughly four and a half million times apart. That gap is the entire argument for shared ownership, and it is why per request state is 8 bytes rather than 210 MB.&lt;/p&gt;




&lt;h2&gt;
  
  
  Numbers
&lt;/h2&gt;

&lt;p&gt;Dev machine, 12 cores, synthetic workloads unless marked otherwise.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;POIs&lt;/td&gt;
&lt;td&gt;~1.2 million&lt;/td&gt;
&lt;td&gt;actual&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Chains&lt;/td&gt;
&lt;td&gt;~11,000&lt;/td&gt;
&lt;td&gt;actual&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Location names not containing their chain name&lt;/td&gt;
&lt;td&gt;99.6%&lt;/td&gt;
&lt;td&gt;actual&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Snapshot on the wire&lt;/td&gt;
&lt;td&gt;~49 MB gzipped&lt;/td&gt;
&lt;td&gt;actual&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Steady state index memory&lt;/td&gt;
&lt;td&gt;~210 MB&lt;/td&gt;
&lt;td&gt;estimated from layout&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Same index under a 4 process runtime&lt;/td&gt;
&lt;td&gt;~840 MB&lt;/td&gt;
&lt;td&gt;estimated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Text normalization&lt;/td&gt;
&lt;td&gt;~12 microseconds per record&lt;/td&gt;
&lt;td&gt;measured&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Full index build&lt;/td&gt;
&lt;td&gt;~14 seconds, single threaded&lt;/td&gt;
&lt;td&gt;measured&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shared pointer clone&lt;/td&gt;
&lt;td&gt;11.46 ns&lt;/td&gt;
&lt;td&gt;measured&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deep copy, 1M strings&lt;/td&gt;
&lt;td&gt;51.7 ms&lt;/td&gt;
&lt;td&gt;measured&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Worker scaling, 8 x 200 ms tasks&lt;/td&gt;
&lt;td&gt;1600 / 800 / 400 / 200 ms&lt;/td&gt;
&lt;td&gt;measured&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Blocking vs offloaded&lt;/td&gt;
&lt;td&gt;1645 ms vs 412 ms&lt;/td&gt;
&lt;td&gt;measured&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Lessons worth keeping
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Build the slow version first.&lt;/strong&gt; The plan deliberately runs a naive linear scan over plain vectors before the inverted index and the compact layout. It provides a correctness baseline and a number to beat. Skipping to the clever version leaves you unable to tell whether it is right or whether it helped.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Normalization belongs in exactly one place.&lt;/strong&gt; Precomputing normalized text in the Python batch job would save the 14 second build, at the cost of two implementations of the same function in two languages that must agree byte for byte forever. When they drift, queries normalize one way and the index another, and search silently returns nothing. No error, no crash, just empty results. Paying 14 seconds on a background thread is clearly the better trade.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Health checks must gate on readiness.&lt;/strong&gt; A fresh instance needs most of a minute to fetch, decompress, parse, and normalize before it can answer anything. If the health endpoint returns 200 during that window, the load balancer routes real users to an empty index and they get zero results with no error. That presents as a search bug and is actually a deployment bug.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The UI shape was a data model leak.&lt;/strong&gt; Chain first selection existed because the delivery mechanism could not handle anything else, not because users think that way. Moving search server side did not just make it faster, it removed a step that should never have been there.&lt;/p&gt;




&lt;p&gt;The interesting conclusion is that the decisive factor was not throughput. It was that one process can hold one index and let every core read it. Once that became the binding constraint, most of the rest of the design followed on its own.&lt;/p&gt;

</description>
      <category>python</category>
      <category>rust</category>
      <category>architecture</category>
      <category>performance</category>
    </item>
    <item>
      <title>Azura: local-first personal assistant (feedback wanted)</title>
      <dc:creator>Chris</dc:creator>
      <pubDate>Fri, 14 Nov 2025 12:04:16 +0000</pubDate>
      <link>https://dev.to/criscmd/azura-local-first-personal-assistant-feedback-wanted-56dp</link>
      <guid>https://dev.to/criscmd/azura-local-first-personal-assistant-feedback-wanted-56dp</guid>
      <description>&lt;p&gt;Hey devs 👋&lt;/p&gt;

&lt;p&gt;I'm working solo on a project called &lt;strong&gt;Azura&lt;/strong&gt; and I’d love blunt technical + product feedback before I go too deep.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Local-first personal AI assistant&lt;/strong&gt; (Windows / macOS / Linux)&lt;/li&gt;
&lt;li&gt;Runs &lt;strong&gt;7B-class models locally&lt;/strong&gt; on your own machine&lt;/li&gt;
&lt;li&gt;Optional &lt;strong&gt;cloud inference&lt;/strong&gt; with &lt;strong&gt;70B+ models&lt;/strong&gt; (potentially up to ~120B if I can get a GPU cluster cheap enough)&lt;/li&gt;
&lt;li&gt;Cloud only sees &lt;strong&gt;temporary context&lt;/strong&gt; for a given query, then it’s gone&lt;/li&gt;
&lt;li&gt;Goal: let AI work with &lt;strong&gt;highly personalized data&lt;/strong&gt; while keeping your data &lt;strong&gt;on-device&lt;/strong&gt; and making AI compute more &lt;strong&gt;sustainable&lt;/strong&gt; by offloading work to the user’s hardware&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think of it as &lt;strong&gt;Signal, but for AI&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;private by default
&lt;/li&gt;
&lt;li&gt;transparent about what leaves your device
&lt;/li&gt;
&lt;li&gt;and actually usable as a daily “second brain”.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Problem I’m trying to solve
&lt;/h2&gt;

&lt;p&gt;Most AI tools today:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ship all your prompts and files to a remote server
&lt;/li&gt;
&lt;li&gt;keep embeddings / logs indefinitely
&lt;/li&gt;
&lt;li&gt;centralize all compute in big datacenters
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s bad if you want to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;use AI on &lt;strong&gt;sensitive&lt;/strong&gt; data (legal docs, internal company info, personal notes)
&lt;/li&gt;
&lt;li&gt;build a &lt;strong&gt;long-term memory&lt;/strong&gt; of your life and work
&lt;/li&gt;
&lt;li&gt;not rely 100% on someone else’s infrastructure for every tiny inference
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On top of that, current AI usage is very &lt;strong&gt;cloud-heavy&lt;/strong&gt;. Every small task hits a GPU in a datacenter, even when a smaller local model would be good enough.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Azura’s goal:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Let AI work deeply with your personal data while keeping that data on your device by default, and offload as much work as possible to the user’s hardware to make AI more sustainable.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Core concept
&lt;/h2&gt;

&lt;p&gt;Azura has two main execution paths:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Local path (default)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Desktop app (Win / macOS / Linux)&lt;/li&gt;
&lt;li&gt;Local backend (Rust / llama.cpp / vector DB)&lt;/li&gt;
&lt;li&gt;Uses a &lt;strong&gt;7B model&lt;/strong&gt; running on your machine&lt;/li&gt;
&lt;li&gt;Good for:

&lt;ul&gt;
&lt;li&gt;day-to-day chat&lt;/li&gt;
&lt;li&gt;note-taking / journaling&lt;/li&gt;
&lt;li&gt;searching your own docs/files&lt;/li&gt;
&lt;li&gt;“second brain” queries that don’t need super high IQ&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Cloud inference path (optional)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When a query is too complex / heavy for the local 7B:

&lt;ul&gt;
&lt;li&gt;Azura builds a &lt;strong&gt;minimal context&lt;/strong&gt; (chunks of docs, metadata, etc.)&lt;/li&gt;
&lt;li&gt;Sends that context + query to a &lt;strong&gt;70B+ model&lt;/strong&gt; in the cloud (ideally up to ~120B later)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data handling:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Files / context are used &lt;strong&gt;only temporarily&lt;/strong&gt; for that request&lt;/li&gt;
&lt;li&gt;Held in memory or short-lived storage just long enough to run the inference&lt;/li&gt;
&lt;li&gt;Then discarded – no long-term cloud memory of your life&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Context engine (high-level idea)
&lt;/h2&gt;

&lt;p&gt;On top of “just call an LLM”, I’m experimenting with a structured &lt;strong&gt;context engine&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ingests:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;files, PDFs, notes, images&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Stores:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;embeddings + metadata (timestamps, tags, entities, locations)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Builds:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;a lightweight relationship graph (people, projects, events, topics)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Answers:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;“What did I do for project A in March?”&lt;/li&gt;
&lt;li&gt;“Show me everything related to ‘Company A’ and ‘pricing’.”&lt;/li&gt;
&lt;li&gt;“What did I wear at the gala in Tokyo?” (from images ingested)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Standard RAG is part of this, but the goal is an &lt;strong&gt;ongoing personal knowledge base&lt;/strong&gt; that the LLM can query, not just a vector search API.&lt;/p&gt;

&lt;p&gt;All of this long-term data lives &lt;strong&gt;on-device&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Sustainability angle (important to me)
&lt;/h2&gt;

&lt;p&gt;Part of the vision is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Don’t hit a giant GPU cluster for every small query.
&lt;/li&gt;
&lt;li&gt;Let the &lt;strong&gt;user’s device&lt;/strong&gt; handle as much as possible (7B locally).
&lt;/li&gt;
&lt;li&gt;Use big cloud models &lt;strong&gt;only when they actually add value&lt;/strong&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Over time, I’d like Azura to feel like a &lt;strong&gt;hybrid compute layer&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Local where possible,
&lt;/li&gt;
&lt;li&gt;Cloud only for heavy stuff,
&lt;/li&gt;
&lt;li&gt;Always explicit and transparent.
&lt;/li&gt;
&lt;li&gt;And most of all, &lt;strong&gt;PRIVATE&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What I’d love feedback on
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Architecture sanity&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does the “local-first + direct cloud inference” setup look sane?&lt;/li&gt;
&lt;li&gt;Any better patterns you’ve used for mixing on-device models with cloud models?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Security + privacy model&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For ephemeral cloud context: what would &lt;em&gt;you&lt;/em&gt; need to see (docs / guarantees / telemetry) to trust this?&lt;/li&gt;
&lt;li&gt;Anything obvious I’m missing around temporary file handling?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Sustainability / cost&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;As engineers: do you care about offloading compute to end-user devices vs fully cloud?&lt;/li&gt;
&lt;li&gt;Any horror stories optimizing 7B vs 70B usage that I should know about?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Would you actually use this?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you’re into self-hosting / local LLMs:

&lt;ul&gt;
&lt;li&gt;What’s missing for this to replace “Ollama + notebook + random SaaS” for you?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;Right now I’m:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Testing 7B models on typical consumer hardware&lt;/li&gt;
&lt;li&gt;Designing the first version of the context engine and schema&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If this resonates, I’d really appreciate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Architecture critiques&lt;/li&gt;
&lt;li&gt;“This will break because X” comments&lt;/li&gt;
&lt;li&gt;Ideas for must-have features for a real, daily-use personal AI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thanks for reading 🙏&lt;br&gt;&lt;br&gt;
Happy to dive into any part in more detail if you’re curious.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>community</category>
      <category>programming</category>
      <category>devjournal</category>
    </item>
    <item>
      <title>You Only Really Need to Learn 2 Languages to Succeed</title>
      <dc:creator>Chris</dc:creator>
      <pubDate>Fri, 27 Jun 2025 16:12:59 +0000</pubDate>
      <link>https://dev.to/criscmd/you-only-really-need-to-learn-2-languages-to-succeed-4a7n</link>
      <guid>https://dev.to/criscmd/you-only-really-need-to-learn-2-languages-to-succeed-4a7n</guid>
      <description>&lt;p&gt;Most developers are told they need to learn dozens of languages to stay competitive. One for scripting, one for frontend, one for backend, one for systems, and another five because "they’re trending."&lt;/p&gt;

&lt;p&gt;But in reality?&lt;/p&gt;

&lt;p&gt;You only need two.&lt;br&gt;
Not any two, but one from each of these two fundamental categories:&lt;/p&gt;




&lt;h3&gt;
  
  
  🔹 1. A &lt;strong&gt;High Level Language&lt;/strong&gt; (With Garbage Collection)
&lt;/h3&gt;

&lt;p&gt;These are your productive, developer-friendly languages. They manage memory for you, offer rich ecosystems, and help you ship fast.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;TypeScript&lt;/li&gt;
&lt;li&gt;Go&lt;/li&gt;
&lt;li&gt;Python&lt;/li&gt;
&lt;li&gt;Java&lt;/li&gt;
&lt;li&gt;C#&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use these when building:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Web apps&lt;/li&gt;
&lt;li&gt;APIs&lt;/li&gt;
&lt;li&gt;Scripts&lt;/li&gt;
&lt;li&gt;Automation&lt;/li&gt;
&lt;li&gt;Data pipelines&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  🔸 2. A &lt;strong&gt;Low Level Language&lt;/strong&gt; (Without Garbage Collection)
&lt;/h3&gt;

&lt;p&gt;These languages give you precise control over memory and performance. They are used when predictability, speed, or system-level access matters.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Rust&lt;/li&gt;
&lt;li&gt;C++&lt;/li&gt;
&lt;li&gt;C&lt;/li&gt;
&lt;li&gt;Zig&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Game engines&lt;/li&gt;
&lt;li&gt;OS development&lt;/li&gt;
&lt;li&gt;Embedded systems&lt;/li&gt;
&lt;li&gt;Performance-critical code&lt;/li&gt;
&lt;li&gt;Security and cryptography work&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🎯 Why Just These Two?
&lt;/h2&gt;

&lt;p&gt;Because most real-world software problems fall into one of these buckets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;High level orchestration like building logic, connecting services, and managing flows&lt;/li&gt;
&lt;li&gt;Low level performance like speed, memory management, and hardware interaction&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you can write both, you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build a full stack system on your own&lt;/li&gt;
&lt;li&gt;Write efficient backend services and embedded agents&lt;/li&gt;
&lt;li&gt;Move between startup MVPs and military-grade firmware&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🔧 Smart Pairings
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;GC Language&lt;/th&gt;
&lt;th&gt;Non-GC Language&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Go&lt;/td&gt;
&lt;td&gt;C++&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TypeScript&lt;/td&gt;
&lt;td&gt;Rust&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;td&gt;C&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Java&lt;/td&gt;
&lt;td&gt;Rust&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You do not need to master all of them. Just pick one solid language from each group and go deep.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧠 Depth Over Breadth
&lt;/h2&gt;

&lt;p&gt;Mastering a garbage-collected language makes you productive&lt;br&gt;
Mastering a non-GC language makes you powerful&lt;/p&gt;

&lt;p&gt;You do not need to chase trends. You need to understand tradeoffs, how systems work, how memory is managed, and how to scale clean code.&lt;/p&gt;

&lt;p&gt;Everything else is just syntax.&lt;/p&gt;




&lt;h2&gt;
  
  
  ✅ TLDR
&lt;/h2&gt;

&lt;p&gt;You only really need to learn two languages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One that frees you to move fast&lt;/li&gt;
&lt;li&gt;One that forces you to think clearly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Learn them deeply&lt;br&gt;
Build boldly&lt;br&gt;
Forget the noise&lt;/p&gt;

</description>
      <category>programming</category>
      <category>softwareengineering</category>
      <category>beginners</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Stop Overengineering in the Name of Clean Architecture</title>
      <dc:creator>Chris</dc:creator>
      <pubDate>Sun, 11 May 2025 03:15:29 +0000</pubDate>
      <link>https://dev.to/criscmd/stop-overengineering-in-the-name-of-clean-architecture-b8h</link>
      <guid>https://dev.to/criscmd/stop-overengineering-in-the-name-of-clean-architecture-b8h</guid>
      <description>&lt;p&gt;Clean Architecture is a great concept. It’s meant to help you write maintainable, modular, and scalable software.&lt;/p&gt;

&lt;p&gt;But too many developers treat it like a religion. They follow it blindly, stuffing projects with unnecessary layers, abstractions, and patterns. All in the name of “clean code.”&lt;/p&gt;

&lt;p&gt;Let’s be honest, Clean Architecture is often &lt;strong&gt;overused&lt;/strong&gt;, not because the ideas are bad, but because people &lt;strong&gt;overengineer&lt;/strong&gt; the implementation.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Quick Joke (That Some Devs Write Unironically)
&lt;/h2&gt;

&lt;p&gt;Here’s an example of multiplying two numbers implemented with an absurd number of patterns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// overengineered-multiplier.ts&lt;/span&gt;

&lt;span class="c1"&gt;// Interface&lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;IMultiplier&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;multiply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Singleton&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MultiplierService&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;IMultiplier&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MultiplierService&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="nf"&gt;getInstance&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;MultiplierService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;MultiplierService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;MultiplierService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MultiplierService&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="nx"&gt;MultiplierService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;multiply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Adapter&lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;IMultiplierInput&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;InputAdapter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;rawInput&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="nf"&gt;adapt&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;IMultiplierInput&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="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rawInput&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rawInput&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="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Decorator&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LoggingMultiplierDecorator&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;IMultiplier&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;inner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IMultiplier&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="nf"&gt;multiply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Logging: Multiplying &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; * &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;multiply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Logging: Result is &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Proxy&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MultiplierProxy&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;IMultiplier&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IMultiplier&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="nf"&gt;multiply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Invalid input types&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;multiply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Abstract Factory&lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;IMultiplierFactory&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;IMultiplier&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RealMultiplierFactory&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;IMultiplierFactory&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;IMultiplier&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;singleton&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;MultiplierService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getInstance&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;withLogging&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;LoggingMultiplierDecorator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;singleton&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;withProxy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MultiplierProxy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;withLogging&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;withProxy&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Usage&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rawInput&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;number&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="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;adapted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;InputAdapter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawInput&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;adapt&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;factory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RealMultiplierFactory&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;multiplier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;multiplier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;multiply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;adapted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;adapted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Final Result: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All of this just to calculate &lt;code&gt;6 * 9&lt;/code&gt;. This is what happens when design patterns become cosplay.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Real Problem: Over-Abstraction
&lt;/h2&gt;

&lt;p&gt;Clean Architecture promotes decoupling. That’s good. But too many devs interpret that as "abstract everything."&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 typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;IUserRepository&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;findById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;UserDTO&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserRepositoryImpl&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;IUserRepository&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PrismaClient&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="nf"&gt;findById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;UserDTO&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findUnique&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&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;Then you add a use case on top:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GetUserByIdUseCase&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IUserRepository&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="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;UserDTO&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&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;All of this for one database call. This isn’t clean it’s waste.&lt;/p&gt;




&lt;h2&gt;
  
  
  Common Overengineering Patterns
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Interfaces and Implementations for Everything
&lt;/h3&gt;

&lt;p&gt;Blindly creating an interface and a class for every service adds friction without real value.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use it when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You actually have multiple implementations&lt;/li&gt;
&lt;li&gt;You’re building a plugin system or SDK&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Don’t use it when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You only have one implementation&lt;/li&gt;
&lt;li&gt;You’re doing it "just in case"&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  2. Use Case Classes for Simple Logic
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreatePostUseCase&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CreatePostDTO&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;PostDTO&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;postRepo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&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 is a one-line call. Wrapping it in a class adds nothing. Use a service or method directly unless business logic really requires separation.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. DTO Explosion
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;CreatePostDTO&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;PostResponseDTO&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the structure is the same across layers, reuse the object. You don’t need a new DTO for every transition unless there’s a reason.&lt;/p&gt;




&lt;h3&gt;
  
  
  4. Domain Models with No Behavior
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your "domain entity" is just a data wrapper, it’s not a domain model. Add behavior or don’t abstract.&lt;/p&gt;




&lt;h3&gt;
  
  
  5. Dependency Injection Overkill
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Module&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;providers&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="na"&gt;provide&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IUserService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;useClass&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UserServiceImpl&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;provide&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IUserRepository&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;useClass&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UserRepositoryImpl&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;DI containers are great. But if you’re not benefiting from polymorphism or dynamic injection, just instantiate the class.&lt;/p&gt;




&lt;h2&gt;
  
  
  What You Should Do Instead
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Start simple.&lt;/strong&gt; Build complexity only when needed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Abstract with intent.&lt;/strong&gt; Not everything needs to be swappable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Refactor later.&lt;/strong&gt; Let the structure grow organically as the app gets more complex.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optimize for clarity.&lt;/strong&gt; Not for pleasing architecture diagrams.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Clean Code Is Not More Code
&lt;/h2&gt;

&lt;p&gt;Good code is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Easy to read&lt;/li&gt;
&lt;li&gt;Easy to test&lt;/li&gt;
&lt;li&gt;Easy to change&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s not defined by how many layers, decorators, or design patterns it uses.&lt;/p&gt;




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

&lt;p&gt;Clean Architecture should serve your code, not the other way around.&lt;/p&gt;

&lt;p&gt;It’s a tool, not a rulebook. Use it to solve complexity not to create it.&lt;/p&gt;




&lt;h2&gt;
  
  
  TLDR
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Avoid This&lt;/th&gt;
&lt;th&gt;Do This Instead&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Interface and Impl for everything&lt;/td&gt;
&lt;td&gt;Use one class until you actually need two&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Use cases for CRUD logic&lt;/td&gt;
&lt;td&gt;Use simple services or methods&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Separate DTOs for same shapes&lt;/td&gt;
&lt;td&gt;Reuse types where possible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DI for everything&lt;/td&gt;
&lt;td&gt;Instantiate directly when practical&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Layers for layers’ sake&lt;/td&gt;
&lt;td&gt;Let structure grow with real needs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Build software, not monuments.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Of course, if your goal is job security, then by all means abstract everything. Write five layers per feature, name nothing clearly, and inject interfaces into factories into services into use cases. Once the codebase hits 100,000 lines, no one will know what’s going on but you. Congratulations, you’re now unfireable LOL.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>programming</category>
      <category>discuss</category>
      <category>cleancode</category>
    </item>
    <item>
      <title>TypeScript + Rust: All You Need in 2025</title>
      <dc:creator>Chris</dc:creator>
      <pubDate>Sun, 11 May 2025 02:59:58 +0000</pubDate>
      <link>https://dev.to/criscmd/typescript-rust-all-you-need-in-2025-3dic</link>
      <guid>https://dev.to/criscmd/typescript-rust-all-you-need-in-2025-3dic</guid>
      <description>&lt;p&gt;Let’s be real. The software landscape is bloated. Between AI hyped frameworks and the constant cycle of JS libraries trying to reinvent the wheel, it’s easy to get lost chasing tech for the sake of it.&lt;/p&gt;

&lt;p&gt;But if you're trying to actually build something valuable in 2025, not just keep up with Hacker News, you really only need two languages:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TypeScript&lt;/strong&gt; and &lt;strong&gt;Rust&lt;/strong&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Why TypeScript?
&lt;/h3&gt;

&lt;p&gt;Because 90 percent of products today touch the web. And when it comes to frontend, API clients, or even backend services, nothing is more practical than TypeScript.&lt;/p&gt;

&lt;p&gt;You get type safety without the Java boilerplate&lt;br&gt;
You get first class tooling with VSCode, ESLint, Bun, tsup&lt;br&gt;
You can build full stack apps across Next.js, Node, Cloudflare Workers, Firebase&lt;br&gt;
You move fast without breaking everything&lt;/p&gt;

&lt;p&gt;It’s the language of the web. Period&lt;/p&gt;




&lt;h3&gt;
  
  
  Why Rust?
&lt;/h3&gt;

&lt;p&gt;Because when performance, memory safety, and scale matter, Rust is unmatched&lt;/p&gt;

&lt;p&gt;C level performance with zero segfaults&lt;br&gt;
Compiles to WebAssembly or runs as a blazing fast backend&lt;br&gt;
Handles AI pipelines, edge compute, custom databases, file systems&lt;br&gt;
Gives you memory control and fearless concurrency for real workloads&lt;/p&gt;

&lt;p&gt;You are not wasting CPU cycles on garbage collection or runtime bugs&lt;/p&gt;




&lt;h3&gt;
  
  
  What About the Others?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Python&lt;/strong&gt;&lt;br&gt;
Nice syntax until the codebase grows. The type system is glued on, not designed in. Static analysis is a mess and errors sneak in constantly&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Go&lt;/strong&gt;&lt;br&gt;
Go is great for building simple tools quickly, but it can become a liability at scale. Discord famously dropped Go from their caching system after the garbage collector became a bottleneck. While that specific issue was later addressed, GC performance can still be unpredictable under heavy load. If you need fine-grained control or predictable latency, Go might not cut it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Java&lt;/strong&gt;&lt;br&gt;
Enterprise hell. Verbose classes, XML config files, Spring Boot complexity. Java is built for a different era. If you are building modern software today, why are you still using it&lt;/p&gt;




&lt;h3&gt;
  
  
  Real World Example
&lt;/h3&gt;

&lt;p&gt;In my current projects&lt;/p&gt;

&lt;p&gt;Frontend and admin dashboards: TypeScript with React, Chakra, Next.js&lt;br&gt;
Backend logic, APIs, authentication, realtime: TypeScript with NestJS, Firebase, Cloud Run&lt;br&gt;
Data processing, local AI, indexing: Rust with REST API, image parsing, zip and file system crawling&lt;br&gt;
Vector search and storage orchestration: Rust with Qdrant and PostgreSQL&lt;/p&gt;

&lt;p&gt;One stack&lt;br&gt;
End to end control&lt;br&gt;
No bloat&lt;br&gt;
No regrets&lt;/p&gt;




&lt;h3&gt;
  
  
  Final Thoughts
&lt;/h3&gt;

&lt;p&gt;If you master TypeScript and Rust, you are not a JS dev&lt;br&gt;
You are not a systems dev&lt;br&gt;
You are a &lt;strong&gt;full spectrum engineer&lt;/strong&gt; who can build anything from the interface to the infrastructure&lt;/p&gt;

&lt;p&gt;In a world filled with AI noise, cloud saturation, and latency pressure&lt;/p&gt;

&lt;p&gt;That’s all you need&lt;/p&gt;




&lt;p&gt;What are you building with in 2025&lt;br&gt;
Let’s talk in the comments&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>rust</category>
      <category>typescript</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Don’t Use SQL If Your Client Doesn’t Know What They Want</title>
      <dc:creator>Chris</dc:creator>
      <pubDate>Sun, 11 May 2025 02:46:56 +0000</pubDate>
      <link>https://dev.to/criscmd/dont-use-sql-if-your-client-doesnt-know-what-they-want-2hhm</link>
      <guid>https://dev.to/criscmd/dont-use-sql-if-your-client-doesnt-know-what-they-want-2hhm</guid>
      <description>&lt;h2&gt;
  
  
  Firestore saved me from schema hell
&lt;/h2&gt;

&lt;p&gt;If you’re building for a client who keeps shifting the goalpost, here’s some honest advice:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Avoid SQL until the spec is stable.&lt;/strong&gt;&lt;br&gt;
You’ll save yourself from endless migrations, broken data, and wasted hours.&lt;/p&gt;




&lt;h2&gt;
  
  
  💥 Why SQL is a bad idea in chaotic projects
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Every schema change = manual migration&lt;/li&gt;
&lt;li&gt;Every migration = rollback risk&lt;/li&gt;
&lt;li&gt;Every miscommunication = broken relations or corrupted data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s like building a skyscraper with a blueprint drawn in crayon.&lt;/p&gt;




&lt;h2&gt;
  
  
  ✅ Why Firestore (NoSQL) saved me
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No migrations&lt;/strong&gt;&lt;br&gt;
Just start writing data. Add new fields whenever. No tables to alter. No DB admin.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Nested data, no problem&lt;/strong&gt;&lt;br&gt;
Need a user profile with 5 settings and a subcollection of comments? Firestore handles that like it’s native.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Speed over structure&lt;/strong&gt;&lt;br&gt;
Perfect for MVPs. When the client changes their mind (again), you don’t have to rewrite half your DB logic.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Schema-less = stress-less&lt;/strong&gt;&lt;br&gt;
You don’t get yelled at for not “planning ahead.” You just ship.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  ⚖️ But if you &lt;em&gt;have&lt;/em&gt; to use SQL…
&lt;/h2&gt;

&lt;p&gt;Some teams/projects require relational DBs. Here are tools that make the pain tolerable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prisma&lt;/strong&gt; Type-safe, declarative schema, powerful migration engine&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Knex.js + Objection.js&lt;/strong&gt; Mature and battle-tested in Node environments&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Atlas by Ariga&lt;/strong&gt; Clean migration workflows, good for CI/CD&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Supabase&lt;/strong&gt; 🔥 Firebase-like developer experience &lt;strong&gt;on top of PostgreSQL&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Instant APIs&lt;/li&gt;
&lt;li&gt;Auth + storage included&lt;/li&gt;
&lt;li&gt;Web UI for managing schema/migrations&lt;/li&gt;
&lt;li&gt;Great for teams that &lt;em&gt;need&lt;/em&gt; SQL but want NoSQL-level velocity&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why Supabase is different
&lt;/h3&gt;

&lt;p&gt;Even though it’s SQL under the hood, &lt;strong&gt;Supabase makes schema changes easier to manage&lt;/strong&gt;. You can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;View and edit tables in the dashboard&lt;/li&gt;
&lt;li&gt;Auto-generate APIs&lt;/li&gt;
&lt;li&gt;Roll back or push migrations with Git workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's a great compromise if your project is too relational for Firestore but still in flux.&lt;/p&gt;




&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;If the client says “we’re still figuring it out,” avoid SQL unless you enjoy stress.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Use &lt;strong&gt;Firestore&lt;/strong&gt; when things are unstable or MVP&lt;/li&gt;
&lt;li&gt;✅ Use &lt;strong&gt;Supabase&lt;/strong&gt; if you &lt;em&gt;need&lt;/em&gt; SQL but want NoSQL flexibility&lt;/li&gt;
&lt;li&gt;✅ Use proper migration tools (Prisma, Knex, Atlas) if you're stuck with raw SQL&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Choose tools that match the &lt;em&gt;reality&lt;/em&gt; of the project, not what sounds good on paper.&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>database</category>
      <category>webdev</category>
      <category>career</category>
    </item>
    <item>
      <title>I Started My Own Company at 20 and Landed a $70K Contract. Here's What I'm Building Next</title>
      <dc:creator>Chris</dc:creator>
      <pubDate>Sun, 11 May 2025 02:41:10 +0000</pubDate>
      <link>https://dev.to/criscmd/i-started-my-own-company-at-20-and-landed-a-70k-contract-heres-what-im-building-next-1h0c</link>
      <guid>https://dev.to/criscmd/i-started-my-own-company-at-20-and-landed-a-70k-contract-heres-what-im-building-next-1h0c</guid>
      <description>&lt;p&gt;At 20 years old, I took the leap and officially founded my first company. I’ve been a software engineer since I was 15, leading teams, shipping products, and consulting. But this is different. This is mine.&lt;/p&gt;

&lt;p&gt;Just days after incorporation, I closed a $70,000 contract. No funding. No connections. No fancy pitch decks. Just proof of work, relentless execution, and solving real problems fast.&lt;/p&gt;

&lt;p&gt;Now I’m channeling that momentum into building something much bigger.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing &lt;em&gt;NousCore&lt;/em&gt; My Legacy Project
&lt;/h2&gt;

&lt;p&gt;I won’t spill everything. There are too many eyes and too many people trying to ride waves they didn’t earn.&lt;/p&gt;

&lt;p&gt;But here’s what I can share:&lt;/p&gt;

&lt;p&gt;• It’s an AI-native platform that runs on the edge, not in the cloud&lt;br&gt;
• It’s built for performance, privacy, and permanence&lt;br&gt;
• It combines my love for systems programming, local-first data, and real-world utility&lt;br&gt;
• It’s memory-efficient, resilient, and doesn’t need a datacenter to function&lt;/p&gt;

&lt;p&gt;Think of it as a personal AI system that works for you and not for advertisers, not for surveillance, and not for data farms.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I’m Doing This
&lt;/h2&gt;

&lt;p&gt;I’m tired of bloated software and overengineered cloud stacks that leak data and burn budgets. I want to create something powerful, self-contained, and private. Something that lasts.&lt;/p&gt;

&lt;p&gt;This isn’t just a product. It’s a statement.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;I’ll be documenting parts of my journey (without giving away the sauce). From technical breakdowns to startup realities.&lt;/p&gt;

&lt;p&gt;If you're into AI, systems design, or just want to build things that matter, follow along.&lt;/p&gt;

&lt;p&gt;Let’s make tech that respects people again.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>discuss</category>
      <category>career</category>
      <category>startup</category>
    </item>
    <item>
      <title>Stop Letting AI Do Your Thinking For You</title>
      <dc:creator>Chris</dc:creator>
      <pubDate>Thu, 24 Apr 2025 03:36:12 +0000</pubDate>
      <link>https://dev.to/criscmd/stop-letting-ai-do-your-thinking-for-you-1ne9</link>
      <guid>https://dev.to/criscmd/stop-letting-ai-do-your-thinking-for-you-1ne9</guid>
      <description>&lt;p&gt;Lately it feels like roles are reversing.&lt;br&gt;&lt;br&gt;
Instead of developers using AI as a tool, they are becoming tools for AI.&lt;/p&gt;

&lt;p&gt;People plug their entire codebase into Copilot or Cursor, click accept like it is gospel, and think they are building software. You are not. You are just pasting code with no idea where it came from or how it fits into the system you are supposed to understand.&lt;/p&gt;

&lt;p&gt;Let me be clear:&lt;br&gt;&lt;br&gt;
I use AI. I use ChatGPT. But I use it like a &lt;strong&gt;scalpel&lt;/strong&gt;, not a &lt;strong&gt;crutch&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I do not touch Copilot. I do not rely on Cursor. Why?&lt;br&gt;&lt;br&gt;
Because the second you offload thinking, you lose track of context.&lt;br&gt;&lt;br&gt;
And context is the &lt;strong&gt;only real superpower&lt;/strong&gt; a software engineer has.&lt;/p&gt;

&lt;p&gt;No AI today not ChatGPT, not Claude, not anything can keep track of your mental model. They do not know the trade-offs you made last sprint. They do not remember that one brittle API you agreed to avoid touching until Q3. They do not understand why you chose composition over inheritance in a specific module that is now quietly holding your whole app together.&lt;/p&gt;

&lt;p&gt;But you do.&lt;br&gt;&lt;br&gt;
Your brain does.&lt;br&gt;&lt;br&gt;
And when you give up your role as the system’s architect to let some autocomplete guess what you need, you become the assistant, not the engineer.&lt;/p&gt;




&lt;h2&gt;
  
  
  AI is a hammer. You are still the one swinging it.
&lt;/h2&gt;

&lt;p&gt;Use AI to accelerate your thinking. Ask it to break down a regex, write a first draft of a boring config, or summarize documentation. Fine. Great. But never let it own the architecture. Do not let it become your crutch for avoiding complexity.&lt;/p&gt;

&lt;p&gt;Because if you do, one day you will open your codebase and realize you do not even recognize the thing you built.&lt;br&gt;&lt;br&gt;
And worse, you will not know how to fix it.&lt;/p&gt;




&lt;p&gt;If this resonates, drop your thoughts. I am all for AI, but we need to stop worshipping it like it is the engineer. It is not. You are.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>ai</category>
      <category>webdev</category>
      <category>discuss</category>
    </item>
    <item>
      <title>How I Made My SaaS "Students Only" Without School IDs Using WHOIS and GPT</title>
      <dc:creator>Chris</dc:creator>
      <pubDate>Thu, 24 Apr 2025 00:49:18 +0000</pubDate>
      <link>https://dev.to/criscmd/how-i-made-my-saas-students-only-without-school-ids-using-whois-and-gpt-1p82</link>
      <guid>https://dev.to/criscmd/how-i-made-my-saas-students-only-without-school-ids-using-whois-and-gpt-1p82</guid>
      <description>&lt;p&gt;In building my resume review SaaS tailored for students and 転職活動 (career change) seekers in Japan, I faced a simple question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"How do I restrict access to just students without relying on official university logins or SheerID?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A lot of companies solve this using school issued IDs, OAuth through G Suite for Education, or even SheerID verification. But all of those come with integration complexity, potential friction, and worst of all kill the speed and ease of my UX.&lt;/p&gt;

&lt;p&gt;So I built something better.&lt;/p&gt;




&lt;h3&gt;
  
  
  ✅ The Goal: Students Only Access With Just an Email
&lt;/h3&gt;

&lt;p&gt;My signup form asks for a university email. That’s it. If your domain is a real school, you’re in. If it’s not, you’re blocked. No ID upload. No OAuth. No API dependency.&lt;/p&gt;




&lt;h3&gt;
  
  
  ⚙️ The Stack
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Backend&lt;/strong&gt;: Node.js (NestJS)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database&lt;/strong&gt;: Firestore (for whitelist and blacklist)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;External Tools&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;WHOIS lookup&lt;/li&gt;
&lt;li&gt;OpenAI GPT API&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;No SheerID. No OAuth. No .edu requirement&lt;/strong&gt;&lt;/li&gt;

&lt;/ul&gt;




&lt;h3&gt;
  
  
  💡 The Logic
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;User submits an email like &lt;code&gt;someone@s.chibakoudai.jp&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;I extract the domain: &lt;code&gt;s.chibakoudai.jp&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Check my Firestore whitelist and blacklist&lt;/li&gt;
&lt;li&gt;If unknown, use WHOIS and GPT to verify&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  🔍 WHOIS and GPT Magic
&lt;/h3&gt;

&lt;p&gt;If the domain isn’t already in my whitelist, I check WHOIS to get metadata like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Organization name&lt;/li&gt;
&lt;li&gt;Registrant info&lt;/li&gt;
&lt;li&gt;Domain category (often &lt;code&gt;.ac.jp&lt;/code&gt;, &lt;code&gt;.edu&lt;/code&gt;, etc.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then I feed that WHOIS data into ChatGPT with a prompt like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Is this domain associated with an educational institution? Just return Yes or No."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If GPT says yes → ✅ add to whitelist&lt;br&gt;&lt;br&gt;
If GPT says no → ❌ add to blacklist and block the user&lt;/p&gt;




&lt;h3&gt;
  
  
  ✨ Example Code Snippet
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;whoisData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;lookupWhois&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`Is this domain from a university or educational institution?\n\nWHOIS:\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;whoisData&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;gptResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gpt-4&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isSchool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;gptResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Yes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  🧠 Why It Works
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fast UX&lt;/strong&gt; — Users don’t need to upload IDs or register school portals&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self expanding list&lt;/strong&gt; — My whitelist grows automatically as GPT verifies new domains&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Low Cost&lt;/strong&gt; — GPT and WHOIS is cheaper and faster than commercial APIs&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  🔐 What About Abuse
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Domains like &lt;code&gt;gmail.com&lt;/code&gt; or &lt;code&gt;yahoo.co.jp&lt;/code&gt; get instantly rejected&lt;/li&gt;
&lt;li&gt;Once flagged, a domain is blacklisted and denied forever&lt;/li&gt;
&lt;li&gt;This isn’t a hardcore identity proof — it’s smart friction&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  🚀 Outcome
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Zero authentication integrations&lt;/li&gt;
&lt;li&gt;Super smooth UX&lt;/li&gt;
&lt;li&gt;Only real students can register&lt;/li&gt;
&lt;li&gt;No need to deal with Japanese school bureaucracy (trust me, that’s worth it)&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  🤖 TLDR
&lt;/h3&gt;

&lt;p&gt;I used WHOIS and GPT to check if an email domain belonged to a school and whitelist it automatically. No OAuth. No ID. Just a clever prompt and database check. Lightweight, fast, and clean.&lt;/p&gt;




&lt;p&gt;If you want to implement this or have questions, feel free to drop a comment. Happy to share the logic in more detail&lt;br&gt;&lt;br&gt;
And if you’re working on an AI SaaS and want to avoid auth complexity this might be the easiest "student only" gate you'll ever build.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>security</category>
    </item>
    <item>
      <title>How I Landed a $150K Contract at 20 While Everyone Else Was Virtue Signaling</title>
      <dc:creator>Chris</dc:creator>
      <pubDate>Tue, 22 Apr 2025 10:24:31 +0000</pubDate>
      <link>https://dev.to/criscmd/how-i-landed-a-150k-contract-at-20-while-everyone-else-was-virtue-signaling-2hdo</link>
      <guid>https://dev.to/criscmd/how-i-landed-a-150k-contract-at-20-while-everyone-else-was-virtue-signaling-2hdo</guid>
      <description>&lt;p&gt;Let’s be real&lt;br&gt;&lt;br&gt;
Most of what you see online from “young founders” is noise&lt;/p&gt;

&lt;p&gt;Pitch decks, buzzwords, hackathon demos, endless posts about “building in public”&lt;br&gt;&lt;br&gt;
No users, no product, no revenue&lt;br&gt;&lt;br&gt;
Just vibes&lt;/p&gt;

&lt;p&gt;Meanwhile, I’m 20&lt;br&gt;&lt;br&gt;
No college&lt;br&gt;&lt;br&gt;
No investor&lt;br&gt;&lt;br&gt;
No clout&lt;br&gt;&lt;br&gt;
Just skill and execution&lt;/p&gt;

&lt;p&gt;And with that alone, I closed a $150,000 contract to build a full-scale SNS platform from the ground up&lt;/p&gt;

&lt;p&gt;This post isn’t about bragging&lt;br&gt;&lt;br&gt;
It’s about showing you that you don’t need credentials when you can provide undeniable value&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Did (That Most Don’t)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. I shut up and learned to build
&lt;/h3&gt;

&lt;p&gt;I didn’t waste my time posting “day 34 of building my startup” or preaching on LinkedIn&lt;br&gt;&lt;br&gt;
I spent my time mastering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Full stack engineering (React, NestJS, PostgreSQL, etc.)&lt;/li&gt;
&lt;li&gt;Real world infrastructure (GCP, Docker, Firebase, Cloud Run)&lt;/li&gt;
&lt;li&gt;Clean architecture, auth systems, deployment pipelines&lt;/li&gt;
&lt;li&gt;Systems that actually scale&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While others were building MVPs with no understanding of backend security or performance, I built real, working apps&lt;/p&gt;

&lt;h3&gt;
  
  
  2. I made my skills public through work
&lt;/h3&gt;

&lt;p&gt;No resumes. Just output&lt;br&gt;&lt;br&gt;
I didn’t tell people I could build, I showed them&lt;/p&gt;

&lt;p&gt;I took smaller gigs early, overdelivered, built trust, and made sure everything I touched was undeniably professional&lt;br&gt;&lt;br&gt;
Design. Docs. Architecture. Deployment&lt;br&gt;&lt;br&gt;
All clean. All tight&lt;/p&gt;

&lt;p&gt;When the $150K opportunity came, there was no question of "can you handle this"&lt;br&gt;&lt;br&gt;
They already knew I could&lt;/p&gt;

&lt;h3&gt;
  
  
  3. I didn’t act like a founder, I acted like an engineer
&lt;/h3&gt;

&lt;p&gt;I didn’t wear a fake CEO badge or pretend I was changing the world&lt;br&gt;&lt;br&gt;
I scoped the problem, broke it down, communicated clearly, and delivered a real execution plan&lt;/p&gt;

&lt;p&gt;That alone made me stand out more than any mission statement ever could&lt;/p&gt;

&lt;h2&gt;
  
  
  Why It Worked
&lt;/h2&gt;

&lt;p&gt;Because companies don’t care about your startup story&lt;br&gt;&lt;br&gt;
They care about whether you can deliver something that works, that scales, and that solves a problem&lt;/p&gt;

&lt;p&gt;I didn’t pretend to be a visionary&lt;br&gt;&lt;br&gt;
I just did the work, and that work turned into value which turned into a $150K payout&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Words
&lt;/h2&gt;

&lt;p&gt;If you’re young and trying to get in&lt;br&gt;&lt;br&gt;
Stop trying to look impressive. Be useful&lt;br&gt;&lt;br&gt;
Stop preaching about disruption. Start delivering output&lt;br&gt;&lt;br&gt;
Let your craft speak for itself&lt;/p&gt;

&lt;p&gt;Because in the end&lt;br&gt;&lt;br&gt;
Value over virtue signaling&lt;br&gt;&lt;br&gt;
Results over resumes&lt;br&gt;&lt;br&gt;
Execution over excuses&lt;/p&gt;

&lt;p&gt;I’m 20, I didn’t go to college, and I’m already profiting with skill alone&lt;br&gt;&lt;br&gt;
You can too if you stop playing the game everyone else is playing&lt;/p&gt;

</description>
      <category>programming</category>
      <category>discuss</category>
      <category>career</category>
    </item>
  </channel>
</rss>
