<?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: Kan-Chen Lin</title>
    <description>The latest articles on DEV Community by Kan-Chen Lin (@kanchen_lin_331136af621d).</description>
    <link>https://dev.to/kanchen_lin_331136af621d</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3634020%2F1154df49-6bd8-4f77-84c8-6b2cd0e465e7.jpg</url>
      <title>DEV Community: Kan-Chen Lin</title>
      <link>https://dev.to/kanchen_lin_331136af621d</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kanchen_lin_331136af621d"/>
    <language>en</language>
    <item>
      <title>From Template to Production-Shaped: An AI-Native Dev Flow for Go Side Projects</title>
      <dc:creator>Kan-Chen Lin</dc:creator>
      <pubDate>Tue, 26 May 2026 02:07:42 +0000</pubDate>
      <link>https://dev.to/kanchen_lin_331136af621d/from-template-to-production-shaped-an-ai-native-dev-flow-for-go-side-projects-245g</link>
      <guid>https://dev.to/kanchen_lin_331136af621d/from-template-to-production-shaped-an-ai-native-dev-flow-for-go-side-projects-245g</guid>
      <description>&lt;p&gt;I wanted my next side project to &lt;em&gt;look&lt;/em&gt; like the kind of code I'd ship at work — hexagonal architecture, sqlc, depguard, integration tests — without the usual side-project tax of spending three evenings on scaffolding before writing the first line of domain logic. So I built it twice. First, I forked a Go backend template I'd been hardening for months. Then I drove every feature on top of it through a structured AI workflow I call &lt;strong&gt;qrspi&lt;/strong&gt;: &lt;em&gt;question → research → structure → plan → implement&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The product itself is unremarkable on purpose: a QR code generator. Paste a URL, get back a scannable PNG and a &lt;code&gt;/r/:token&lt;/code&gt; redirect, with per-link scan counts and a soft-delete kill switch. The interesting part — the part I'd want a reviewer to look at — is the &lt;em&gt;process&lt;/em&gt; that produced it.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/linkc0829/go-qrcode-generator" rel="noopener noreferrer"&gt;linkc0829/go-qrcode-generator&lt;/a&gt;. Every artifact mentioned in this post is committed there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Choose the template, then commit to its rules
&lt;/h2&gt;

&lt;p&gt;Step zero was actually choosing what to build on. I shortlisted several Go backend templates, walked through each one with Claude to pressure-test the architecture, and landed on the one I'd been hardening for a while: &lt;a href="https://github.com/linkc0829/go-backend-template" rel="noopener noreferrer"&gt;linkc0829/go-backend-template&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The template is a feature-first hexagonal Go backend. Each feature lives in a single package under &lt;code&gt;internal/&amp;lt;feature&amp;gt;/&lt;/code&gt;, and inside that package, &lt;code&gt;domain.go&lt;/code&gt;, &lt;code&gt;service.go&lt;/code&gt;, &lt;code&gt;ports.go&lt;/code&gt;, and the adapters sit side-by-side as separate files. The Go package boundary &lt;em&gt;is&lt;/em&gt; the hexagon edge.&lt;/p&gt;

&lt;p&gt;What makes it stick is &lt;code&gt;depguard&lt;/code&gt; in &lt;code&gt;.golangci.yml&lt;/code&gt;. The build fails if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;domain.go&lt;/code&gt; imports anything beyond stdlib and shared value objects&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;service.go&lt;/code&gt; reaches for a driver or web framework&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;handler_*.go&lt;/code&gt; touches a repo or cache directly&lt;/li&gt;
&lt;li&gt;one feature imports another feature&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last rule is the one that pays the most rent. Cross-feature dependencies are forced through &lt;strong&gt;capability ports&lt;/strong&gt; — feature A defines an interface named after the capability it needs, and the composition root in &lt;code&gt;internal/bootstrap/wire.go&lt;/code&gt; injects feature B's service to satisfy it. The features never know about each other.&lt;/p&gt;

&lt;p&gt;This was the first decision I had to actually live with. The template ships with demo &lt;code&gt;user&lt;/code&gt;, &lt;code&gt;order&lt;/code&gt;, and &lt;code&gt;payment&lt;/code&gt; slices. My project has no orders and no payments. The rule is: don't leave dead code as "future scaffolding." Delete the whole slice — the package, the wire block, the SQL queries, the migration tables, the OpenAPI paths, the depguard block. &lt;code&gt;make lint &amp;amp;&amp;amp; make test&lt;/code&gt; after each removal flushes out dangling references. By the time I started writing QR code logic, the repo only knew about things that existed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Build the spec from a real system-design prompt
&lt;/h2&gt;

&lt;p&gt;The functional spec came from a system-design exercise I'd worked through separately:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generate a QR code from a URL&lt;/li&gt;
&lt;li&gt;302 redirect through our server on every scan (so we can count, and so we can kill a link)&lt;/li&gt;
&lt;li&gt;Targets: redirect latency &amp;lt; 100 ms, 1B codes, 100M users&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The high-level design called out the load shape — read-heavy, one write to thousands of reads — and the levers that fall out of it: stateless API behind a gateway, cache &lt;code&gt;qr_token → image_url&lt;/code&gt;, CDN the PNGs, index on &lt;code&gt;qr_token&lt;/code&gt;. Tokens were originally specified as &lt;code&gt;base62(SHA-256(url + user_secret))&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For the local build, I wrote down explicit &lt;strong&gt;deviations&lt;/strong&gt; from the spec rather than pretending they didn't exist:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tokens use 96-bit &lt;code&gt;crypto/rand&lt;/code&gt; → &lt;code&gt;base64url&lt;/code&gt;. Loses idempotency for repeated (user, url) pairs but avoids the deterministic-token leak surface.&lt;/li&gt;
&lt;li&gt;The CDN tier is dropped. The browser fetches PNGs directly from MinIO using its anonymous &lt;code&gt;download&lt;/code&gt; bucket policy. Same architectural shape as S3+CloudFront, minus the edge cache.&lt;/li&gt;
&lt;li&gt;Soft delete and PUT/DELETE endpoints land in a later slice.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Writing the deviations down up front is the part that makes the design honest. It's also the part that makes a portfolio reviewer's job easier — they can see &lt;em&gt;what was traded and why&lt;/em&gt;, not just what got built.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: qrspi — the workflow that does the actual building
&lt;/h2&gt;

&lt;p&gt;The workflow idea started from &lt;a href="https://github.com/humanlayer/advanced-context-engineering-for-coding-agents" rel="noopener noreferrer"&gt;Research-Plan-Implement&lt;/a&gt; (RPI). QRSPI is an 8-phase extension of it that I picked up from community discussions and adapted for this project.&lt;/p&gt;

&lt;p&gt;Once the spec was on paper, every feature went through the same eight phases. Each phase is a slash command backed by a skill, and each one writes its artifact to &lt;code&gt;thoughts/qrspi/&amp;lt;date&amp;gt;-&amp;lt;slug&amp;gt;/&lt;/code&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/qrspi:1_question&lt;/code&gt;&lt;/strong&gt; — decompose the ticket into neutral research questions. No opinions yet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/qrspi:2_research&lt;/code&gt;&lt;/strong&gt; — answer the questions by reading the codebase. Facts only.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/qrspi:3_design&lt;/code&gt;&lt;/strong&gt; — discuss &lt;em&gt;where&lt;/em&gt; we're going before &lt;em&gt;how&lt;/em&gt;. Trade-offs surface here.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/qrspi:4_structure&lt;/code&gt;&lt;/strong&gt; — outline vertical slices with test checkpoints.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/qrspi:5_plan&lt;/code&gt;&lt;/strong&gt; — the tactical implementation plan; my working document.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/qrspi:6_worktree&lt;/code&gt;&lt;/strong&gt; — isolated git worktree so the main checkout stays clean.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/qrspi:7_implement&lt;/code&gt;&lt;/strong&gt; — execute the plan phase by phase, verifying at each checkpoint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/qrspi:8_pr&lt;/code&gt;&lt;/strong&gt; — open a PR that carries the design context forward into review.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The MinIO feature shows the whole thing on disk: a &lt;code&gt;ticket.md&lt;/code&gt; pulled from the Notion source via MCP, then &lt;code&gt;questions.md&lt;/code&gt;, &lt;code&gt;research.md&lt;/code&gt;, &lt;code&gt;design.md&lt;/code&gt;, &lt;code&gt;structure.md&lt;/code&gt;, and &lt;code&gt;plan.md&lt;/code&gt;. Each one builds on the last. By the time implementation starts, the agent isn't guessing — it's executing a plan I already agreed with.&lt;/p&gt;

&lt;p&gt;The follow-up Redis redirect cache shipped the same way. The plan called out the read-heavy shape, picked a write-behind click-count buffer to avoid hammering Postgres on every scan, and named the cache invariants explicitly. The implementation was almost mechanical because the design phase had already resolved the interesting questions.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this buys, and what it costs
&lt;/h2&gt;

&lt;p&gt;The cost is real: each feature carries five or six markdown files of design artifacts. For a single-developer side project, that's overhead I wouldn't tolerate in a freeform sketch.&lt;/p&gt;

&lt;p&gt;What it buys:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The diff is reviewable.&lt;/strong&gt; Every commit is small, scoped, and traceable back to a design decision.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The architecture holds.&lt;/strong&gt; depguard catches the slow-drift violations (handler reaches into a repo, feature A imports feature B) the moment they appear, not three months later.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The agent stays useful past 2,000 LOC.&lt;/strong&gt; Most AI-coding flows degrade as the codebase grows because the model loses the plot. Writing the plot down — in &lt;code&gt;design.md&lt;/code&gt;, in &lt;code&gt;plan.md&lt;/code&gt; — keeps the next session grounded.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The portfolio story is the &lt;em&gt;process&lt;/em&gt;, not the artifact.&lt;/strong&gt; Anyone can ship a QR code generator. Shipping one where the architecture, the trade-offs, and the deviations from spec are all written down on disk is a different signal.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The template is on GitHub; the qrspi artifacts are committed alongside the code. If you want to see how a single feature flows from ticket to PR, &lt;a href="https://github.com/linkc0829/go-qrcode-generator/tree/main/thoughts/qrspi/2026-05-15-minio-local-object-storage" rel="noopener noreferrer"&gt;the MinIO slice&lt;/a&gt; is the cleanest example. The architecture ADRs in &lt;code&gt;docs/adr/&lt;/code&gt; cover the two foundational decisions: feature-first hexagonal, and sqlc over an ORM.&lt;/p&gt;

&lt;p&gt;Next up: a metrics slice (Prometheus + a Grafana dashboard for redirect latency), and a proper deletion follow-up so the spec's full CRUD surface lands. Both will go through qrspi. That's the point.&lt;/p&gt;

</description>
      <category>go</category>
      <category>architecture</category>
      <category>ai</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
