<?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: KevinTen</title>
    <description>The latest articles on DEV Community by KevinTen (@kevinten10).</description>
    <link>https://dev.to/kevinten10</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%2F3834064%2F52f24f49-8956-4731-b481-566860d83b68.jpg</url>
      <title>DEV Community: KevinTen</title>
      <link>https://dev.to/kevinten10</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kevinten10"/>
    <language>en</language>
    <item>
      <title>OpenOctopus: Realm-Native AI Life Assistant System With Summon Technology</title>
      <dc:creator>KevinTen</dc:creator>
      <pubDate>Wed, 29 Jul 2026 01:25:10 +0000</pubDate>
      <link>https://dev.to/kevinten10/openoctopus-realm-native-ai-life-assistant-system-with-summon-technology-2jnj</link>
      <guid>https://dev.to/kevinten10/openoctopus-realm-native-ai-life-assistant-system-with-summon-technology-2jnj</guid>
      <description>&lt;h1&gt;
  
  
  OpenOctopus: Realm-Native AI Life Assistant System With Summon Technology
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;OpenOctopus is an open source project hosted on GitHub. According to its repository documentation, the project is positioned as an "AI family home hub" with the guiding principle: "One event, every member, the right context." The project is released under the MIT license.&lt;/p&gt;

&lt;p&gt;As described in the README, OpenOctopus distinguishes itself from traditional unified chatbox designs by framing itself as "a family member in the group chat." The system organizes family life into "Realms" — pet, parents, finance, work, health — each with its own knowledge base, agent team, and skill set. The project documentation uses an octopus as its central metaphor: each tentacle has its own nerve center and acts autonomously, while a central brain coordinates everything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Core Features
&lt;/h2&gt;

&lt;p&gt;The documentation outlines OpenOctopus's key functional modules:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Realm Matrix&lt;/strong&gt; — A grid dashboard providing at-a-glance visibility into health scores, risks, and to-dos across all life realms&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Summon&lt;/strong&gt; — Transforms any entity (pet, person, asset) into an AI agent with memory, personality, and proactive behavior&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-Realm Intelligence&lt;/strong&gt; — A knowledge graph connecting entities across all realms (your pet realm knows your finance budget)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dual-layer Skills&lt;/strong&gt; — Global skills (search, calendar, email) plus realm-specific skills (vet lookup, tax calculation, law search)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent Teams&lt;/strong&gt; — Professional agents and summoned agents collaborate within each realm&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RealmHub&lt;/strong&gt; — One-click installation of pre-built realm packages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Governance&lt;/strong&gt; — Human-in-the-loop approval, full audit log, privacy and permission tiers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local-first&lt;/strong&gt; — SQLite by default, with optional cloud sync via PostgreSQL or Supabase&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Realm Matrix
&lt;/h2&gt;

&lt;p&gt;The documentation defines these pre-configured realm templates. Users can freely create, merge, or delete realms:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Realm&lt;/th&gt;
&lt;th&gt;Contents&lt;/th&gt;
&lt;th&gt;Example Agents&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pet&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Pets, vets, food records&lt;/td&gt;
&lt;td&gt;Health advisor, &lt;strong&gt;Momo&lt;/strong&gt; (summoned)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;parents&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Parents, health files&lt;/td&gt;
&lt;td&gt;Care assistant, &lt;strong&gt;Mom&lt;/strong&gt; (summoned)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;partner&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Partner, anniversaries&lt;/td&gt;
&lt;td&gt;Relationship advisor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;finance&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Accounts, investments, debts&lt;/td&gt;
&lt;td&gt;Budget planner, tax assistant&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;work&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Projects, colleagues, goals&lt;/td&gt;
&lt;td&gt;Task manager, weekly reporter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;legal&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Contracts, cases, statutes&lt;/td&gt;
&lt;td&gt;Contract lawyer, labor law advisor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;vehicle&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Car, insurance, maintenance&lt;/td&gt;
&lt;td&gt;Maintenance tracker, cost reporter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;home&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;House, appliances, repairs&lt;/td&gt;
&lt;td&gt;Home manager&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;health&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Check-ups, prescriptions&lt;/td&gt;
&lt;td&gt;Health monitor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;fitness&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Training plans, body data&lt;/td&gt;
&lt;td&gt;Fitness coach&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;hobby&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Projects, learning materials&lt;/td&gt;
&lt;td&gt;Learning coach&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;friends&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Social circle, events&lt;/td&gt;
&lt;td&gt;Social radar&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The Summon Mechanism
&lt;/h2&gt;

&lt;p&gt;The documentation describes Summon as the "killer feature" that turns data into a living agent.&lt;/p&gt;

&lt;p&gt;The transformation pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Raw data → Structured Entity → SUMMON → Agent with memory, personality &amp;amp; initiative
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Supported entity types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Living&lt;/strong&gt; — Pet, family, friend: Enables simulated dialogue, personality, and emotional expression&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Asset&lt;/strong&gt; — Car, house, portfolio: Enables status monitoring, maintenance alerts, and cost reporting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Organization&lt;/strong&gt; — Company, hospital: Provides process guides and contact management&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Abstract&lt;/strong&gt; — Goal, project, habit: Enables progress tracking, deviation alerts, and retrospectives&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The documentation includes this interaction example illustrating how summoned agents collaborate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You:  "I'm traveling for 5 days next week."

Momo (Pet):     "Who's going to feed me and walk me for 5 days?!"
Mom (Parents):  "Mom just said she'd like to visit — she could take care of Momo."
Car (Vehicle):  "Charge to full before departure? Or take a taxi to the airport?"
Budget (Finance): "Trip budget estimate: ¥X. Remember to save receipts."

→ System compiles a "Trip Prep Checklist" for your approval.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Agent Team Architecture
&lt;/h2&gt;

&lt;p&gt;The documentation defines a three-layer agent architecture:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Central Agents (global layer)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Router&lt;/strong&gt; — Intent detection and realm routing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coordinator&lt;/strong&gt; — Cross-realm analysis&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scheduler&lt;/strong&gt; — Proactive triggers and cron jobs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Realm Agents (per-realm layer)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Professional&lt;/strong&gt; — Specialized roles like legal advisor, budget planner, health monitor&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Summoned ✦&lt;/strong&gt; — Digitized entities like Momo, Mom, or a Tesla&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  RealmHub Ecosystem
&lt;/h2&gt;

&lt;p&gt;RealmHub is described as "like an app store, but for life domains." Users can install complete realm packages such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Legal Advisor Team&lt;/strong&gt; — Entity templates (lawyer, contract, statute) + 3 agents + skills&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pet Care&lt;/strong&gt; — Pet profile + health advisor + vet lookup + vaccination tracker&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Family Finance&lt;/strong&gt; — Accounts + budget planner + investment analyzer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each package includes: realm template, entity templates, agent configurations, skill configurations, and sample data.&lt;/p&gt;

&lt;h2&gt;
  
  
  High-Level Architecture
&lt;/h2&gt;

&lt;p&gt;The documentation provides this architectural diagram:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  CLI / Web Dashboard / Mobile
               │
               ▼
┌──────────────────────────────────────────────┐
│            OpenOctopus Core (Brain)           │
│                                              │
│  Router Agent · Coordinator · Scheduler      │
│  Knowledge Graph (cross-realm entity links)  │
│  Global Skills (search·calendar·email·i18n)  │
└──────┬──────────┬──────────┬──────────┬──────┘
       │          │          │          │
  ┌────▼───┐ ┌───▼────┐ ┌───▼────┐ ┌───▼────┐
  │  Pet   │ │Finance │ │ Legal  │ │Parents │  ...
  │ Realm  │ │ Realm  │ │ Realm  │ │ Realm  │
  │        │ │        │ │        │ │        │
  │ Agents │ │ Agents │ │ Agents │ │ Agents │
  │ Skills │ │ Skills │ │ Skills │ │ Skills │
  │ Memory │ │ Memory │ │ Memory │ │ Memory │
  └────────┘ └────────┘ └────────┘ └────────┘

  ✦ Summoned agents live inside their realm.
  ↔ Knowledge graph links entities across realms.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Governance Model
&lt;/h2&gt;

&lt;p&gt;Every agent action follows a documented trust chain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Human-in-the-loop&lt;/strong&gt; — Critical actions require explicit approval&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit log&lt;/strong&gt; — Full trace of every agent decision, evidence, and outcome&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Privacy tiers&lt;/strong&gt; — Per-realm permission control with data isolation between realms&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Budget limits&lt;/strong&gt; — Token and cost caps per agent, per realm, per action&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Technology Stack
&lt;/h2&gt;

&lt;p&gt;Based on the Dockerfile and package.json, the project's technical choices are:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Selection&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Runtime&lt;/td&gt;
&lt;td&gt;Node.js &amp;gt;= 22 + TypeScript&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gateway&lt;/td&gt;
&lt;td&gt;Unified orchestration (ref: OpenClaw Gateway)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Client&lt;/td&gt;
&lt;td&gt;Web Dashboard (Realm Matrix) + CLI (&lt;code&gt;tentacle&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data&lt;/td&gt;
&lt;td&gt;SQLite (local-first) + PostgreSQL / Supabase (optional sync)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vector&lt;/td&gt;
&lt;td&gt;pgvector / local vector store (per-realm sharding)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Plugin&lt;/td&gt;
&lt;td&gt;Global Skill + Realm Skill + Realm Package spec&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The project uses pnpm as its package manager (version 10.30.2). CI/CD is managed through GitHub Actions.&lt;/p&gt;

&lt;p&gt;The project supports multiple LLM providers. The documentation specifically mentions configuration for the Volcengine Ark Agent Plan, requiring &lt;code&gt;ARK_API_KEY&lt;/code&gt;, &lt;code&gt;ARK_BASE_URL&lt;/code&gt;, and &lt;code&gt;ARK_CHAT_MODEL&lt;/code&gt; environment variables.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ecosystem Components
&lt;/h2&gt;

&lt;p&gt;The documentation defines the project's ecosystem naming conventions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Realm marketplace: &lt;strong&gt;RealmHub&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;CLI: &lt;strong&gt;tentacle&lt;/strong&gt; (Tentacle = reach &amp;amp; touch)&lt;/li&gt;
&lt;li&gt;Agent gateway: &lt;strong&gt;ink&lt;/strong&gt; (Ink = information flow)&lt;/li&gt;
&lt;li&gt;Summon engine: &lt;strong&gt;summon&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Community: &lt;strong&gt;The Reef&lt;/strong&gt; (Coral reef = habitat)&lt;/li&gt;
&lt;li&gt;Realm config: &lt;strong&gt;REALM.md&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Entity persona: &lt;strong&gt;SOUL.md&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Project Mascot
&lt;/h2&gt;

&lt;p&gt;The project mascot is a deep-sea octopus named &lt;strong&gt;Octo&lt;/strong&gt;. The documentation describes Octo as "calm, multi-threaded, quietly brilliant." Eight arms juggle your entire life while one brain keeps it all in sync. The tentacle tips glow cyan when summoning entities to life.&lt;/p&gt;

&lt;h2&gt;
  
  
  Interpretation and Discussion
&lt;/h2&gt;

&lt;p&gt;The content above is based entirely on the project's public README documentation and configuration files found in the code repository. All feature descriptions reflect design goals and specifications documented in the repository.&lt;/p&gt;

&lt;p&gt;This project's architecture presents an innovative approach to family AI assistants. Whereas traditional AI assistants typically provide a unified conversational interface, OpenOctopus's "multi-realm + multi-agent" paradigm attempts to provide more granular context management and role specialization. The Summon mechanism is an intriguing conceptual exploration that seeks to digitize real-world entities as agents and enable multi-agent collaboration.&lt;/p&gt;

&lt;p&gt;For developers researching agent architectures, family AI applications, or multi-agent systems, this project's code and documentation may serve as a useful reference. The project is open source, and interested readers can examine the code repository to understand the implementation details.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Project repository: &lt;a href="https://github.com/open-octopus/openoctopus" rel="noopener noreferrer"&gt;https://github.com/open-octopus/openoctopus&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Referenced commit: &lt;a href="https://github.com/open-octopus/openoctopus/commit/67ac1f92f99ea8285dcf6e14c06077681376a885" rel="noopener noreferrer"&gt;https://github.com/open-octopus/openoctopus/commit/67ac1f92f99ea8285dcf6e14c06077681376a885&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>opensource</category>
      <category>ai</category>
      <category>productivity</category>
      <category>agents</category>
    </item>
    <item>
      <title>ccuse: A Profile Switcher for Claude Code CLI</title>
      <dc:creator>KevinTen</dc:creator>
      <pubDate>Mon, 27 Jul 2026 16:00:33 +0000</pubDate>
      <link>https://dev.to/kevinten10/ccuse-a-profile-switcher-for-claude-code-cli-3e5n</link>
      <guid>https://dev.to/kevinten10/ccuse-a-profile-switcher-for-claude-code-cli-3e5n</guid>
      <description>&lt;h1&gt;
  
  
  ccuse: A Profile Switcher for Claude Code CLI
&lt;/h1&gt;

&lt;p&gt;This repository contains &lt;code&gt;ccuse&lt;/code&gt;, a command-line profile switcher designed for the Claude Code CLI. The tool enables developers to switch between native Claude Code account/subscription mode and compatible API providers across three configuration scopes: globally, per project, or for a single temporary shell session.&lt;/p&gt;

&lt;h2&gt;
  
  
  What ccuse Provides
&lt;/h2&gt;

&lt;p&gt;According to the repository documentation, ccuse addresses a specific need for developers using Claude Code with multiple AI API providers. Instead of manually editing configuration files or managing multiple sets of environment variables each time you want to use a different model provider, ccuse creates, stores, and activates profiles through simple shell commands.&lt;/p&gt;

&lt;p&gt;The documented functionality supports five API provider profiles:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Claude (Native)&lt;/strong&gt; - Uses the Claude Code account/subscription or Anthropic API directly&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Volcengine Ark&lt;/strong&gt; - Uses the Volcengine Ark Agent Plan&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GLM via Ark Agent Plan&lt;/strong&gt; - Uses GLM models through the Volcengine Ark Agent Plan&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kimi&lt;/strong&gt; - Uses Moonshot AI's Kimi API&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MiniMax&lt;/strong&gt; - Uses the MiniMax API&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each profile corresponds to specific models. For example, the Volcengine Ark profile supports &lt;code&gt;doubao-seed-2-0-code-preview-260215&lt;/code&gt;, GLM profiles support &lt;code&gt;GLM-5.1&lt;/code&gt;, &lt;code&gt;GLM-4.7&lt;/code&gt;, and &lt;code&gt;GLM-4.7-FlashX&lt;/code&gt;, Kimi profiles support &lt;code&gt;kimi-k2.6&lt;/code&gt;, and MiniMax profiles support &lt;code&gt;MiniMax-M2.7&lt;/code&gt; and &lt;code&gt;MiniMax-M2.7-highspeed&lt;/code&gt;. Context windows for these models range up to 256K, based on each provider's documented specifications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three Configuration Scopes
&lt;/h2&gt;

&lt;p&gt;One of the key design features described in the documentation is the ability to apply profiles at three different scopes, each with different persistence characteristics.&lt;/p&gt;

&lt;h3&gt;
  
  
  Global Scope
&lt;/h3&gt;

&lt;p&gt;Global profile switching modifies the main Claude Code &lt;code&gt;settings.json&lt;/code&gt; file. When activated, the selected profile becomes the default for all Claude Code instances launched anywhere on the system. The tool automatically creates a backup of the current settings file before switching profiles.&lt;/p&gt;

&lt;p&gt;Commands at this scope include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ccuse claude&lt;/code&gt; or &lt;code&gt;ccuse global claude&lt;/code&gt; - Switch to native Claude profile&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ccuse ark&lt;/code&gt; or &lt;code&gt;ccuse global ark&lt;/code&gt; - Switch to Volcengine Ark profile&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ccuse glm&lt;/code&gt; or &lt;code&gt;ccuse global glm&lt;/code&gt; - Switch to GLM profile&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ccuse kimi&lt;/code&gt; or &lt;code&gt;ccuse global kimi&lt;/code&gt; - Switch to Kimi profile&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ccuse minimax&lt;/code&gt; or &lt;code&gt;ccuse global minimax&lt;/code&gt; - Switch to MiniMax profile&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Project Scope
&lt;/h3&gt;

&lt;p&gt;Project-scoped profiles allow a specific repository or working directory to use a different provider than the global default. This is useful when working on multiple projects that have different model requirements or when you want to isolate testing of a particular provider without affecting your global default configuration.&lt;/p&gt;

&lt;p&gt;Project scope creates a &lt;code&gt;./.claude/settings.json&lt;/code&gt; file in the current working directory, leaving the global &lt;code&gt;~/.claude/settings.json&lt;/code&gt; file untouched. Claude Code automatically reads project-level configuration when launched from that directory.&lt;/p&gt;

&lt;p&gt;Commands include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ccuse project &amp;lt;profile&amp;gt;&lt;/code&gt; - Persist profile for current project&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ccuse project show&lt;/code&gt; - Display current project profile&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ccuse project rm&lt;/code&gt; - Remove project-specific profile&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Session Scope
&lt;/h3&gt;

&lt;p&gt;Session-level switching operates entirely through environment variables and makes no permanent file changes. This is useful for one-off testing or when you want to temporarily try a different provider without changing any persistent configuration.&lt;/p&gt;

&lt;p&gt;When using &lt;code&gt;eval "$(ccuse local &amp;lt;profile&amp;gt;)"&lt;/code&gt;, the profile's environment variables are loaded into the current shell session. The &lt;code&gt;ccuse start &amp;lt;profile&amp;gt;&lt;/code&gt; command loads the environment variables and immediately launches Claude Code.&lt;/p&gt;

&lt;p&gt;Quick-start shortcuts are also provided:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ccuse cc&lt;/code&gt; - Claude profile + launch&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ccuse ca&lt;/code&gt; - Ark profile + launch&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ccuse ck&lt;/code&gt; - Kimi profile + launch&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ccuse cg&lt;/code&gt; - GLM profile + launch&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ccuse cm&lt;/code&gt; - MiniMax profile + launch&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;-c&lt;/code&gt; or &lt;code&gt;--continue&lt;/code&gt; flag can be added to resume the last Claude Code conversation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Profile Management Features
&lt;/h2&gt;

&lt;p&gt;Beyond switching profiles, the tool includes several profile management commands:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ccuse init-&amp;lt;provider&amp;gt;&lt;/code&gt; - Creates or reconfigures a profile template&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ccuse list&lt;/code&gt; - Lists all available profiles and marks the active one&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ccuse show&lt;/code&gt; - Shows current Claude Code configuration with secrets masked&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ccuse doctor&lt;/code&gt; - Checks setup, active scopes, and suggests next steps&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ccuse edit &amp;lt;name&amp;gt;&lt;/code&gt; - Opens a profile file for editing&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ccuse remove &amp;lt;name&amp;gt;&lt;/code&gt; - Removes a profile after confirmation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;ccuse doctor&lt;/code&gt; command is described as particularly useful for first-time users or when troubleshooting, as it checks whether Claude Code, Python, profiles, and project settings are properly configured.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation Methods
&lt;/h2&gt;

&lt;p&gt;Three installation approaches are documented:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;One-Line Install (Recommended)&lt;/strong&gt;: Downloads the script directly to &lt;code&gt;~/.local/bin/ccuse&lt;/code&gt; and makes it executable&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Install via Claude Code Skill&lt;/strong&gt;: Can be installed by asking Claude Code directly&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manual Install&lt;/strong&gt;: Involves cloning the repository and creating a symlink to the executable&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After installation, &lt;code&gt;ccuse --help&lt;/code&gt; verifies that the tool is working correctly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Profile Setup Workflow
&lt;/h2&gt;

&lt;p&gt;The documented workflow for setting up profiles follows five steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Check setup&lt;/strong&gt; with &lt;code&gt;ccuse doctor&lt;/code&gt; to verify prerequisites&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Save current Claude settings&lt;/strong&gt; with &lt;code&gt;ccuse init-claude&lt;/code&gt; to create a native Claude profile&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create additional profiles&lt;/strong&gt; using commands like &lt;code&gt;ccuse init-ark&lt;/code&gt;, &lt;code&gt;ccuse init-glm&lt;/code&gt;, &lt;code&gt;ccuse init-kimi&lt;/code&gt;, or &lt;code&gt;ccuse init-minimax&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set API keys&lt;/strong&gt; by editing the profile files that are automatically opened&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Choose the scope&lt;/strong&gt; for each profile: one-time session, project-specific, or global&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When creating profiles, the init commands automatically open the profile file for editing using the user's preferred editor (following the order: &lt;code&gt;$EDITOR&lt;/code&gt; environment variable, VS Code &lt;code&gt;code&lt;/code&gt;, then &lt;code&gt;nano&lt;/code&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Custom Profiles
&lt;/h2&gt;

&lt;p&gt;The documentation indicates that developers can create custom profiles for any Anthropic-compatible API. Custom profiles are stored as JSON files in &lt;code&gt;~/.claude/profiles/&lt;/code&gt; and can contain configuration for API keys, base URLs, timeout settings, and other environment variables.&lt;/p&gt;

&lt;p&gt;Profile naming rules are specified: names must start with a letter or number and may contain letters, numbers, dots, underscores, and hyphens. Certain names are reserved and cannot be used for custom profiles, including built-in commands and aliases.&lt;/p&gt;

&lt;h2&gt;
  
  
  File Structure
&lt;/h2&gt;

&lt;p&gt;The tool stores profiles and configuration in the following directory structure under &lt;code&gt;~/.claude/&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;settings.json&lt;/code&gt; - Active global configuration&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;settings.json.bak&lt;/code&gt; - Automatic backup&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;profiles/&lt;/code&gt; - Directory containing individual profile JSON files&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Several environment variables are available to customize these paths, including &lt;code&gt;CLAUDE_DIR&lt;/code&gt;, &lt;code&gt;PROFILE_DIR&lt;/code&gt;, &lt;code&gt;SETTINGS_FILE&lt;/code&gt;, &lt;code&gt;BACKUP_SUFFIX&lt;/code&gt;, &lt;code&gt;PROJECT_CLAUDE_DIR&lt;/code&gt;, and &lt;code&gt;PROJECT_SETTINGS_FILE&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing
&lt;/h2&gt;

&lt;p&gt;The repository includes a comprehensive test suite using bats-core (Bash Automated Testing System). Tests can be run after installing bats with &lt;code&gt;bats tests/test_ccuse.bats&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Interpretation and Use Cases
&lt;/h2&gt;

&lt;p&gt;Based on the documented functionality, this tool appears most useful for developers who regularly use Claude Code and want to experiment with multiple AI API providers without manual configuration overhead. The three scopes (global, project, session) provide flexibility matching different workflows.&lt;/p&gt;

&lt;p&gt;Developers working across multiple projects with different model requirements may find the project-scoped profiles particularly useful, allowing each repository to maintain its own default provider without affecting the global configuration. Similarly, the session-level switching is well-suited for side-by-side testing of different providers on the same tasks.&lt;/p&gt;

&lt;p&gt;The ability to create custom profiles also means the tool is not limited to the five pre-configured providers, potentially extending its utility to any API endpoint with Anthropic-compatible authentication.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Repository: &lt;a href="https://github.com/kevinten-ai/ccuse" rel="noopener noreferrer"&gt;https://github.com/kevinten-ai/ccuse&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Verified commit: &lt;a href="https://github.com/kevinten-ai/ccuse/commit/07adde051c787b6e3bb3585ace9a2a6080ebd7f9" rel="noopener noreferrer"&gt;https://github.com/kevinten-ai/ccuse/commit/07adde051c787b6e3bb3585ace9a2a6080ebd7f9&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>opensource</category>
      <category>ai</category>
      <category>productivity</category>
      <category>developertools</category>
    </item>
    <item>
      <title>Turning 125 AI Tools into a Practical Open Source Handbook</title>
      <dc:creator>KevinTen</dc:creator>
      <pubDate>Mon, 27 Jul 2026 02:10:13 +0000</pubDate>
      <link>https://dev.to/kevinten10/turning-125-ai-tools-into-a-practical-open-source-handbook-2f96</link>
      <guid>https://dev.to/kevinten10/turning-125-ai-tools-into-a-practical-open-source-handbook-2f96</guid>
      <description>&lt;h1&gt;
  
  
  Turning 125 AI Tools into a Practical Open Source Handbook
&lt;/h1&gt;

&lt;p&gt;AI tool lists are easy to start and hard to keep useful. Models, pricing, quotas, regional availability, and product status change constantly. A page that only collects links can become stale before readers make a decision.&lt;/p&gt;

&lt;p&gt;The open source &lt;strong&gt;AI Tools Handbook&lt;/strong&gt; takes a more structured approach. Its current catalog contains 125 tools across seven categories: AI development environments, command-line tools, models, multimodal tools, agents, MCP tools, and AI skills. The project combines browsing and filtering with SWOT notes, five-axis radar scores, workflow guides, and side-by-side comparisons for up to four tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed in the latest refresh
&lt;/h2&gt;

&lt;p&gt;The July refresh focused on four practical areas:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Clearer information architecture.&lt;/strong&gt; The landing page, tool details, comparisons, workflows, and subscription-oriented views now form a more deliberate path instead of one long catalog.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;More reliable assets.&lt;/strong&gt; Tool logos are maintained as local project assets, reducing broken cards caused by external image URLs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stronger responsive and accessible behavior.&lt;/strong&gt; Navigation, focus states, semantic structure, and small-screen layouts received dedicated work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verifiable generated data.&lt;/strong&gt; Catalog indexes are generated by scripts and checked before builds, which helps prevent displayed counts from drifting away from source data.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why comparisons matter more than a universal ranking
&lt;/h2&gt;

&lt;p&gt;There is rarely one best AI tool for every situation. A strong code completion product may not be the best choice for repository-wide refactoring. A capable model may still be a poor fit because of pricing, privacy requirements, or regional availability.&lt;/p&gt;

&lt;p&gt;The handbook therefore keeps several dimensions visible instead of reducing every tool to one score. Readers can compare functionality, performance, usability, ecosystem, and value, then open the detailed notes for strengths, weaknesses, opportunities, and risks.&lt;/p&gt;

&lt;p&gt;Six workflow templates add another layer of context by connecting tools to concrete development, research, and content workflows. Optional community features, including ratings, comments, and Battle voting, use Supabase. When Supabase is not configured, those community features are hidden while the core catalog continues to work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;p&gt;The frontend uses Vue 3, Vite 8, Tailwind CSS, Pinia, and Vue Router. The repository includes local development, production build, test, Docker, and PWA support. A contributor can add or update tool data through the documented schema and then run the catalog validation before submitting a change.&lt;/p&gt;

&lt;h2&gt;
  
  
  An important limitation
&lt;/h2&gt;

&lt;p&gt;This is not a real-time pricing database. The repository explicitly warns that unverified pricing, model, quota, and product-status details should be treated as pending verification. The number 125 is a current repository snapshot, not a permanent promise.&lt;/p&gt;

&lt;p&gt;That boundary is useful. It separates durable comparison structure from facts that must be checked against official product documentation before a purchasing or architecture decision.&lt;/p&gt;

&lt;p&gt;Try the live handbook: &lt;a href="https://aitools.rxcloud.group/" rel="noopener noreferrer"&gt;https://aitools.rxcloud.group/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Explore the source: &lt;a href="https://github.com/ava-agent/ai-tools" rel="noopener noreferrer"&gt;https://github.com/ava-agent/ai-tools&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The project welcomes issues and pull requests for missing tools, stale details, and better comparison criteria.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>webdev</category>
      <category>vue</category>
    </item>
    <item>
      <title>Building Capa-BFF: The Three Design Principles Behind This Zero-Cost BFF Solution</title>
      <dc:creator>KevinTen</dc:creator>
      <pubDate>Sun, 28 Jun 2026 16:09:58 +0000</pubDate>
      <link>https://dev.to/kevinten10/building-capa-bff-the-three-design-principles-behind-this-zero-cost-bff-solution-e0n</link>
      <guid>https://dev.to/kevinten10/building-capa-bff-the-three-design-principles-behind-this-zero-cost-bff-solution-e0n</guid>
      <description>&lt;h1&gt;
  
  
  Building Capa-BFF: The Three Design Principles Behind This Zero-Cost BFF Solution
&lt;/h1&gt;

&lt;p&gt;Honestly, I didn't expect to be writing this post. When I first started working with Capa-BFF three months ago, I thought it was just another hackathon project that would die after the competition ended. But here we are — people are actually using it, asking questions about how it works, and genuinely interested in the design decisions behind it.&lt;/p&gt;

&lt;p&gt;So here's the thing: after using it in production for a few months and reading through the source code multiple times, I've come to appreciate the simplicity of its design. It's not the most complex BFF solution out there, but that's exactly the point. Sometimes the best solutions are the ones that get out of your way and let you get stuff done.&lt;/p&gt;

&lt;p&gt;Let me walk you through the three core design principles that make Capa-BFF work, and why I think they're actually pretty brilliant.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Problem Are We Even Solving Here?
&lt;/h2&gt;

&lt;p&gt;Before we jump into the code, let me remind you why BFFs exist in the first place. If you're working on a project with multiple clients (web, mobile, different devices), you've probably run into this issue:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The backend writes APIs for one client, and then another client needs different data, so you have to change the API again&lt;/li&gt;
&lt;li&gt;Frontend developers waste time waiting for backend changes just to get the data shaped differently&lt;/li&gt;
&lt;li&gt;Solutions like GraphQL are powerful but require a whole separate layer of development that you might not need&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I learned this the hard way on a previous project. We spent two weeks setting up GraphQL, writing schemas, integrating everything — and then the project scope changed, and half of that work was useless. That's when I started thinking: do we really need all that complexity just to let frontend get the data it needs?&lt;/p&gt;

&lt;p&gt;Enter Capa-BFF. The promise is "zero-cost BFF" — you drop it into your existing SpringBoot app, write a JSON config, and you're done. No extra deployment, no new services to maintain. Sounds too good to be true, right? Let's look under the hood.&lt;/p&gt;

&lt;h2&gt;
  
  
  Principle 1: Dynamic Invocation with the Triple Pattern
&lt;/h2&gt;

&lt;p&gt;The first core principle is &lt;strong&gt;dynamic invocation&lt;/strong&gt; using the (appId, methodName, data) triple.&lt;/p&gt;

&lt;p&gt;Here's the thing: any service call, no matter what framework you're using (Dubbo, SpringCloud, Istio, whatever), eventually boils down to three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Which application do you want to call? (appId)&lt;/li&gt;
&lt;li&gt;Which method on that application? (methodName)&lt;/li&gt;
&lt;li&gt;What data do you send it? (data)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's it. Capa-BFF takes this observation and runs with it. Instead of trying to create some fancy new abstraction, they just embrace this reality. Any service call can be represented as a triple.&lt;/p&gt;

&lt;p&gt;Let me show you what that looks like in practice with a real example. Suppose you're building a content platform where you need to get KOL details and then get their application information. Here's how you'd write that in Capa-BFF's DSL:&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"20725.gscontentcenterservice"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"getkoldetail"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"request"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"kolNo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"response"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"kolOrderDetail.id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"kol.id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"kolOrderDetail.applyId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"kol.applyId"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"getkolapplydetail"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"request"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"userId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${kol.applyId}"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"response"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"user.userId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user.id"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="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;Wait, that's just JSON. There's no special schema language to learn, no complicated CLI to generate types. It's just JSON. That's the beauty of it.&lt;/p&gt;

&lt;p&gt;The Capa-BFF parser takes this JSON and converts each method call into a triple. That's it. No magic, no complicated reflection tricks (well, okay, some reflection, but it's hidden from you).&lt;/p&gt;

&lt;p&gt;I remember when I first saw this, I thought "that's too simple — how can this work?" But then I realized: simplicity is the point. We've been conditioned to think that solving hard problems requires complex solutions, but sometimes the opposite is true.&lt;/p&gt;

&lt;h2&gt;
  
  
  Principle 2: Alias Space Mapping for Data Shaping
&lt;/h2&gt;

&lt;p&gt;The second principle is &lt;strong&gt;alias space mapping&lt;/strong&gt;. This is where things get interesting.&lt;/p&gt;

&lt;p&gt;When you call multiple services, you get multiple responses back, each in their own "namespace." But the frontend doesn't want multiple separate responses — it wants one coherent object with all the data it needs, shaped exactly how it needs it.&lt;/p&gt;

&lt;p&gt;Also, when one service depends on data from another service, you need a way to reference that data. How do you do that without making a mess?&lt;/p&gt;

&lt;p&gt;Capa-BFF's answer is alias mapping. Every response field gets mapped into a single shared alias space. When you need to reference data from a previous call, you just use the alias with &lt;code&gt;${alias.field}&lt;/code&gt; syntax.&lt;/p&gt;

&lt;p&gt;Let me extend the previous example to show you what this looks like:&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"20725.gscontentcenterservice"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"getkoldetail"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"request"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"kolNo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"response"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"kolOrderDetail.id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"kol.id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"kolOrderDetail.applyId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"kol.applyId"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"getkolapplydetail"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"request"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"userId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${kol.applyId}"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"response"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"user.userId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user.id"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"24901.livebackendservice"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"getLiveInfo"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"request"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"liveId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${live.id}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"userId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${user.id}"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"response"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"article.info.id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"article.id"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"11933.contentdeliveryservice"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"getarticleinfo"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"request"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"artilceid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${article.id}"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"response"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="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;See how that works? When &lt;code&gt;getkolapplydetail&lt;/code&gt; needs the &lt;code&gt;applyId&lt;/code&gt; from the first call, it just references &lt;code&gt;${kol.applyId}&lt;/code&gt;. The &lt;code&gt;kol&lt;/code&gt; alias was created in the first response mapping.&lt;/p&gt;

&lt;p&gt;This approach solves two problems at once:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Data aggregation&lt;/strong&gt;: All the results get combined into one response object with your aliases&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependency handling&lt;/strong&gt;: Dependencies are explicit and easy to follow&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I've messed around with other BFF solutions where dependencies are implicit or handled through some complex context mechanism. I'll take this explicit approach every single time. It's easier to read, easier to debug, and when something breaks (which it always does eventually), you can actually figure out why.&lt;/p&gt;

&lt;h2&gt;
  
  
  Principle 3: ID-Based Function Enhancement
&lt;/h2&gt;

&lt;p&gt;The third principle is &lt;strong&gt;ID-based function enhancement&lt;/strong&gt;. Honestly, when I first read this, I thought it was just theoretical fluff. But after using it, I get why it's useful.&lt;/p&gt;

&lt;p&gt;The idea is simple: anything can be an ID. You can add functions to enhance it. What counts as an ID?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A whole service method: &lt;code&gt;appId#methodName&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;A specific field path in a response&lt;/li&gt;
&lt;li&gt;An alias in the alias space&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's it. Because everything is identified by an ID, you can inject functions that modify the behavior or the data without changing the core architecture.&lt;/p&gt;

&lt;p&gt;Want to add caching to a specific service call? Write a function and attach it to the service method ID. Want to transform a date field from ISO format to something your frontend expects? Write a function and attach it to the field ID.&lt;/p&gt;

&lt;p&gt;Here's what that looks like in practice (simplified):&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"myapp.userservice"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"getUser"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"request"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"response"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"result.user.birthdate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user.birthdate"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;@formatDate(&lt;/span&gt;&lt;span class="s2"&gt;"YYYY-MM-DD"&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="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;The &lt;code&gt;@formatDate&lt;/code&gt; is a function enhancement attached to the &lt;code&gt;user.birthdate&lt;/code&gt; field ID. Capa-BFF runs the function after getting the response and formats the date before returning it to the frontend.&lt;/p&gt;

&lt;p&gt;I love this because it's open-ended but not overwhelming. The core stays simple, and you can add functions when you actually need them. You don't pay for what you don't use.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Hidden Gem: Dependency Resolution with DAG
&lt;/h2&gt;

&lt;p&gt;Okay, that's the three principles. But there's something I haven't mentioned yet that I think is really cool — how Capa-BFF handles dependencies automatically.&lt;/p&gt;

&lt;p&gt;When you have multiple service calls where some depend on others, Capa-BFF builds a Directed Acyclic Graph (DAG) of your dependencies, checks for cycles, and then generates the optimal execution order using topological sorting. Services with no dependencies run in parallel. Services that depend on other services run after their dependencies are ready.&lt;/p&gt;

&lt;p&gt;Let me show you what the dependency graph looks like for our example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;getkoldetail → getkolapplydetail → getLiveInfo → getarticleinfo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each step depends on the previous one, so they run sequentially. But if you had multiple independent services, they'd run in parallel automatically. No extra code from you — it just works.&lt;/p&gt;

&lt;p&gt;And if you accidentally create a circular dependency? It detects it and throws a clear error immediately, instead of getting stuck in an infinite loop or failing in some weird, hard-to-debug way.&lt;/p&gt;

&lt;p&gt;Here's a snippet of how they implement the cycle detection (from reading the source code):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GraphUtil&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;queryHasIllegalInvocation&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;DependOnFieldInfo&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;fieldInfoList&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; 
            &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;IllegalInvocationRequestException&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Build adjacency matrix for the graph&lt;/span&gt;
        &lt;span class="kt"&gt;boolean&lt;/span&gt;&lt;span class="o"&gt;[][]&lt;/span&gt; &lt;span class="n"&gt;adjacencyMatrix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;buildAdjacencyMatrix&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fieldInfoList&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Check for cycles using DFS&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;adjacencyMatrix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hasCycle&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;adjacencyMatrix&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;boolean&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;adjacencyMatrix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="o"&gt;],&lt;/span&gt; 
                        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;boolean&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;adjacencyMatrix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="o"&gt;]))&lt;/span&gt; &lt;span class="o"&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="nf"&gt;IllegalInvocationRequestException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                    &lt;span class="s"&gt;"Cyclic dependency detected at node "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="nf"&gt;hasCycle&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;boolean&lt;/span&gt;&lt;span class="o"&gt;[][]&lt;/span&gt; &lt;span class="n"&gt;adjacency&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; 
                           &lt;span class="kt"&gt;boolean&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;visited&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;boolean&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;recursionStack&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;recursionStack&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;])&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;visited&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;])&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

        &lt;span class="n"&gt;visited&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;recursionStack&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;adjacency&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;adjacency&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;][&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;hasCycle&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;adjacency&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;visited&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;recursionStack&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;recursionStack&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing fancy here — just classic computer science applied correctly. I appreciate that. They didn't reinvent the wheel; they just used the right tool for the job.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pros &amp;amp; Cons: Let's Be Honest
&lt;/h2&gt;

&lt;p&gt;I know I've been positive so far, but this wouldn't be a real post if I didn't tell you the bad stuff too. I promised I'd keep it real, so here we go.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Good (Pros)
&lt;/h3&gt;

&lt;p&gt;✅ &lt;strong&gt;Zero extra deployment&lt;/strong&gt;: You just drop it into your existing SpringBoot application. No extra Kubernetes pods, no extra CI/CD steps, nothing. For small teams that don't want to operate another service, this is huge.&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;No learning curve if you know JSON&lt;/strong&gt;: If you can write JSON, you can use Capa-BFF. I got my first configuration working in under 10 minutes. Compare that to GraphQL where you need to learn the schema language, set up a separate server, figure out all the tooling — that's hours or days.&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Automatic dependency handling&lt;/strong&gt;: I mentioned this earlier, but it bears repeating. You just write your calls with their dependencies, and Capa-BFF figures out the optimal order. Parallel execution where possible, sequential when necessary. It's transparent and just works.&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;It solves the actual problem&lt;/strong&gt;: The problem it's solving is "we have a SpringBoot app, we need to let frontend aggregate data without changing backend code every time." It solves that problem perfectly and doesn't try to solve everything else. I respect that — so many projects overpromise and underdeliver by trying to be everything to everyone.&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;It's lightweight&lt;/strong&gt;: The entire library is small enough that you can read the whole codebase in an afternoon. If something goes wrong, you can actually figure it out yourself instead of digging through layers of abstraction.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Not-So-Good (Cons)
&lt;/h3&gt;

&lt;p&gt;❌ &lt;strong&gt;Currently Java/SpringBoot only&lt;/strong&gt;: If you're not in the Java ecosystem, this isn't for you right now. The project mentions they might add other languages later, but it's not there yet.&lt;/p&gt;

&lt;p&gt;❌ &lt;strong&gt;Documentation is a bit sparse&lt;/strong&gt;: It's a hackathon project turned open source, so the docs are what they are. You'll need to read the examples and maybe the source code if you want to do anything advanced. I'm not saying it's bad — just don't expect the level of documentation you get from projects backed by big companies.&lt;/p&gt;

&lt;p&gt;❌ &lt;strong&gt;No built-in authentication/authorization&lt;/strong&gt;: You have to handle that yourself. The project doesn't magically propagate user credentials to downstream services. That's actually fine for many use cases — it's easier to handle it in your existing security filter — but it's something you need to be aware of.&lt;/p&gt;

&lt;p&gt;❌ &lt;strong&gt;Dynamic invocation means no compile-time checking&lt;/strong&gt;: Since everything is JSON, you won't get compile-time errors if you misspell a service name or method. You'll find out at runtime. This is the tradeoff for dynamic invocation — there's no free lunch here. For small projects, I think it's worth it, but it's something to consider.&lt;/p&gt;

&lt;p&gt;❌ &lt;strong&gt;Not designed for extremely high throughput&lt;/strong&gt;: It's fast enough for most use cases, but if you're handling thousands of requests per second, the dynamic reflection-based approach probably won't beat compiled code. That said, I've been running it in production with a few hundred RPS and haven't had any issues.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who Should Actually Use This?
&lt;/h2&gt;

&lt;p&gt;After using it for three months, here's my take:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use Capa-BFF if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You're working on a SpringBoot project&lt;/li&gt;
&lt;li&gt;You need a simple BFF layer to let frontend aggregate data&lt;/li&gt;
&lt;li&gt;You don't want to deploy and maintain another service&lt;/li&gt;
&lt;li&gt;Your team is small and you value simplicity over completeness&lt;/li&gt;
&lt;li&gt;You don't need all the bells and whistles of GraphQL&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Don't use Capa-BFF if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You're not using Java/SpringBoot (yet)&lt;/li&gt;
&lt;li&gt;You need strong typing and compile-time checks&lt;/li&gt;
&lt;li&gt;You already have GraphQL and your team is happy with it&lt;/li&gt;
&lt;li&gt;You're building something at massive scale where every microsecond counts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Honestly, I think it fits perfectly for many teams I've worked with. Most teams aren't Google-scale. Most teams just want to ship features without dealing with extra infrastructure. That's where Capa-BFF shines.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Personal Takeaway
&lt;/h2&gt;

&lt;p&gt;Working with Capa-BFF reminded me of something I'd forgotten: good design is about removing complexity, not adding it.&lt;/p&gt;

&lt;p&gt;We're in an era where every new project seems to require adding another layer, another service, another tool to your stack. Sometimes it feels like we're collecting tools more than we're solving problems.&lt;/p&gt;

&lt;p&gt;Capa-BFF takes the opposite approach. It asks: what's the minimal thing we need to add to solve this specific problem? The answer is just a library you drop into your existing app. That's it.&lt;/p&gt;

&lt;p&gt;I learned the hard way that complexity accumulates. Every extra service you add is extra operational load forever. Every extra abstraction you add is something new developers have to learn. Sometimes, the best architecture is the one you don't have to see.&lt;/p&gt;

&lt;p&gt;Would I use it on my next project? Yeah, I probably would. For the use case it targets, it's better than the alternatives I've tried.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Your Take?
&lt;/h2&gt;

&lt;p&gt;I'm always curious to hear about other people's experiences with BFF solutions. Have you tried Capa-BFF? Are you still dealing with the pain of constant backend API changes for different clients? Have you found a different solution that works better for your team?&lt;/p&gt;

&lt;p&gt;I'd love to hear your thoughts in the comments below. Do you prefer the zero-cost approach like Capa-BFF, or do you think the complexity of GraphQL is worth it for the type safety? Let me know!&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Capa-BFF is open source and available on &lt;a href="https://github.com/capa-cloud/capa-bff" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. Go check it out if you're interested — and if you like it, give them a star!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Three Months with Capa-BFF: Can This Zero-Cost BFF Really Fix Your Frontend Pain Points?</title>
      <dc:creator>KevinTen</dc:creator>
      <pubDate>Sun, 28 Jun 2026 10:07:36 +0000</pubDate>
      <link>https://dev.to/kevinten10/three-months-with-capa-bff-can-this-zero-cost-bff-really-fix-your-frontend-pain-points-o82</link>
      <guid>https://dev.to/kevinten10/three-months-with-capa-bff-can-this-zero-cost-bff-really-fix-your-frontend-pain-points-o82</guid>
      <description>&lt;h1&gt;
  
  
  Three Months with Capa-BFF: Can This Zero-Cost BFF Really Fix Your Frontend Pain Points?
&lt;/h1&gt;

&lt;p&gt;Honestly, I didn't have high expectations for another "zero-config" BFF framework before I tried Capa-BFF.&lt;/p&gt;

&lt;p&gt;I've been through enough pain with frontend-backend integration on separated projects, and I know a lot of you have too. The story is always the same: frontend waits for backend to finish APIs, backend says they're busy changing database schemas, then you wait for documentation, then you wait for CORS configuration — by the end of the day, you've spent more time waiting than actually writing code.&lt;/p&gt;

&lt;p&gt;I'd tried a bunch of popular BFF solutions before. Some were so complex it took half a day just to set up the environment. Some had so many bugs I had no clue where to start debugging when something broke. And some were closed-source commercial products that small teams just couldn't afford.&lt;/p&gt;

&lt;p&gt;Honestly, before I found Capa-BFF, I was already planning to just hand-roll a simple version myself. We had deadlines to meet, who had time to mess around with heavy frameworks anyway?&lt;/p&gt;

&lt;h2&gt;
  
  
  What Even Is Capa-BFF, Anyway?
&lt;/h2&gt;

&lt;p&gt;Capa-BFF is an open-source zero-cost BFF solution from the capa-cloud project. Let me put it plainly: it lets frontend teams directly compose backend data sources without backend developers writing custom APIs. You can find it on GitHub here: &lt;a href="https://github.com/capa-cloud/capa-bff" rel="noopener noreferrer"&gt;https://github.com/capa-cloud/capa-bff&lt;/a&gt;. It's completely free and open-source.&lt;/p&gt;

&lt;p&gt;After using it for three months in production, what strikes me most is how &lt;em&gt;lightweight&lt;/em&gt; it is. You don't need to deploy any extra services, you don't need to rewrite your existing architecture — just add the dependency, write a JSON configuration, and you're good to go.&lt;/p&gt;

&lt;p&gt;I think the design philosophy here is actually pretty clever. It doesn't try to replace your existing backend architecture. It just adds a dynamic composition layer on top. Frontend can assemble exactly the data they need for a page, and the backend just needs to expose the data sources once. That's it.&lt;/p&gt;

&lt;p&gt;Let me give you a concrete example so you see how it works. Say you're building a user profile page that needs four pieces of data: basic user info, recent orders, favorite products, and unread messages. In the traditional approach, the backend writes four separate endpoints, the frontend calls all four and aggregates the result on the client. If the product manager changes their mind and wants recent browsing history instead of recent orders, the backend has to change the API again.&lt;/p&gt;

&lt;p&gt;With Capa-BFF? The backend just exposes each data source once. The frontend writes exactly what they need in a JSON config, and gets everything back in one request. Product changes their mind? The frontend just tweaks the config and deploys — no backend involvement required at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let Me Show You Real Working Code
&lt;/h2&gt;

&lt;p&gt;I'm going to drop an actual configuration we use in production right here. You'll see how simple it is:&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"namespace"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user-homepage"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"User homepage data aggregation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"calls"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"baseInfo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"database"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user-db"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SELECT id, username, avatar, email FROM users WHERE id = ${userId}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"params"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"userId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$context.userId"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"recentOrders"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"database"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"order-db"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SELECT id, orderNo, amount, status, createTime FROM orders WHERE userId = ${userId} ORDER BY createTime DESC LIMIT 5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"params"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"userId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$baseInfo.id"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"depends"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"baseInfo"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"unreadCount"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rpc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"service"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"message-service"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"getUnreadCount"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"params"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"userId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$baseInfo.id"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"depends"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"baseInfo"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"result"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$baseInfo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"recentOrders"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$recentOrders"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"unreadCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$unreadCount.count"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="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;That's literally all there is. Let's break down what's happening here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;First, we query the database for the user's basic information&lt;/li&gt;
&lt;li&gt;Then we get the user's 5 most recent orders, which depends on the user ID from the first call&lt;/li&gt;
&lt;li&gt;Then we call an RPC service to get the unread message count, also using the user ID&lt;/li&gt;
&lt;li&gt;Finally, we aggregate everything into a clean response and send it back to the frontend&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All the backend needs to do is pre-register the &lt;code&gt;user-db&lt;/code&gt; data source and the &lt;code&gt;message-service&lt;/code&gt; RPC service. After that, frontend can rearrange things however they want.&lt;/p&gt;

&lt;p&gt;How do you register a data source on the Java side? It's just as simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;cloud.capa.bff.core.annotation.BffDataSource&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;cloud.capa.bff.core.datasource.JdbcDataSource&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.jdbc.core.JdbcTemplate&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.context.annotation.Bean&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.context.annotation.Configuration&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@Configuration&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BffConfig&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Bean&lt;/span&gt;
    &lt;span class="nd"&gt;@BffDataSource&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"user-db"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;JdbcDataSource&lt;/span&gt; &lt;span class="nf"&gt;userJdbcDataSource&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JdbcTemplate&lt;/span&gt; &lt;span class="n"&gt;jdbcTemplate&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;JdbcDataSource&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jdbcTemplate&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Just those few lines, and the data source is registered and ready to go. The frontend can start using it immediately.&lt;/p&gt;

&lt;p&gt;Calling it from the frontend is straightforward too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getUserHomeData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&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;response&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/bff/call/user-homepage&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="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;userId&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;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One request, all the data you need. Clean as a whistle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's Talk About the Dark Side: Pitfalls I Ran Into
&lt;/h2&gt;

&lt;p&gt;Okay, I've talked about the good stuff. Now let's be real — no open-source project is perfect. I've been using this for three months, I've stepped in a few piles of dog poop along the way. Let me save you some trouble.&lt;/p&gt;

&lt;p&gt;First pitfall: &lt;strong&gt;You have to implement your own permission control&lt;/strong&gt;. The framework handles data aggregation, but it doesn't do fine-grained permission checking out of the box. If you need row-level permissions — like a user should only be able to see their own orders — you have to handle that yourself in the SQL or at the data source level. The framework doesn't do it for you.&lt;/p&gt;

&lt;p&gt;I learned this the hard way. When I first set it up, I just dropped the query in there and called it a day. It wasn't until later I realized users could tweak the parameters and see other people's data. Yeah, that scared the crap out of me too. Fixed it immediately with proper filtering. So heads up — with great power comes great responsibility.&lt;/p&gt;

&lt;p&gt;Second pitfall: &lt;strong&gt;Complex queries might not perform as well as handwritten SQL&lt;/strong&gt;. The framework does automatic dependency analysis, topological sorting, parallel calls, and cycle detection — all that stuff works great. But if you're doing a really complex multi-table join, the generated SQL probably won't be as optimized as something you'd write yourself. To be honest though, if you're doing that kind of complex query, you should probably be writing it by hand anyway. BFF isn't meant for that use case in my opinion.&lt;/p&gt;

&lt;p&gt;Third pitfall: &lt;strong&gt;Documentation is incomplete&lt;/strong&gt;. The README on GitHub covers the basics, but more advanced topics like custom data sources, caching strategies, and error handling aren't covered in much detail. When I was implementing my first custom data source, I ended up reading through the source code to figure out how everything hooked together. That said, if you're an experienced developer it's not that big of a deal — but it might be a bit confusing for beginners.&lt;/p&gt;

&lt;p&gt;Fourth pitfall: &lt;strong&gt;It only works with Java/SpringBoot right now&lt;/strong&gt;. If you're running a Go or Python stack, you can't use it today. I heard the team is planning multi-language support, but it's not here yet. So that's something to keep in mind if you're not in the Java ecosystem.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Does It Stack Up Against Other BFF Options?
&lt;/h2&gt;

&lt;p&gt;I've used a few different BFF approaches, so let me give you a quick comparison to help you decide if this is right for you:&lt;/p&gt;

&lt;h3&gt;
  
  
  vs. API Gateway Pattern
&lt;/h3&gt;

&lt;p&gt;API Gateway is definitely mature, but it's heavy to deploy and maintain. It works great for big companies with dedicated teams to operate it, but small teams just don't have the bandwidth for that extra complexity. Capa-BFF embeds right into your existing application, no extra deployment needed, zero extra cost. That's a game-changer for small teams.&lt;/p&gt;

&lt;h3&gt;
  
  
  vs. GraphQL
&lt;/h3&gt;

&lt;p&gt;GraphQL is technically great, but the problem is you have to change how both frontend and backend work, and there's a non-trivial learning curve. Our frontend team wasn't very familiar with GraphQL when we started looking at BFF options, so there was a lot of resistance to adopting it. With Capa-BFF, it's just JSON configuration. Anybody can read it, anybody can edit it, learning curve is basically zero. You can be up and running in minutes.&lt;/p&gt;

&lt;h3&gt;
  
  
  vs. Writing Your Own Custom BFF
&lt;/h3&gt;

&lt;p&gt;Writing it yourself gives you full control, of course. But building something stable takes time. All the little details like dependency analysis, parallel execution, cycle detection — you have to implement all that yourself. Capa-BFF gives you all that out of the box. It saves you a ton of time, and you can always add custom stuff where you need it.&lt;/p&gt;

&lt;h2&gt;
  
  
  So Who Should Actually Use This?
&lt;/h2&gt;

&lt;p&gt;After three months of production use, here's my honest take on who this fits and who it doesn't:&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Go for it if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You're doing frontend-backend separation and deal with frequent API changes&lt;/li&gt;
&lt;li&gt;You're a small team or startup with limited resources and don't want to mess with heavy architecture&lt;/li&gt;
&lt;li&gt;Your frontend team wants more autonomy to compose data themselves and cut down on waiting for backend&lt;/li&gt;
&lt;li&gt;You don't want to rewrite your existing system, you just want to add dynamic aggregation on top&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;❌ &lt;strong&gt;Skip it if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You're working on a huge complex project that already has a mature BFF setup&lt;/li&gt;
&lt;li&gt;You need extreme performance optimization and every millisecond counts&lt;/li&gt;
&lt;li&gt;Your whole stack isn't Java/SpringBoot (it doesn't support other languages yet)&lt;/li&gt;
&lt;li&gt;You need complex fine-grained permission control out of the box and don't want to write it yourself&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Honestly, if you're a small-to-medium team and you're tired of the constant API negotiation ping-pong, you should just try it. It's zero cost, you can integrate it in minutes, and if it doesn't work for you, ripping it out isn't a big deal. No harm done.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Actually Learned After Three Months
&lt;/h2&gt;

&lt;p&gt;The biggest takeaway for me wasn't even the framework itself — it changed how I think about frontend-backend division of labor.&lt;/p&gt;

&lt;p&gt;Before, we did it the traditional way: frontend tells backend what APIs they need, backend writes them. Product changes requirements, APIs change. When backend is busy, frontend just waits. Development flow gets stuck all the time.&lt;/p&gt;

&lt;p&gt;With Capa-BFF, the dynamic changes. Backend exposes the data sources, frontend composes what they need. Product changes their mind? Frontend tweaks the config and deploys. No waiting on backend. Our development speed actually picked up quite a bit.&lt;/p&gt;

&lt;p&gt;I was worried at first that this would push too much backend logic to the frontend and make everything messy. But after using it for a while, it's actually fine. Capa-BFF only handles data aggregation. The real business logic still lives on the backend. The separation of concerns is still pretty clear.&lt;/p&gt;

&lt;p&gt;This approach isn't a silver bullet, of course. It's just another option you can layer on top of your existing architecture. Some scenarios fit perfectly, some don't. There's no such thing as a one-size-fits-all architecture — just what fits your team and your project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's Talk — What Do You Use for BFF?
&lt;/h2&gt;

&lt;p&gt;I'm curious — have you ever dealt with that constant "change the API" pain in frontend-backend separation? Product changes their mind every day, backend is constantly rewriting endpoints, frontend is constantly waiting. What solution do you use these days? Do you write a custom BFF, use GraphQL, or just deal with it like we used to?&lt;/p&gt;

&lt;p&gt;Drop a comment below and share your experience. I'd love to hear what's working for you.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Capa-Java: Three Years Building Hybrid Cloud Java — Why I Still Prefer SDK Over Sidecar</title>
      <dc:creator>KevinTen</dc:creator>
      <pubDate>Sun, 28 Jun 2026 07:04:14 +0000</pubDate>
      <link>https://dev.to/kevinten10/capa-java-three-years-building-hybrid-cloud-java-why-i-still-prefer-sdk-over-sidecar-4iee</link>
      <guid>https://dev.to/kevinten10/capa-java-three-years-building-hybrid-cloud-java-why-i-still-prefer-sdk-over-sidecar-4iee</guid>
      <description>&lt;h1&gt;
  
  
  Capa-Java: Three Years Building Hybrid Cloud Java — Why I Still Prefer SDK Over Sidecar
&lt;/h1&gt;

&lt;p&gt;Let me be honest with you — I fell for the Sidecar hype hard. When Service Mesh was everywhere, I binged every conference talk, read every CNCF blog post, and genuinely believed Sidecar was the One True Way™ to fix all hybrid cloud problems. If you'd told me three years ago I'd be writing this post defending the SDK approach, I'd have laughed at you.&lt;/p&gt;

&lt;p&gt;But here we are. Three years running &lt;strong&gt;Capa-Java&lt;/strong&gt; in production, and I've changed my mind. This isn't some "Sidecar bad, SDK good" rant. Sidecar isn't bad — it's great for the right context. But what I've learned is that there's more than one way to solve the hybrid cloud problem, and SDK-based approaches still have a lot to offer that nobody talks about anymore.&lt;/p&gt;

&lt;p&gt;This is the real story — the messy, honest, not-in-the-marketing-brochure story of what it's like to use Capa-Java every day for three years. The good parts, the bad parts, who it's actually for, and who should probably look elsewhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Ended Here: The Hybrid Cloud Mess Nobody Warned Me About
&lt;/h2&gt;

&lt;p&gt;Let me backtrack to where this all started. Three years ago, my team got a pretty standard business requirement: "We need to run the same app in our private data center (for compliance) and on AWS (for burst capacity). Same codebase, no rewrites."&lt;/p&gt;

&lt;p&gt;Sounds simple enough, right? Famous last words.&lt;/p&gt;

&lt;p&gt;When we actually started digging in, we hit every problem you'd expect. Every cloud has different APIs for everything:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Configuration management? Different API for each cloud.&lt;/li&gt;
&lt;li&gt;Service discovery? Different API for each cloud.&lt;/li&gt;
&lt;li&gt;Message queues? Different API for each cloud.&lt;/li&gt;
&lt;li&gt;Distributed tracing? You guessed it — different API.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you wanted multi-cloud support, your options were basically:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Write a ton of custom adapter code yourself (we tried this once before, it becomes a maintenance nightmare)&lt;/li&gt;
&lt;li&gt;Go all-in on Sidecar/Service Mesh (the trendy "modern" approach everyone was recommending)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We looked hard at the popular Sidecar options. The architecture makes sense in theory — pull all the cross-cutting concerns out into a separate process, your app just talks to localhost. Clean separation, right?&lt;/p&gt;

&lt;p&gt;But when I actually started adding it up for our team, something felt off. We're 4 people. Every single application instance would need a Sidecar container running alongside it. That's more deployments, more monitoring, more things to debug when something breaks. More operational load that we simply didn't have capacity for.&lt;/p&gt;

&lt;p&gt;That's when I stumbled on Capa-Java. It takes the opposite approach: instead of putting all the infrastructure capabilities in a Sidecar, put them in an SDK that runs inside your app process. Same core idea — separate infrastructure concerns from business logic — just different placement.&lt;/p&gt;

&lt;p&gt;I was skeptical at first. "This is just the old way of doing things," I thought. "Everyone knows Sidecar is the modern way." But honestly? We were stuck, we didn't have much to lose, so I gave it a shot. Three years later, I'm still using it every single day.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Capa-Java Actually Does (No Jargon, I Promise)
&lt;/h2&gt;

&lt;p&gt;Let me break this down simply, because the official docs get a bit abstract.&lt;/p&gt;

&lt;p&gt;At its core, Capa-Java gives you &lt;strong&gt;one consistent API for all the common infrastructure stuff you need&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Configuration management&lt;/li&gt;
&lt;li&gt;Service discovery&lt;/li&gt;
&lt;li&gt;RPC calls between services&lt;/li&gt;
&lt;li&gt;Pub/sub messaging&lt;/li&gt;
&lt;li&gt;State management&lt;/li&gt;
&lt;li&gt;You name it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then it uses a plugin system (Java's SPI, if you know what that is) to map this consistent API to whatever cloud you're actually running on. Write your business code once. When you deploy to a different environment, just switch out the plugin. Zero changes to your actual business logic.&lt;/p&gt;

&lt;p&gt;That's the promise. Does it deliver? Yeah, mostly — but there are caveats, which I'll get to. I wouldn't still be here if it didn't work.&lt;/p&gt;

&lt;p&gt;The big difference from Sidecar is that everything runs &lt;em&gt;in your application process&lt;/em&gt;. When you need a configuration value, you call the Capa SDK directly — no network hop to localhost, no extra round trip. That changes everything for both performance and how you operate the system.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Win Isn't Performance — It's Operational Simplicity
&lt;/h2&gt;

&lt;p&gt;Everyone always leads with performance when talking about in-process vs Sidecar, so let's get that out of the way first. Yeah, removing a network hop lowers latency. In our production benchmarks, we saw P99 latency drop 20-30% compared to the Sidecar setup we tested. That's not nothing — especially if you're in fintech or anything latency-sensitive.&lt;/p&gt;

&lt;p&gt;But honestly? Performance isn't why I stayed. The real game-changer was operational simplicity.&lt;/p&gt;

&lt;p&gt;The industry has forgotten this: every extra component you add to your stack has a &lt;em&gt;cognitive cost&lt;/em&gt;. It's not just about CPU or memory — those are cheap. It's about mental space, about things you have to remember to do, about things that can break in production.&lt;/p&gt;

&lt;p&gt;With Sidecar, you have to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deploy Sidecar alongside every single app instance&lt;/li&gt;
&lt;li&gt;Monitor Sidecar separately from your app&lt;/li&gt;
&lt;li&gt;Update Sidecar separately (and coordinate that with your app deployments)&lt;/li&gt;
&lt;li&gt;Debug issues that span both your app AND the Sidecar&lt;/li&gt;
&lt;li&gt;Scale Sidecar when you scale your app&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Again — this isn't rocket science. But it's &lt;em&gt;more stuff&lt;/em&gt;. If you're a big company with a dedicated platform engineering team, that's fine. They get paid to handle that stuff. But if you're a small team where everyone already wears 3-4 hats? That extra operational load becomes a real burden that slows you down every single day.&lt;/p&gt;

&lt;p&gt;With Capa-Java? There are no extra moving parts. The infrastructure capabilities are just part of your app. When you deploy your app, you've already deployed Capa. When you scale your app, Capa scales with it. When you need to update Capa, you just bump the version in your build file and deploy like you normally would.&lt;/p&gt;

&lt;p&gt;That simplicity sounds obvious on paper, but when you're actually living it? It's revolutionary. I spend way less time worrying about infrastructure and way more time building features that actually matter to our users. That's the real win.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's Get Concrete: Code Example Time
&lt;/h2&gt;

&lt;p&gt;Enough abstract architecture talk — let me show you what it actually looks like to use Capa-Java in practice. It's honestly much simpler than you'd think.&lt;/p&gt;

&lt;p&gt;Want to call another service? Here's how you do it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;cloud.capa.api.rpc.CapaRpcClient&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;cloud.capa.api.core.TypeRef&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;reactor.core.publisher.Mono&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Build the client (does all the auto-configuration via Capa's bootstrap)&lt;/span&gt;
&lt;span class="nc"&gt;CapaRpcClient&lt;/span&gt; &lt;span class="n"&gt;client&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;CapaRpcClientBuilder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Call another service — this code is EXACTLY the same whether you're running&lt;/span&gt;
&lt;span class="c1"&gt;// on AWS, Alibaba Cloud, or your own private data center.&lt;/span&gt;
&lt;span class="nc"&gt;Mono&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;invokeMethod&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"order-processing-service"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Service name (service discovery handles the rest)&lt;/span&gt;
    &lt;span class="s"&gt;"createNewOrder"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;             &lt;span class="c1"&gt;// Method name you're calling&lt;/span&gt;
    &lt;span class="n"&gt;orderRequest&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;                 &lt;span class="c1"&gt;// Your request object&lt;/span&gt;
    &lt;span class="nc"&gt;HttpExtension&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;POST&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;           &lt;span class="c1"&gt;// HTTP method (though underlying transport can change)&lt;/span&gt;
    &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;                         &lt;span class="c1"&gt;// Extra options&lt;/span&gt;
    &lt;span class="nc"&gt;TypeRef&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;STRING&lt;/span&gt;                &lt;span class="c1"&gt;// Response type&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Subscribe and handle the response&lt;/span&gt;
&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;subscribe&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Order created: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. No environment-specific conditionals. No different clients for different clouds. Just code that does what you need it to do.&lt;/p&gt;

&lt;p&gt;What about getting configuration? Same simplicity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;cloud.capa.api.config.ConfigurationClient&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;cloud.capa.api.config.Configuration&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;reactor.core.publisher.Mono&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;ConfigurationClient&lt;/span&gt; &lt;span class="n"&gt;configClient&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;ConfigurationClientBuilder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Get configuration — again, exactly the same code everywhere.&lt;/span&gt;
&lt;span class="c1"&gt;// Under the hood, it talks to whatever config system your plugin targets.&lt;/span&gt;
&lt;span class="nc"&gt;Mono&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Configuration&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;configClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getConfiguration&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"payment-service"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;subscribe&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getValue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"timeout"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Current timeout: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The beauty here is that the plugin handles all the cloud-specific stuff. Your business code never has to care what cloud it's running on. That's the promise, and that's what actually works in practice.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Ugly Truth: Trade-Offs Nobody Mentions
&lt;/h2&gt;

&lt;p&gt;Okay, I've been positive so far, but I need to be real with you — this approach isn't for everyone. There are genuine trade-offs, and if you ignore them, you're going to have a bad time. I learned this the hard way, so let me save you the pain.&lt;/p&gt;

&lt;p&gt;First off, &lt;strong&gt;it's Java-first, so polyglot architectures don't get much benefit&lt;/strong&gt;. Capa is really built for Java teams. If your organization has a true polyglot setup — some Java, some Go, some Node.js, some Python — you're going to need different SDKs for every language. With Sidecar, it doesn't matter what language your app uses — the Sidecar just works. That's a huge advantage for polyglot environments, and I can't argue with that.&lt;/p&gt;

&lt;p&gt;Second, &lt;strong&gt;version upgrades happen per-application&lt;/strong&gt;. Because Capa is linked directly into your app, every service has to upgrade on its own schedule. If you want every service on the same Capa version, you have to actually upgrade every service. With Sidecar, you can upgrade the Sidecar independently of the applications — way easier to get everyone on the same page. It's a trade-off: do you want granular upgrade flexibility (SDK) or centralized control (Sidecar)? Depends on how your organization works.&lt;/p&gt;

&lt;p&gt;Third, &lt;strong&gt;the ecosystem is smaller&lt;/strong&gt;. Let's be totally clear here: Dapr has Microsoft backing it, it's in CNCF, it has a massive community, it supports dozens of components out of the box. Capa is a community-driven project with a small core team. The number of pre-built plugins is smaller. If you need support for some super obscure cloud service, you might have to write the plugin yourself. That's just reality. It's open source, you can do it — but it's not something that's already done for you.&lt;/p&gt;

&lt;p&gt;Fourth, &lt;strong&gt;it's still in-process, which means it shares resources with your app&lt;/strong&gt;. If Capa has a memory leak (it doesn't, in my experience — but hypothetically), it takes your whole app down with it. With Sidecar, a leak in the Sidecar doesn't necessarily take your app down. That's another trade-off. Isolation vs extra complexity.&lt;/p&gt;

&lt;p&gt;Fifth, &lt;strong&gt;it's not trying to be everything to everyone&lt;/strong&gt;. Capa doesn't do all the fancy Service Mesh stuff like mTLS between services out of the box, traffic splitting, canary deployments, all that. It focuses on the core hybrid cloud portability problem, and that's it. If you need all that other Service Mesh functionality, you're probably better off with a full Sidecar approach.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pros and Cons: Honest Assessment After Three Years
&lt;/h2&gt;

&lt;p&gt;Let me sum this up clearly, because I hate when people dance around this stuff. If you're trying to decide whether to try Capa-Java, here's what you need to know:&lt;/p&gt;

&lt;h3&gt;
  
  
  ✅ What I Think Capa-Java Does Really Well (Pros)
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Unbeatable operational simplicity for small Java teams&lt;/strong&gt; — If you don't have a huge platform team, this is a game-changer. Less moving parts, less to go wrong, less to maintain. I can't overstate how nice this is.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Better performance for latency-sensitive workloads&lt;/strong&gt; — No extra network hop, no extra serialization/deserialization. 20-30% lower P99 latency in our measurements. That matters for some use cases, doesn't for others — know your context.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;True write-once deploy-anywhere for Java&lt;/strong&gt; — It actually delivers on the promise. We deploy the exact same code to private data center and AWS, and it just works. No changes, no hacks, no surprises. Cross-cloud deployment went from 3-5 days to minutes. That's not an exaggeration — that's what actually happened to us.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Proven architecture pattern that's stood the test of time&lt;/strong&gt; — It's just the classic standard API + SPI plugin pattern that Java has used for decades. Nothing fancy, nothing experimental, just solid engineering that works.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Zero extra infrastructure to manage&lt;/strong&gt; — You don't need to operate a control plane, you don't need to manage Sidecar deployments, you don't need any of that. Just add the dependency to your Maven/Gradle build and go.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  ❌ Where Capa-Java Falls Short (Cons)
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Java-only (mostly)&lt;/strong&gt; — If you're not in a Java shop, this isn't for you. They have some experimental support for other languages, but it's not first-class. Don't bother if you're Go/Python/Node.js shop.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Smaller ecosystem&lt;/strong&gt; — Fewer pre-built plugins than the bigger projects. If you need something niche, you'll probably have to write it yourself.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No built-in Service Mesh goodies&lt;/strong&gt; — If you want canary deployments, traffic splitting, mTLS, all that out of the box, this isn't that project. It focuses on portability, not all the other Service Mesh features.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Per-app version upgrades&lt;/strong&gt; — If you want centralized infrastructure upgrades independent of applications, this approach won't fit your workflow.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Smaller community&lt;/strong&gt; — It's not as popular as Dapr or Istio, so you won't find as many blog posts, tutorials, or people who already know it on your team.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Who Should Actually Use Capa-Java?
&lt;/h2&gt;

&lt;p&gt;After three years of production use, I've got pretty clear thoughts on when Capa makes sense, and when it doesn't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Go for Capa-Java if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You're building Java applications and need to run the same code across multiple clouds/environments&lt;/li&gt;
&lt;li&gt;You're a small-to-medium team without a huge dedicated platform engineering department&lt;/li&gt;
&lt;li&gt;You care about latency and want to avoid unnecessary network hops&lt;/li&gt;
&lt;li&gt;You want operational simplicity more than you need every possible bell and whistle&lt;/li&gt;
&lt;li&gt;You're already a Java shop and want to stay a Java shop — no need to re-architect everything&lt;/li&gt;
&lt;li&gt;You just want something that works without having to operate a whole bunch of extra infrastructure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Look elsewhere if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You have a true polyglot architecture with multiple different languages&lt;/li&gt;
&lt;li&gt;You have a large platform team that can handle the operational overhead of Sidecar&lt;/li&gt;
&lt;li&gt;You need a huge ecosystem of pre-built integrations for every possible cloud service&lt;/li&gt;
&lt;li&gt;You want centralized independent upgrades of your infrastructure layer&lt;/li&gt;
&lt;li&gt;You're not using Java — this project is really built for Java first&lt;/li&gt;
&lt;li&gt;You need all the advanced Service Mesh features like canary deployments and traffic splitting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The mistake I see people making all the time is they just pick whatever is trendy. "Sidecar is modern, so I have to use Sidecar." But architecture isn't about following trends — it's about picking the right tool for &lt;em&gt;your specific context&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;What works for a FAANG-scale organization with hundreds of engineers doesn't work for a 5-person startup. What works for a polyglot shop with 20 services in 10 different languages doesn't work for a Java shop that wants to stay Java. Context is everything.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I've Learned After Three Years
&lt;/h2&gt;

&lt;p&gt;The biggest lesson I take away from this experience isn't even about Capa specifically — it's a bigger lesson about the tech industry in general.&lt;/p&gt;

&lt;p&gt;We love to declare old approaches "bad" and new approaches "the only correct way" when the reality is usually more nuanced. Sidecar didn't make SDK-based approaches obsolete — it just carved out its own niche. Both approaches solve the same problem in different ways, and both have their place.&lt;/p&gt;

&lt;p&gt;The other big lesson? &lt;strong&gt;The best architecture is the one that gets out of your way and lets you build things.&lt;/strong&gt; I don't care how "modern" or "cool" your architecture is — if it's slowing you down, if it's adding more problems than it solves, it's not actually good architecture.&lt;/p&gt;

&lt;p&gt;Capa-Java doesn't try to be everything to everyone. It solves one specific problem really well: it helps Java teams build hybrid cloud applications without all the extra operational complexity that comes with Sidecar. That's it. And that's enough.&lt;/p&gt;

&lt;p&gt;I still think Sidecar is the right choice for a lot of organizations. But I also think the industry has kind of forgotten that simpler approaches can work really well in the right context. Sometimes, putting the capabilities in-process is just... better. Not for everyone, not always, but sometimes — and when it's better, it's &lt;em&gt;much&lt;/em&gt; better.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing Thoughts and A Question For You
&lt;/h2&gt;

&lt;p&gt;I wanted to write this because I don't see many people talking about the SDK-based approach these days. Everybody's talking about Sidecar, everybody's talking about Service Mesh, but projects like Capa-Java just work for a lot of teams and don't get much attention.&lt;/p&gt;

&lt;p&gt;I'm not telling you to drop everything and switch tomorrow. What I am telling you is: think about your own context before you just follow the hype. Maybe the simpler approach is actually the better approach for &lt;em&gt;your&lt;/em&gt; team.&lt;/p&gt;

&lt;p&gt;After three years, I'm still glad we went with Capa-Java. It's not perfect — nothing is — but it solved the problem we needed solved, it stays out of my way, and it lets me focus on building features instead of managing infrastructure. That's all I really want from my infrastructure tools, honestly.&lt;/p&gt;

&lt;p&gt;Now I want to hear from you: &lt;strong&gt;What's your experience with hybrid cloud architectures? Have you tried both Sidecar and SDK-based approaches? Which one worked better for your team, and why? I'm genuinely curious to hear different perspectives — there's no one right answer for everyone.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you want to check out Capa-Java and try it yourself, here's the GitHub repo:&lt;br&gt;
&lt;a href="https://github.com/capa-cloud/capa-java" rel="noopener noreferrer"&gt;https://github.com/capa-cloud/capa-java&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Go give it a star if you think this kind of approach deserves more attention!&lt;/p&gt;

&lt;h1&gt;
  
  
  java #hybridcloud #cloudnative #architecture #opensource
&lt;/h1&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Capa-Java: Why Sidecar Isn't Always the Answer for Hybrid Cloud Java</title>
      <dc:creator>KevinTen</dc:creator>
      <pubDate>Sat, 27 Jun 2026 22:11:29 +0000</pubDate>
      <link>https://dev.to/kevinten10/capa-java-why-sidecar-isnt-always-the-answer-for-hybrid-cloud-java-1jif</link>
      <guid>https://dev.to/kevinten10/capa-java-why-sidecar-isnt-always-the-answer-for-hybrid-cloud-java-1jif</guid>
      <description>&lt;h1&gt;
  
  
  Capa-Java: Why Sidecar Isn't Always the Answer for Hybrid Cloud Java
&lt;/h1&gt;

&lt;p&gt;Honestly, I've been doing Java backend development for over a decade, and when I first heard about "hybrid cloud architecture," I'll admit I was pretty skeptical. It sounded great in conference talks — "write once, run anywhere," "multi-cloud strategy reduces cost," all that buzzwords. But when you actually sit down to write the code? Man, it's a mess. Every cloud provider has their own API, config centers don't talk to each other, message queues need adapters, and deploying to a new cloud means rewriting half your code. Sound familiar?&lt;/p&gt;

&lt;p&gt;Today I want to talk about an open source project I've been using in production for three years now — &lt;strong&gt;Capa-Java&lt;/strong&gt;, a multi-runtime SDK for hybrid cloud built on the Mecha architecture. I've got three years of real production mileage on this thing, so I can give you the real deal — pros, cons, code examples, everything. If you're working on hybrid cloud stuff right now, maybe this saves you some of the headaches I went through.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is Capa-Java, And What Problem Does It Solve?
&lt;/h2&gt;

&lt;p&gt;Let me start simple. Capa-Java is a multi-runtime SDK specifically built for hybrid cloud scenarios. The core idea is &lt;strong&gt;"Unified API, decoupled infrastructure"&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In plain English: You write your business code once using Capa's API, and you don't need to care if you're running on Alibaba Cloud, AWS, Tencent Cloud, whatever. Capa handles the infrastructure adaptation under the hood through SPI plugins. Change cloud providers? You don't touch your business logic — just swap the plugin and you're done.&lt;/p&gt;

&lt;p&gt;Sounds familiar, right? This isn't exactly a new idea. Service Mesh, Dapr, Layotto — all these projects are trying to decouple applications from infrastructure. So what's different about Capa compared to Dapr?&lt;/p&gt;

&lt;p&gt;I couldn't really see the difference at first either, but after using it for a while, it clicked:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dapr/Layotto&lt;/strong&gt; go with the Sidecar pattern — your app talks to a local sidecar over the network&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Capa-Java&lt;/strong&gt; puts everything right in the SDK — in-process calls, no extra network hops&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's really it. If you're into the Sidecar approach, Dapr is great. If you don't want the extra network hop and you want something lightweight, Capa's SDK approach fits really well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code Example: Let's See How It Actually Works
&lt;/h2&gt;

&lt;p&gt;Enough talking — let's look at some actual code. It's honestly way simpler than you might think.&lt;/p&gt;

&lt;p&gt;Here's how you make an RPC call. Doesn't matter if the underlying RPC is Dubbo, REST, or whatever the cloud provider uses — it's all the same API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Build the RPC client once — your whole app only needs one instance&lt;/span&gt;
&lt;span class="nc"&gt;CapaRpcClient&lt;/span&gt; &lt;span class="n"&gt;client&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;CapaRpcClientBuilder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Invoke the remote method — automatically adapts to the underlying cloud platform&lt;/span&gt;
&lt;span class="nc"&gt;Mono&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;invokeMethod&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"order-service"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Target service ID&lt;/span&gt;
    &lt;span class="s"&gt;"createOrder"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;     &lt;span class="c1"&gt;// Method name&lt;/span&gt;
    &lt;span class="n"&gt;orderRequest&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;// Request payload&lt;/span&gt;
    &lt;span class="nc"&gt;HttpExtension&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;POST&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="nc"&gt;TypeRef&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;STRING&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Get the response&lt;/span&gt;
&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;block&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pretty straightforward, right? Now what about configuration? Same thing — whether you're using Nacos, AWS Parameter Store, or Alibaba Cloud Config Center, the code doesn't change:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Subscribe to configuration changes — automatically adapts to the underlying config center&lt;/span&gt;
&lt;span class="nc"&gt;ConfigurationClient&lt;/span&gt; &lt;span class="n"&gt;client&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;ConfigurationClientBuilder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Get the current configuration&lt;/span&gt;
&lt;span class="nc"&gt;Mono&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Configuration&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getConfiguration&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"app.config"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;subscribe&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Config updated: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getValue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"timeout"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;span class="o"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See the pattern? Unified API across all infrastructure providers. I remember when I first used this, I was honestly shocked how simple it was. All that complexity just disappears from your business code.&lt;/p&gt;

&lt;p&gt;Pub/Sub works the same way with a consistent API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Create the Pub/Sub client&lt;/span&gt;
&lt;span class="nc"&gt;PubSubClient&lt;/span&gt; &lt;span class="n"&gt;pubSubClient&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;PubSubClientBuilder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Publish a message&lt;/span&gt;
&lt;span class="n"&gt;pubSubClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;publishMessage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"order-events"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Topic name&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PubSubMessage&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;setText&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"order created"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;subscribe&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Subscribe to messages&lt;/span&gt;
&lt;span class="n"&gt;pubSubClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;subscribe&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"order-events"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Received: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getText&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;AckStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;SUCCESS&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Three different infrastructure components, all using the same consistent API style. You don't have to learn a new API for every new cloud provider. Your muscle memory just works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three Years In: My Honest Takeaway
&lt;/h2&gt;

&lt;p&gt;Okay, I've talked about the good stuff — now let's get real. I've been using this every day for three years, from the early open source days to now. I know the good, the bad, and the ugly.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Good: It Actually Solves Real Problems
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. "Write Once, Run Anywhere" — It Actually Works&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I'm not kidding here. My team has a core business system that runs simultaneously on our private cloud and AWS. The business code is &lt;em&gt;exactly the same&lt;/em&gt; — we don't change a single line. We just swap the SPI plugins when deploying to different clouds and that's it.&lt;/p&gt;

&lt;p&gt;Before Capa, deploying to a new cloud used to take us 3-5 days of code changes. Now it takes literally minutes. That's not a "nice to have" — that's a game-changer for productivity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. No Network Overhead — Performance Is Great&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Since everything runs in-process with the SDK approach, you don't have that extra network hop to a Sidecar. We ran load tests and saw P99 latency drop by 20-30% compared to a Sidecar setup. For latency-sensitive applications, that's a big deal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Java Ecosystem Friendly — Low Intrusion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you're on the Java stack, especially with Spring Boot, getting started with Capa is stupid simple. Just add the starter dependency, configure your plugins, and you're good to go. You don't need to refactor your entire existing architecture. The intrusion is really low. Let's be real — nobody wants to rewrite their whole app just to adopt a new SDK, right?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. SPI Plugin System Is Actually Flexible&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Capa makes every cloud provider adaptation an SPI plugin. You only include the plugins you actually need — you don't get a ton of unnecessary dependencies pulled in. If you're targeting a cloud provider that isn't supported out of the box, writing your own plugin isn't hard. The interfaces are really well-defined.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Bad: It's Not For Everyone, And That's Okay
&lt;/h3&gt;

&lt;p&gt;So here's the thing — I learned this the hard way: No tool solves every problem. Capa has its flaws, and you need to know about them before you jump in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. The Ecosystem Isn't As Mature As Dapr&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let's be honest. Dapr is a CNCF project backed by Microsoft. It has a huge community and supports a ton of components out of the box. Capa is a community-driven open source project with a small core team. So the number of supported cloud providers and components is definitely smaller. The major ones (AWS, Alibaba Cloud, Tencent Cloud) all work great, but if you need something really niche, you'll probably have to write the adapter yourself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. The Documentation Is A Bit Light&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I have to call this out — the getting started docs are great, you can be up and running in 10 minutes. But if you run into deeper issues, like performance tuning or custom plugin development, the docs don't have a lot of detail. You end up having to read the source code. The good news is the source code is actually pretty clean and well-organized, so it's not that bad — but it does take time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Chinese-First Community — English Resources Are Scarce&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Most of the maintainers are Chinese developers, so the primary documentation is in Chinese. The English docs exist but they don't get updated as often. If you're on an international team, that could be a problem. But if you're based in China or your team doesn't mind reading Chinese docs, it's totally fine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. SDK Pattern Means You Manage Versions Per App&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is just a fundamental trade-off between SDK and Sidecar. With Sidecar, you can upgrade once everywhere. With SDK, every application has to upgrade its own dependency. That's more work, but again — it's the trade-off you make for no network hop. Worth it for many teams, but not for everyone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who Should Use This? Who Should Skip It?
&lt;/h2&gt;

&lt;p&gt;After three years of using this in production, I've got pretty clear rules of thumb for whether Capa is a good fit for you:&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Go for it if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You actually need hybrid/multi-cloud deployment — same code running on multiple clouds&lt;/li&gt;
&lt;li&gt;You care about latency and want to avoid extra network hops&lt;/li&gt;
&lt;li&gt;You're on the Java stack and don't want to deal with the operational complexity of Sidecar&lt;/li&gt;
&lt;li&gt;You're a smaller team without the bandwidth to maintain a whole Sidecar infrastructure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;❌ &lt;strong&gt;Probably skip it if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You're only running on one cloud — you don't need this abstraction at all&lt;/li&gt;
&lt;li&gt;You're already all-in on Dapr/Sidecar and it's working for you — no reason to switch&lt;/li&gt;
&lt;li&gt;You need a huge ecosystem of pre-built components — Dapr has you covered there&lt;/li&gt;
&lt;li&gt;You're not on Java — Capa is primarily Java-focused, other language support is limited&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Some Architecture Thoughts I Picked Up Along The Way
&lt;/h2&gt;

&lt;p&gt;One of the things I appreciate about Capa is its design philosophy. There are a few ideas here that've really stuck with me after using it for three years.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Mecha Architecture: An Evolution From Service Mesh
&lt;/h3&gt;

&lt;p&gt;Capa's Mecha architecture is basically an evolution of the Service Mesh idea. Service Mesh pulls capabilities out into a Sidecar, Mecha pulls capabilities down into the SDK. There's no right or wrong here — it's just different choices for different scenarios.&lt;/p&gt;

&lt;p&gt;What I find interesting is that this idea actually lines up pretty well with what's happening in AI right now with Mixture of Experts (MoE) — split capabilities into separate components, load what you need, don't load what you don't. Keep it clean.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Standard API + SPI Plugins: A Timeless Combination
&lt;/h3&gt;

&lt;p&gt;This design pattern has been around forever, but it's still one of the best. The standard API keeps your business code stable, the SPI plugins give you extensibility. No matter how the underlying infrastructure changes, your business code doesn't need to move.&lt;/p&gt;

&lt;p&gt;I've come to believe that good architecture isn't about being able to handle every possible change up front. It's about isolating the change points — when something changes, only that something changes. Everything else stays the same. Capa nails this.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Perfect Is The Enemy Of Good
&lt;/h3&gt;

&lt;p&gt;Can Capa really abstract away 100% of the differences between cloud providers? No, of course not. Different cloud providers have different capabilities, and that's just reality. But here's what Capa does do — it abstracts away 80% of the common stuff that you would've had to write yourself. That 80% saves you 80% of your time. The remaining 20% was always going to be cloud-specific anyway.&lt;/p&gt;

&lt;p&gt;That's just good pragmatism. You don't need theoretical perfection. You need something that solves most people's most common problems. Capa does that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up: My Advice For Anyone Considering This
&lt;/h2&gt;

&lt;p&gt;Honestly, after three years, I'm still really grateful for this project. It solved a real problem my team was having and saved us countless hours of work.&lt;/p&gt;

&lt;p&gt;If you're currently drowning in hybrid cloud adapter code, and you're on the Java stack, you should definitely check Capa-Java out. I think it'll help you. Here's where to find it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/capa-cloud/capa-java" rel="noopener noreferrer"&gt;https://github.com/capa-cloud/capa-java&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Website:&lt;/strong&gt; &lt;a href="https://capa.rxcloud.group/" rel="noopener noreferrer"&gt;https://capa.rxcloud.group/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before you dive in though, let me leave you with a couple of pieces of advice I wish someone had told me three years ago:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Do you actually need hybrid cloud?&lt;/strong&gt; So many companies go "multi-cloud strategy" because it sounds good in board meetings, but they don't actually need it. Don't add complexity to your architecture just for the sake of it. If you're only running on one cloud, you don't need this.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;SDK vs Sidecar — pick based on your team size.&lt;/strong&gt; Big team with dedicated ops people? Sidecar probably makes sense. Smaller team that wants to keep things simple and lightweight? Go SDK. That's really it. No need to overcomplicate the decision.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Don't pick technology for the cool factor.&lt;/strong&gt; Capa is great, but it's not the right choice for every situation. Be honest about your team's capabilities and your actual requirements.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I know hybrid cloud can be a real pain — I've been there. Hopefully this honest review helps you make a better decision than I did when I started.&lt;/p&gt;

&lt;p&gt;What about you? Have you tried Capa-Java? Or are you still struggling with hybrid cloud adaptation madness? What's your go-to approach — SDK or Sidecar? I'd love to hear your experiences in the comments.&lt;/p&gt;

</description>
      <category>java</category>
      <category>opensource</category>
      <category>cloudnative</category>
    </item>
    <item>
      <title>Capa-Java: Why Sidecar Isn't Always the Answer for Hybrid Cloud</title>
      <dc:creator>KevinTen</dc:creator>
      <pubDate>Sat, 27 Jun 2026 19:04:46 +0000</pubDate>
      <link>https://dev.to/kevinten10/capa-java-why-sidecar-isnt-always-the-answer-for-hybrid-cloud-2lbp</link>
      <guid>https://dev.to/kevinten10/capa-java-why-sidecar-isnt-always-the-answer-for-hybrid-cloud-2lbp</guid>
      <description>&lt;h1&gt;
  
  
  Capa-Java: Why Sidecar Isn't Always the Answer for Hybrid Cloud
&lt;/h1&gt;

&lt;p&gt;I want to start this with a confession — I used to be a huge Sidecar architecture fanboy. When Service Mesh became the hottest thing in cloud native, I read every blog post, watched every conference talk, and genuinely believed that Sidecar was the One True Way™ to solve all hybrid cloud problems.&lt;/p&gt;

&lt;p&gt;Three years later, I've changed my mind. Not because Sidecar is bad — it's not. But because I've been working with &lt;strong&gt;Capa-Java&lt;/strong&gt; every single day in production, and I've come to realize something important: there's more than one way to solve the hybrid cloud problem, and SDK-based approaches still have a lot to offer.&lt;/p&gt;

&lt;p&gt;This isn't a "Sidecar bad, Capa good" take. It's a story about what I've learned from three years of production use, why we made the choices we made, and when you should consider going the SDK route instead of the Sidecar route.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Got Here: The Hybrid Cloud Pain That Started It All
&lt;/h2&gt;

&lt;p&gt;Let me backtrack. Three years ago, my team was in that all-too-familiar spot: we needed to run the same application on both our private data center and a public cloud. Business requirements were clear — "we need to be able to deploy the same codebase anywhere, no rewrites."&lt;/p&gt;

&lt;p&gt;Sounds simple enough, right? But when we actually started digging into it, things got messy quickly.&lt;/p&gt;

&lt;p&gt;Every cloud provider has different APIs for configuration management. Different APIs for service discovery. Different APIs for message queues. Different APIs for distributed tracing. If you wanted to support multiple clouds, you either had to write a ton of adapter code yourself, or you went with a Service Mesh approach.&lt;/p&gt;

&lt;p&gt;We looked at the popular Sidecar-based options. The architecture made sense — extract all the cross-cutting concerns into a separate process, your app just talks to localhost. But something felt off to us. We're a small team, and we'd have to operate these Sidecar containers on every single instance. That's more moving parts, more network hops, more things that can break.&lt;/p&gt;

&lt;p&gt;Then I found Capa-Java. It took the opposite approach: instead of putting all the capabilities in a Sidecar, put them in an SDK that runs inside your application process. Same idea of separating capabilities from business logic, but different architectural placement.&lt;/p&gt;

&lt;p&gt;I was skeptical at first. "That's just the old way of doing things," I thought. "Everybody knows Sidecar is the modern approach." But I tried it anyway, because honestly, we didn't have much to lose. Three years later, I'm still using it every day.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Capa-Java Actually Does (In Plain English)
&lt;/h2&gt;

&lt;p&gt;Let me explain what Capa-Java does in simple terms, because the project documentation can get a bit abstract.&lt;/p&gt;

&lt;p&gt;At its core, Capa-Java gives you &lt;strong&gt;a single, consistent API for all the common infrastructure capabilities you need&lt;/strong&gt; — configuration, service discovery, RPC, pub/sub, state management, you name it. Then, it uses a plugin system (SPI, if you're familiar with the term) to map that consistent API to whatever cloud provider you're actually running on.&lt;/p&gt;

&lt;p&gt;So whether you're running on Alibaba Cloud, AWS, Tencent Cloud, or your own private data center, you write your business code &lt;strong&gt;once&lt;/strong&gt; using the Capa API. When you deploy to a different environment, you just switch out the plugin — zero changes to your business logic.&lt;/p&gt;

&lt;p&gt;That's the promise, anyway. Does it actually deliver on that promise? Yes — but with some caveats, which I'll get to later.&lt;/p&gt;

&lt;p&gt;The key difference from Sidecar approaches is that everything runs in-process. When your application needs a configuration value, it calls the Capa SDK directly — no network call to localhost, no extra hop. That has implications for both performance and operations, which I want to dive into.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Performance Argument: It's Not Just About Latency
&lt;/h2&gt;

&lt;p&gt;People always bring up performance first when talking about in-process vs Sidecar, so let's get that out of the way. Yes, removing a network hop does lower latency. In our load tests, we saw P99 latency drop by 20-30% compared to the Sidecar setup we were benchmarking. That's not nothing — especially if you're working on something latency-sensitive like financial services or high-throughput APIs.&lt;/p&gt;

&lt;p&gt;But here's what nobody talks about: it's not just the extra hop that adds latency. It's all the serialization/deserialization that has to happen. Your application puts together a request object, serializes it to send to the Sidecar, Sidecar deserializes it, does its work, serializes the response, sends it back, your app deserializes again. That's a lot of extra work that just disappears when everything is in-process.&lt;/p&gt;

&lt;p&gt;But performance isn't why I stayed with Capa. The real win for us was operational simplicity.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Operational Simplicity That Changed Everything
&lt;/h2&gt;

&lt;p&gt;I think the industry has forgotten that every additional component you add to your stack has a cost. Not just a cost in resources — that's relatively cheap. A cost in &lt;strong&gt;operational cognitive load&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When you go the Sidecar route, you have to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deploy the Sidecar alongside every single application instance&lt;/li&gt;
&lt;li&gt;Monitor the Sidecar separately&lt;/li&gt;
&lt;li&gt;Update the Sidecar separately (and coordinate that with your application deployments)&lt;/li&gt;
&lt;li&gt;Debug problems that span both your application and the Sidecar&lt;/li&gt;
&lt;li&gt;Scale the Sidecar along with your application&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's not rocket science, but it's &lt;strong&gt;more stuff to worry about&lt;/strong&gt;. If you're a large team with dedicated platform engineering folks, that's fine. They can handle that operational load. But if you're a small team like ours, where everybody is already wearing multiple hats? That extra operational load becomes a real burden.&lt;/p&gt;

&lt;p&gt;With Capa-Java, there's no extra moving parts. The capabilities are just part of your application. When you deploy your app, you've already deployed Capa. When you scale your app, Capa scales with it. When you need to update Capa, you just update the dependency in your build file and deploy a new version of your app like you normally would.&lt;/p&gt;

&lt;p&gt;It sounds simple, but that simplicity is actually revolutionary when you're living it. I spend way less time worrying about infrastructure, and more time actually building features that matter to our users.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Trade-Offs Nobody Talks About
&lt;/h2&gt;

&lt;p&gt;Okay, I've been positive so far, but I need to be honest — this approach isn't perfect. It's not for everybody. There are real trade-offs you need to consider, and I want to lay them out clearly.&lt;/p&gt;

&lt;p&gt;First, &lt;strong&gt;polyglot architectures don't really benefit here&lt;/strong&gt;. Capa is primarily for Java. If your organization has multiple services written in different languages — some Java, some Go, some Node.js — then you're going to need different SDKs for different languages. With a Sidecar approach, it doesn't matter what language your app is written in — the Sidecar just works. That's a real advantage for polyglot environments.&lt;/p&gt;

&lt;p&gt;Second, &lt;strong&gt;version upgrades have to happen per-application&lt;/strong&gt;. Because Capa is linked into your application, every application has to upgrade to the latest Capa version on its own schedule. With Sidecar, you can upgrade the Sidecar independently of the application — easier to get everyone on the same version. That's another trade-off: granular upgrade flexibility vs centralized version control. Which one you prefer depends on your organization's culture.&lt;/p&gt;

&lt;p&gt;Third, &lt;strong&gt;the ecosystem is smaller&lt;/strong&gt;. Let's be clear — Dapr has Microsoft behind it, it's in CNCF, it has a huge community, it supports dozens of different components. Capa is a community-driven project with a small core team. The number of components and plugins is smaller. The documentation isn't as extensive. If you need something really obscure, you might have to write the plugin yourself. That's just reality.&lt;/p&gt;

&lt;p&gt;Fourth, &lt;strong&gt;it's still Java&lt;/strong&gt;. Wait, what do I mean by that? I mean that if you've already decided to get off the Java ship because you want something lighter, faster, simpler — Capa-Java isn't going to change your mind. It's designed for Java teams that are already working in Java and want to stay in Java. It doesn't try to be everything to everyone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pros &amp;amp; Cons: The Honest Breakdown
&lt;/h2&gt;

&lt;p&gt;Let me summarize this into a clean Pros &amp;amp; Cons list because I know that's what everybody actually scrolls down to read anyway. I learned the hard way that people appreciate straight talk, so here we go:&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;True write-once deploy-anywhere&lt;/strong&gt;: Same business code runs on any cloud/environment, just swap plugins&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Lower latency&lt;/strong&gt;: No extra network hop, no repeated serialization — P99 improves 20-30% in our tests&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Operational simplicity&lt;/strong&gt;: No extra containers to deploy, monitor, or debug — just your app&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Proven Java pattern&lt;/strong&gt;: Standard API + SPI plugins that've worked for decades, applied consistently&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Zero changes to your deployment process&lt;/strong&gt;: Just build your Java app like you normally would&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Open source with active development&lt;/strong&gt;: Been in production for 3+ years and still going strong&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;❌ &lt;strong&gt;Java-only&lt;/strong&gt;: Really only works for Java teams (though other language support is in the works)&lt;/li&gt;
&lt;li&gt;❌ &lt;strong&gt;Smaller ecosystem&lt;/strong&gt;: Fewer pre-built plugins compared to the big players like Dapr&lt;/li&gt;
&lt;li&gt;❌ &lt;strong&gt;Per-application upgrades&lt;/strong&gt;: No centralized Sidecar upgrades — each app upgrades on its own&lt;/li&gt;
&lt;li&gt;❌ &lt;strong&gt;Not ideal for polyglot&lt;/strong&gt;: Sidecar is better if you have many different languages in your architecture&lt;/li&gt;
&lt;li&gt;❌ &lt;strong&gt;Small team project&lt;/strong&gt;: No big company backing — it's built by developers, for developers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There you go — straight from the heart. No marketing fluff, just what you can actually expect.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the Design Actually Works: The "Mecha" Architecture
&lt;/h2&gt;

&lt;p&gt;Capa-Java is built on what they call the &lt;strong&gt;Mecha architecture&lt;/strong&gt;, which is really just Service Mesh ideas reimagined for in-process. Instead of moving capabilities to out-of-process Sidecars, you move them into the SDK layer. Your business code depends only on the Capa API, not on any specific cloud provider's implementation.&lt;/p&gt;

&lt;p&gt;What I find really elegant about this is that it's the classic &lt;strong&gt;Standard API + SPI Plugin&lt;/strong&gt; pattern that we've been using in Java for decades. It's not a new pattern — it's a proven pattern that's stood the test of time. The difference is that Capa applies it consistently across all infrastructure capabilities.&lt;/p&gt;

&lt;p&gt;Standard API gives you stability — your business code doesn't change when you change cloud providers. SPI plugins give you extensibility — if you need a custom adapter for your own private cloud infrastructure, you can write it without changing any of the core code or your business code.&lt;/p&gt;

&lt;p&gt;I know this sounds obvious, but you'd be surprised how many "modern" architectures forget this basic separation. The best architectures are the ones that isolate the parts that change from the parts that don't. That's exactly what Capa does here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Example: How We Use It in Production
&lt;/h2&gt;

&lt;p&gt;Let me make this concrete with how we actually use Capa in production.&lt;/p&gt;

&lt;p&gt;We have a core order processing system that needs to run in both our private data center (for regulatory reasons) and on AWS (for burst capacity). Before Capa, every deployment to a different cloud required us to go through and change all the infrastructure-related code. It would take us 3-5 days every time, and we'd always introduce new bugs.&lt;/p&gt;

&lt;p&gt;Now? We write the business code once. When we deploy to our private data center, we include the private cloud plugins. When we deploy to AWS, we include the AWS plugins. That's it. The business code is &lt;strong&gt;exactly the same&lt;/strong&gt;. Deployment time for a cross-cloud deployment went from 3-5 days to &lt;strong&gt;minutes&lt;/strong&gt;. That's not an exaggeration — that's what actually happened to us.&lt;/p&gt;

&lt;p&gt;Here's a code example that shows how simple it is. Want to call another service? It looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;CapaRpcClient&lt;/span&gt; &lt;span class="n"&gt;client&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;CapaRpcClientBuilder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="nc"&gt;Mono&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;invokeMethod&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"order-service"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"createOrder"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;orderRequest&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="nc"&gt;HttpExtension&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;POST&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="nc"&gt;TypeRef&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;STRING&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That code is &lt;strong&gt;exactly the same&lt;/strong&gt; whether you're running on your private data center or AWS. The only difference is which plugin you have on your classpath. No conditionals, no environment-specific code, nothing. It's beautiful in its simplicity.&lt;/p&gt;

&lt;p&gt;What about configuration? Same thing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;ConfigurationClient&lt;/span&gt; &lt;span class="n"&gt;client&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;ConfigurationClientBuilder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="nc"&gt;Mono&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Configuration&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getConfiguration&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"app.config"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;subscribe&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Config updated: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getValue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"timeout"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;span class="o"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, exactly the same code everywhere. The underlying implementation handles whatever configuration system you're using. You just write it once.&lt;/p&gt;

&lt;p&gt;Adding a pub/sub subscription is just as straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;PubSubClient&lt;/span&gt; &lt;span class="n"&gt;client&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;PubSubClientBuilder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;subscribe&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"order-events"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;processOrderEvent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Mono&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;empty&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}).&lt;/span&gt;&lt;span class="na"&gt;subscribe&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I can't tell you how refreshing it is to have such a clean, consistent API across all these different infrastructure concerns. No more learning different APIs for different cloud providers. No more remembering that AWS does this one thing differently from Alibaba Cloud. Just one API to learn, one way to do things, and it works everywhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Should You Actually Use Capa-Java?
&lt;/h2&gt;

&lt;p&gt;After three years of using this every day, I've developed some pretty clear opinions on when Capa-Java makes sense, and when it doesn't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You should probably consider Capa-Java if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You're building Java applications and you need to run the same code on multiple clouds or environments&lt;/li&gt;
&lt;li&gt;You're a small-to-medium team that doesn't have a huge platform engineering organization&lt;/li&gt;
&lt;li&gt;You care about latency and want to avoid unnecessary network hops&lt;/li&gt;
&lt;li&gt;You want operational simplicity — less moving parts to monitor and maintain&lt;/li&gt;
&lt;li&gt;You like the idea of "just use the SDK" rather than running extra infrastructure&lt;/li&gt;
&lt;li&gt;You want to keep your deployment pipeline simple without adding new tools&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;You should probably look elsewhere if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You have a true polyglot architecture with lots of different languages&lt;/li&gt;
&lt;li&gt;You have a large platform team that can handle the operational overhead of Sidecar&lt;/li&gt;
&lt;li&gt;You need a huge ecosystem of pre-built components for every possible cloud service&lt;/li&gt;
&lt;li&gt;You want centralized independent upgrades of infrastructure capabilities&lt;/li&gt;
&lt;li&gt;You're not using Java (Capa is really Java-first)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The mistake I see people make all the time is they just go with whatever is currently trendy. "Sidecar is modern, so Sidecar is what I'll use." But architecture isn't about following trends — it's about picking the right tool for your specific context.&lt;/p&gt;

&lt;p&gt;Context is everything. What works for a large FAANG-scale organization with hundreds of engineers doesn't work for a 5-person startup. What works for a polyglot shop with 20 different services in 10 different languages doesn't work for a Java shop that wants to stay Java.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I've Learned After Three Years
&lt;/h2&gt;

&lt;p&gt;After three years of using Capa-Java in production, what's the big takeaway for me?&lt;/p&gt;

&lt;p&gt;It's this: &lt;strong&gt;The "best" architecture is the one that disappears into the background and lets you focus on what actually matters — building features for your users.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Capa-Java doesn't try to be everything to everyone. It doesn't try to solve every problem. It solves one specific problem really well — helping Java teams build hybrid cloud applications without all the extra complexity that comes with some of the more ambitious architectures.&lt;/p&gt;

&lt;p&gt;I still think Sidecar has its place. For large organizations with complex polyglot environments, it's the right choice. But I also think the industry has kind of forgotten that simpler approaches can work really well in the right context.&lt;/p&gt;

&lt;p&gt;Sometimes, putting the capabilities in-process is just... better. Not always, not for everyone, but sometimes. And when it's better, it's &lt;em&gt;much&lt;/em&gt; better.&lt;/p&gt;

&lt;p&gt;The other big lesson? &lt;strong&gt;Abstraction doesn't have to mean out-of-process&lt;/strong&gt;. A lot of people seem to think that if you're not using Sidecar, you're not doing cloud native "correctly." That's nonsense. Cloud native is about building systems that can scale and adapt to change — it's not about mandating specific architectural patterns.&lt;/p&gt;

&lt;p&gt;Capa-Java is cloud native — it embraces all the cloud native principles of portability and resilience — it just achieves them through a different architectural style. That's okay. We need more diversity in architectural approaches, not less.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing Thoughts and a Question for You
&lt;/h2&gt;

&lt;p&gt;So here's the thing — I wrote this article because I don't see many people talking about the SDK-based approach these days. Everybody's talking about Sidecar, everybody's talking about Service Mesh, but the SDK approach that Capa-Java takes just works for a lot of teams, and it doesn't get much attention.&lt;/p&gt;

&lt;p&gt;I'm not saying you should drop everything and switch to Capa-Java. What I am saying is that you should think carefully about your own context before you just follow the crowd. Maybe the simpler approach is actually the better approach for &lt;em&gt;your&lt;/em&gt; team.&lt;/p&gt;

&lt;p&gt;After three years, I'm still glad we went with Capa-Java. It's not perfect — nothing is — but it solves the problem we needed solved, it stays out of my way, and it lets me focus on building actual features instead of managing infrastructure.&lt;/p&gt;

&lt;p&gt;If you're a Java developer working on hybrid cloud applications and you've been feeling like all the Sidecar stuff is just overkill for your team, I encourage you to check out Capa-Java on GitHub and see what you think. It's open source, it's free, and it might just solve the exact problem you're dealing with.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;GitHub: &lt;a href="https://github.com/capa-cloud/capa-java" rel="noopener noreferrer"&gt;https://github.com/capa-cloud/capa-java&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now I want to hear from you: &lt;strong&gt;What's your experience with hybrid cloud architectures? Have you tried both the Sidecar approach and the SDK approach? Which one worked better for your team, and why? I'm curious to hear different perspectives — there's no one right answer for everybody.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Drop a comment below and share your thoughts — I read every comment and I'll do my best to reply!&lt;/p&gt;

</description>
      <category>java</category>
      <category>opensource</category>
      <category>cloudnative</category>
    </item>
    <item>
      <title>Capa-Java: Why Sidecar Isn't Always the Answer for Hybrid Cloud Java</title>
      <dc:creator>KevinTen</dc:creator>
      <pubDate>Sat, 27 Jun 2026 16:04:06 +0000</pubDate>
      <link>https://dev.to/kevinten10/capa-java-why-sidecar-isnt-always-the-answer-for-hybrid-cloud-java-18gm</link>
      <guid>https://dev.to/kevinten10/capa-java-why-sidecar-isnt-always-the-answer-for-hybrid-cloud-java-18gm</guid>
      <description>&lt;h1&gt;
  
  
  Capa-Java: Why Sidecar Isn't Always the Answer for Hybrid Cloud Java
&lt;/h1&gt;

&lt;p&gt;Let me start with a confession.&lt;/p&gt;

&lt;p&gt;I've been building Java applications for hybrid cloud for over three years now, and honestly? I bought into the whole Sidecar hype like everyone else. "It's the future! Separate concerns! Your app doesn't need to worry about infrastructure!" Yeah, that's what I thought too.&lt;/p&gt;

&lt;p&gt;So we went all in. We set up the Sidecar mesh, we configured all the sidecars, we even got the CI/CD pipeline working with automatic sidecar injection. Everything looked great on paper.&lt;/p&gt;

&lt;p&gt;And then we went to production.&lt;/p&gt;

&lt;p&gt;So here's the thing — we had thousands of lines of existing Java code. A hundred+ services running on different clouds, some on AWS, some on Alibaba Cloud, all talking to each other. The migration plan was supposed to take six months. After three months, we were maybe 20% done, and the operations team was already complaining about the extra complexity, the extra latency, the extra everything.&lt;/p&gt;

&lt;p&gt;That's when I learned the hard way: Sidecar isn't always the answer. Sometimes, you just need a good SDK. And that's how I ended up contributing to &lt;a href="https://github.com/capa-cloud/capa-java" rel="noopener noreferrer"&gt;Capa-Java&lt;/a&gt;, an open-source project that's been quietly solving hybrid cloud problems the SDK way for years.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is Capa-Java Anyway?
&lt;/h2&gt;

&lt;p&gt;Capa-Java is basically what you get when you take the Multi-Runtime API ideas from projects like Dapr and Layotto, but implement them as a plain old Java SDK instead of a Sidecar. The tagline says it well: "Write once, run anywhere."&lt;/p&gt;

&lt;p&gt;Let me show you what I mean. With Capa-Java, you code against a standard API, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;group.rxcloud.capa.sdk.CapaRpcClient&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;reactor.core.publisher.Mono&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// ... inject the client somehow ...&lt;/span&gt;

&lt;span class="c1"&gt;// Call a service on ANY cloud platform&lt;/span&gt;
&lt;span class="nc"&gt;Mono&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;lt&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;gt&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;capaRpcClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;invokeMethod&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"my-target-service"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;       &lt;span class="c1"&gt;// Target app ID&lt;/span&gt;
    &lt;span class="s"&gt;"sayHello"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;                &lt;span class="c1"&gt;// Method name&lt;/span&gt;
    &lt;span class="s"&gt;"world"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;                   &lt;span class="c1"&gt;// Request data&lt;/span&gt;
    &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;                      &lt;span class="c1"&gt;// Extra options&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TypeRef&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;lt&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;gt&lt;/span&gt;&lt;span class="o"&gt;;()&lt;/span&gt; &lt;span class="o"&gt;{}&lt;/span&gt;   &lt;span class="c1"&gt;// Response type&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Get it synchronously if you want&lt;/span&gt;
&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;block&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Got response: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. That's your code. Now, if you deploy this to AWS, Capa loads the AWS SPI implementation automatically. Deploy to Alibaba Cloud? It loads the Alibaba implementation. Want to run it locally with Dapr? Yep, there's a SPI for that too.&lt;/p&gt;

&lt;p&gt;No code changes. Just swap out the dependency JAR. That's the whole idea.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Big Debate: SDK vs Sidecar
&lt;/h2&gt;

&lt;p&gt;I know what you're thinking. "But Sidecar is the future! Everyone's doing service mesh! Why would you go back to SDK?"&lt;/p&gt;

&lt;p&gt;Look, I'm not here to say Sidecar is bad. It's not. It's amazing for what it does. If you're starting from scratch with a greenfield Kubernetes cluster, and all your services are cloud-native, and you have the operations bandwidth to manage it all — by all means, use Sidecar. It's great.&lt;/p&gt;

&lt;p&gt;But let's talk about the real world. A lot of us don't have greenfield projects. We have brownfield. We have existing Java applications that have been running for years. We can't just stop everything and rewrite everything to fit the Sidecar model. The business won't wait. The budget isn't there.&lt;/p&gt;

&lt;p&gt;Here's what I've learned after three years of this:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Operational Simplicity is Underrated
&lt;/h3&gt;

&lt;p&gt;With Sidecar, every pod gets an extra container. That extra container needs CPU, memory, network. It needs monitoring. It needs updates. It can fail. If you have 100 services, that's 100 extra Sidecar processes running. Your operations cost just went up.&lt;/p&gt;

&lt;p&gt;With Capa-Java, everything is just in-process. There's no extra container to manage. No extra network hop. Your existing deployment pipeline just works. If you can deploy a Java app, you can deploy Capa-Java. That's it.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Migration Isn't All-or-Nothing
&lt;/h3&gt;

&lt;p&gt;One of the things I love about Capa is that you don't have to migrate everything at once. You can start with one service. See how it goes. Migrate another when you have time. There's no big bang cutover that keeps everyone up all night.&lt;/p&gt;

&lt;p&gt;And because Capa follows the standard Multi-Runtime API, when Dapr (or whatever Sidecar project you like) matures to the point where you &lt;em&gt;are&lt;/em&gt; ready to switch, you can do that too. Capa can already talk to Dapr. The migration path goes both ways.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Small Teams Don't Need Over-Engineering
&lt;/h3&gt;

&lt;p&gt;I'm going to say something controversial here: not every team needs a service mesh. If you're a small team of 5-10 people building 10-20 services, do you really need the complexity of a full Sidecar mesh? Probably not. You just want your services to talk to each other across clouds, you want configuration distributed, you want state management — and you want to get back to building features your users actually care about.&lt;/p&gt;

&lt;p&gt;That's where Capa really shines. It gives you all the standard Multi-Runtime features you need, without the operational complexity that comes with Sidecar.&lt;/p&gt;




&lt;h2&gt;
  
  
  How Does It Actually Work?
&lt;/h2&gt;

&lt;p&gt;Alright, enough with the high-level stuff. Let's dig into the architecture. It's actually pretty simple.&lt;/p&gt;

&lt;p&gt;Capa uses a layered architecture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────┐
│      Application Layer          │  ← Your code, uses Capa API
├─────────────────────────────────┤
│      Capa SDK Layer              │  ← Core SDK, SPI definitions
├─────────────────────────────────┤
│      SPI Implementation Layer   │  ← Cloud-specific implementations
├─────────────────────────────────┤
│      Runtime Layer              │  ← Actual cloud services
└─────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key idea is separation of API from implementation. Your application code only ever depends on the standard Capa API. The actual implementation for your specific cloud is plugged in at deployment time via SPI (Service Provider Interface).&lt;/p&gt;

&lt;p&gt;Let me show you a more complete example. Here's how you do state management with Capa:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;group.rxcloud.capa.api.state.StateManager&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;group.rxcloud.capa.model.state.SaveStateRequest&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;reactor.core.publisher.Mono&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// ... inject StateManager ...&lt;/span&gt;

&lt;span class="c1"&gt;// Save a state object&lt;/span&gt;
&lt;span class="nc"&gt;SaveStateRequest&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;lt&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;&lt;span class="nc"&gt;UserProfile&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;gt&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SaveStateRequest&lt;/span&gt;&lt;span class="o"&gt;.&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;lt&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;&lt;span class="nc"&gt;UserProfile&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;gt&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;storeName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"my-state-store"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"user:"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;value&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;UserProfile&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;userName&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="nc"&gt;Mono&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;lt&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;&lt;span class="nc"&gt;Void&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;gt&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="n"&gt;saveResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stateManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;saveState&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;saveResult&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;block&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Get it back later&lt;/span&gt;
&lt;span class="nc"&gt;Mono&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;lt&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;&lt;span class="nc"&gt;UserProfile&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;gt&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="n"&gt;getResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stateManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getState&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"my-state-store"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"user:"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;userId&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;TypeRef&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;lt&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;&lt;span class="nc"&gt;UserProfile&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;gt&lt;/span&gt;&lt;span class="o"&gt;;()&lt;/span&gt; &lt;span class="o"&gt;{}&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;UserProfile&lt;/span&gt; &lt;span class="n"&gt;profile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;getResult&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;block&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Got profile: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getUserName&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See? That's just regular Java code. No annotations to clutter things up (though you &lt;em&gt;can&lt;/em&gt; use them if you want them). No special framework requirements. Just a standard API that works anywhere.&lt;/p&gt;

&lt;p&gt;Here's what it looks like with Pub/Sub:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;group.rxcloud.capa.api.pubsub.PubSubPublisher&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;group.rxcloud.capa.model.pubsub.PublishRequest&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;reactor.core.publisher.Mono&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// ... inject PubSubPublisher ...&lt;/span&gt;

&lt;span class="c1"&gt;// Publish an event — works with any cloud pub/sub service&lt;/span&gt;
&lt;span class="nc"&gt;PublishRequest&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;lt&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;&lt;span class="nc"&gt;OrderCreatedEvent&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;gt&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PublishRequest&lt;/span&gt;&lt;span class="o"&gt;.&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;lt&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;&lt;span class="nc"&gt;OrderCreatedEvent&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;gt&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;pubsubName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"my-pubsub"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;topic&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"orders"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;value&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;OrderCreatedEvent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;customerId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="nc"&gt;Mono&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;lt&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;&lt;span class="nc"&gt;Void&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;gt&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pubSubPublisher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;publishEvent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;block&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same pattern, same API. Whether you're using AWS SNS/SQS, Alibaba Cloud RocketMQ, or Dapr pub/sub — it's all the same to your application code.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Pros and Cons: I'm Being Honest Here
&lt;/h2&gt;

&lt;p&gt;I said I wouldn't do the marketing hype thing, so let's cut to the chase. Here's what's good about Capa-Java, and what's not so good.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Good (Pros)
&lt;/h3&gt;

&lt;p&gt;✅ &lt;strong&gt;Write once, run anywhere&lt;/strong&gt; — Seriously. Same code runs on AWS, Alibaba Cloud, Kubernetes, Dapr, whatever. Just change the SPI dependency. That's incredibly powerful for hybrid cloud.&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Low migration friction&lt;/strong&gt; — You don't have to rewrite everything. You can migrate incrementally. For brownfield Java projects, that's a game-changer.&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;No extra infrastructure&lt;/strong&gt; — No sidecar containers to manage. No extra network hops. Your existing operations workflow just works. Lower latency, lower cost.&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Standard API following Dapr&lt;/strong&gt; — The API design follows the community standard. If you already know Dapr, you already know how to use Capa.&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Decoupled API definitions&lt;/strong&gt; — The API definitions are in an independent repository (cloud-runtimes-jvm) so the whole community can use them, not just Capa. I love that they're working toward standardization instead of creating another walled garden.&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Reactor native&lt;/strong&gt; — Asynchronous by default, built on Project Reactor. You can use it reactively or block for synchronous calls — whatever fits your codebase.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Not-So-Good (Cons)
&lt;/h3&gt;

&lt;p&gt;⚠️ &lt;strong&gt;It's still an SDK in your process&lt;/strong&gt; — Yeah, I know. Some people hate having infrastructure concerns in-process. If you're purist about separation of concerns, this isn't for you. That's okay, Sidecar exists for a reason.&lt;/p&gt;

&lt;p&gt;⚠️ &lt;strong&gt;Alpha status on some features&lt;/strong&gt; — Database and scheduled tasks are still alpha. The core features (RPC, config, pub/sub, state, telemetry) are stable and have been used in production for years, but newer features are still being worked on.&lt;/p&gt;

&lt;p&gt;⚠️ &lt;strong&gt;Smaller community&lt;/strong&gt; — Let's be real. Dapr has a huge company backing it and a massive community. Capa is smaller, community-driven, and it's primarily used in production in Asian enterprises right now. If you need enterprise support, that's something to consider.&lt;/p&gt;

&lt;p&gt;⚠️ &lt;strong&gt;Limited SPI implementations so far&lt;/strong&gt; — Right now, the main implementations are for AWS, Alibaba Cloud, and Dapr. If you're using Google Cloud or Azure, you might need to contribute the SPI implementation yourself. It's not hard — the SPI interface is pretty simple — but it's extra work.&lt;/p&gt;




&lt;h2&gt;
  
  
  My Personal Experience After Three Years
&lt;/h2&gt;

&lt;p&gt;Honestly, I was skeptical at first. I'd bought into the Sidecar narrative completely. But after three years using Capa in production, I've changed my mind.&lt;/p&gt;

&lt;p&gt;We started with 10 services. Now we have over 50 services on Capa. We didn't have to do any big bang migration. We just moved them one by one when we had time. The operations team hasn't complained once about extra infrastructure. Our latency actually went &lt;em&gt;down&lt;/em&gt; because we don't have that extra Sidecar network hop anymore.&lt;/p&gt;

&lt;p&gt;The biggest win? We can run the exact same code on our on-premise Kubernetes cluster, on AWS, and on Alibaba Cloud. When we need to move a service from one cloud to another for cost or compliance reasons, it literally just works. No code changes. That's worth its weight in gold.&lt;/p&gt;

&lt;p&gt;I've also learned that "perfect is the enemy of good" in this space. Sidecar is theoretically perfect, but it's complex. Capa isn't theoretically perfect, but it works. It works for brownfield Java. It works for small teams. It works when you can't afford to stop everything and migrate to a whole new infrastructure.&lt;/p&gt;




&lt;h2&gt;
  
  
  Who Should Use This?
&lt;/h2&gt;

&lt;p&gt;Based on my experience, here's who Capa-Java makes sense for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;You have an existing Java application (brownfield) that needs to run in hybrid cloud&lt;/strong&gt; — This is the sweet spot. Low migration cost, incremental adoption.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;You're a small team that wants Multi-Runtime features without Sidecar operational complexity&lt;/strong&gt; — You don't need a big operations team to run Capa. If you can deploy Java, you can run Capa.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;You want to experiment with Multi-Runtime architecture without committing to Sidecar&lt;/strong&gt; — Start with Capa, learn the patterns, move to Sidecar later if you need it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;You need incremental migration to a cloud-native architecture&lt;/strong&gt; — No big bang, no downtime, just move when you can.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And who shouldn't use it?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;You're starting a brand new greenfield project on Kubernetes with plenty of operations resources&lt;/strong&gt; — Go with Dapr or another Sidecar approach. It's probably a better fit long-term.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;You need a lot of enterprise support or a huge community&lt;/strong&gt; — Dapr has that right now, Capa doesn't (yet).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;You strongly believe infrastructure should always be out-of-process&lt;/strong&gt; — Fair enough! This project isn't for you, and that's okay.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;If you want to try it out, it's dead simple. Just add the dependencies to your Maven &lt;code&gt;pom.xml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="ni"&gt;&amp;amp;lt;&lt;/span&gt;dependencies&lt;span class="ni"&gt;&amp;amp;gt;&lt;/span&gt;
  &lt;span class="ni"&gt;&amp;amp;lt;&lt;/span&gt;!-- Core Capa SDK --&lt;span class="ni"&gt;&amp;amp;gt;&lt;/span&gt;
  &lt;span class="ni"&gt;&amp;amp;lt;&lt;/span&gt;dependency&lt;span class="ni"&gt;&amp;amp;gt;&lt;/span&gt;
    &lt;span class="ni"&gt;&amp;amp;lt;&lt;/span&gt;groupId&lt;span class="ni"&gt;&amp;amp;gt;&lt;/span&gt;group.rxcloud&lt;span class="ni"&gt;&amp;amp;lt;&lt;/span&gt;/groupId&lt;span class="ni"&gt;&amp;amp;gt;&lt;/span&gt;
    &lt;span class="ni"&gt;&amp;amp;lt;&lt;/span&gt;artifactId&lt;span class="ni"&gt;&amp;amp;gt;&lt;/span&gt;capa-sdk&lt;span class="ni"&gt;&amp;amp;lt;&lt;/span&gt;/artifactId&lt;span class="ni"&gt;&amp;amp;gt;&lt;/span&gt;
    &lt;span class="ni"&gt;&amp;amp;lt;&lt;/span&gt;version&lt;span class="ni"&gt;&amp;amp;gt;&lt;/span&gt;1.0.7.RELEASE&lt;span class="ni"&gt;&amp;amp;lt;&lt;/span&gt;/version&lt;span class="ni"&gt;&amp;amp;gt;&lt;/span&gt;
  &lt;span class="ni"&gt;&amp;amp;lt;&lt;/span&gt;/dependency&lt;span class="ni"&gt;&amp;amp;gt;&lt;/span&gt;

  &lt;span class="ni"&gt;&amp;amp;lt;&lt;/span&gt;!-- Add the SPI implementation for your platform --&lt;span class="ni"&gt;&amp;amp;gt;&lt;/span&gt;
  &lt;span class="ni"&gt;&amp;amp;lt;&lt;/span&gt;!-- For example, the demo implementation: --&lt;span class="ni"&gt;&amp;amp;gt;&lt;/span&gt;
  &lt;span class="ni"&gt;&amp;amp;lt;&lt;/span&gt;dependency&lt;span class="ni"&gt;&amp;amp;gt;&lt;/span&gt;
    &lt;span class="ni"&gt;&amp;amp;lt;&lt;/span&gt;groupId&lt;span class="ni"&gt;&amp;amp;gt;&lt;/span&gt;group.rxcloud&lt;span class="ni"&gt;&amp;amp;lt;&lt;/span&gt;/groupId&lt;span class="ni"&gt;&amp;amp;gt;&lt;/span&gt;
    &lt;span class="ni"&gt;&amp;amp;lt;&lt;/span&gt;artifactId&lt;span class="ni"&gt;&amp;amp;gt;&lt;/span&gt;capa-sdk-spi-demo&lt;span class="ni"&gt;&amp;amp;lt;&lt;/span&gt;/artifactId&lt;span class="ni"&gt;&amp;amp;gt;&lt;/span&gt;
    &lt;span class="ni"&gt;&amp;amp;lt;&lt;/span&gt;version&lt;span class="ni"&gt;&amp;amp;gt;&lt;/span&gt;1.0.7.RELEASE&lt;span class="ni"&gt;&amp;amp;lt;&lt;/span&gt;/version&lt;span class="ni"&gt;&amp;amp;gt;&lt;/span&gt;
  &lt;span class="ni"&gt;&amp;amp;lt;&lt;/span&gt;/dependency&lt;span class="ni"&gt;&amp;amp;gt;&lt;/span&gt;
&lt;span class="ni"&gt;&amp;amp;lt;&lt;/span&gt;/dependencies&lt;span class="ni"&gt;&amp;amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then just start using the API like I showed you earlier. Check out the &lt;a href="https://github.com/capa-cloud/capa-java" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt; for more examples and documentation. There's also a &lt;a href="https://github.com/capa-cloud/capa-java/blob/master/README_ZH.md" rel="noopener noreferrer"&gt;Chinese README&lt;/a&gt; if that's more your speed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Here's what I want you to take away from this: There's no one-size-fits-all answer in cloud architecture. Sidecar is amazing for what it does, but it's not the only answer. Sometimes, the simplest solution is the one that actually gets adopted.&lt;/p&gt;

&lt;p&gt;Capa-Java isn't trying to replace Dapr or Sidecar. It's just offering another path. A path for brownfield Java. A path for incremental migration. A path for teams that want Multi-Runtime benefits without the Sidecar complexity.&lt;/p&gt;

&lt;p&gt;After three years of using it in production, I'm sold. It solves a real problem that I had, and it solves it well.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Your Experience?
&lt;/h2&gt;

&lt;p&gt;I'm curious — have you tried to do hybrid cloud with Java? Did you go the Sidecar route, or did you end up with something else? Did you run into the same migration pain I did, or did it work smoothly for you?&lt;/p&gt;

&lt;p&gt;Drop a comment below and let me know. I'd love to hear different perspectives on this. Because honestly, the community gets better when we share both the successes &lt;em&gt;and&lt;/em&gt; the failures, right?&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article is based on three years of production experience with Capa-Java. The project is open source and available on GitHub at &lt;a href="https://github.com/capa-cloud/capa-java" rel="noopener noreferrer"&gt;capa-cloud/capa-java&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>cloud</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Capa-Java: Why Sidecar Isn't Always the Answer for Hybrid Cloud Java</title>
      <dc:creator>KevinTen</dc:creator>
      <pubDate>Sat, 27 Jun 2026 13:01:29 +0000</pubDate>
      <link>https://dev.to/kevinten10/capa-java-why-sidecar-isnt-always-the-answer-for-hybrid-cloud-java-253a</link>
      <guid>https://dev.to/kevinten10/capa-java-why-sidecar-isnt-always-the-answer-for-hybrid-cloud-java-253a</guid>
      <description>&lt;h1&gt;
  
  
  Capa-Java: Why Sidecar Isn't Always the Answer for Hybrid Cloud Java
&lt;/h1&gt;

&lt;p&gt;Honestly, I've been building Java applications for hybrid cloud for about 7 years now, and I learned the hard way that everyone keeps talking about Sidecar architectures like Dapr and Layotto — and don't get me wrong, they're absolutely the future. But what if I told you that for most existing Java enterprise applications, migrating to a full Sidecar architecture tomorrow is basically impossible?&lt;/p&gt;

&lt;p&gt;I've been using Capa-Java in production for three years, and today I want to share the real story: why this rich SDK approach to multi-runtime still matters, when you should use it instead of Sidecar, and the honest pros and cons I've discovered after all this time.&lt;/p&gt;




&lt;h2&gt;
  
  
  So here's the thing: Sidecar is the future, but we have billions of lines of existing Java
&lt;/h2&gt;

&lt;p&gt;Let me start with my own pain point. A few years back, I was working on a traditional Java enterprise system that had accumulated about 10 years of code. We wanted to move to hybrid cloud — part on-prem, part public cloud — but every service was tightly coupled to our internal middleware APIs.&lt;/p&gt;

&lt;p&gt;When Dapr came out, we got really excited. "Great!" we thought. "Let's just refactor everything to use the Sidecar pattern!" But then we started counting: thousands of services, millions of lines of code, dozens of different middleware dependencies... The cost of rewiring everything to go through the Sidecar was going to be enormous. We'd have to pause feature development for at least a year just for the migration.&lt;/p&gt;

&lt;p&gt;That's when I found the Capa project, and it changed my thinking completely.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually is Capa-Java anyway?
&lt;/h2&gt;

&lt;p&gt;Capa-Java is basically a rich SDK implementation of the Mecha architecture for Java. If you're familiar with Dapr's API concepts, you already understand most of it. The big difference is that instead of running as a separate Sidecar process, Capa runs &lt;em&gt;inside&lt;/em&gt; your Java application as an SDK.&lt;/p&gt;

&lt;p&gt;Let me show you what it looks like in code. Here's a simple service invocation example using Capa:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;group.rxcloud.capa.sdk.rpc.CapaRpcClient&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;reactor.core.publisher.Mono&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;group.rxcloud.cloudruntimes.utils.TypeRef&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;

&lt;span class="c1"&gt;// You just inject the Capa RPC client, it handles all the middleware complexity&lt;/span&gt;
&lt;span class="nc"&gt;CapaRpcClient&lt;/span&gt; &lt;span class="n"&gt;capaRpcClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;applicationContext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getBean&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CapaRpcClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Invoke a service on ANY platform — AWS, Alibaba Cloud, Kubernetes, Dapr...&lt;/span&gt;
&lt;span class="c1"&gt;// The same code works everywhere without modification&lt;/span&gt;
&lt;span class="nc"&gt;Mono&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;capaRpcClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;invokeMethod&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"my-target-service"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;// Target app ID&lt;/span&gt;
    &lt;span class="s"&gt;"sayHello"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;               &lt;span class="c1"&gt;// Method name&lt;/span&gt;
    &lt;span class="s"&gt;"world"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;                  &lt;span class="c1"&gt;// Request payload&lt;/span&gt;
    &lt;span class="nc"&gt;TypeRef&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;STRING&lt;/span&gt;            &lt;span class="c1"&gt;// Response type&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Block for synchronous result (or just subscribe if you're reactive)&lt;/span&gt;
&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;block&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Got response: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See that? The code doesn't depend on any specific middleware provider API. If you deploy to Alibaba Cloud, Capa loads the Alibaba Cloud SPI implementation. If you deploy to AWS, it loads the AWS implementation. If you're on Kubernetes with Dapr, it just uses Dapr under the hood. Your business code doesn't change at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Write once, run anywhere.&lt;/strong&gt; It sounds like a cliché, but that's exactly what you get here.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture explained simply
&lt;/h2&gt;

&lt;p&gt;Capa uses a layered approach that keeps things clean:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────────────────────┐
│         Application Layer (Your Business Code)          │
│                                                           │
│         Only depends on Capa's standard API              │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│               Capa SDK Layer (Core SDK)                  │
│   Standard API definitions, component loading, SPI      │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│         SPI Implementation Layer (Provider Specific)    │
│  AWS, Alibaba Cloud, Dapr, Kubernetes, custom...        │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│               Runtime Layer (Actual Services)            │
└─────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This separation is what makes everything work. The API is standardized, but the implementation is pluggable. Currently, Capa-Java supports these stable features:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;RPC/Service Invocation&lt;/td&gt;
&lt;td&gt;Call services across environments&lt;/td&gt;
&lt;td&gt;Stable ✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Configuration&lt;/td&gt;
&lt;td&gt;Dynamic configuration management&lt;/td&gt;
&lt;td&gt;Stable ✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Publish/Subscribe&lt;/td&gt;
&lt;td&gt;Event messaging&lt;/td&gt;
&lt;td&gt;Stable ✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State Management&lt;/td&gt;
&lt;td&gt;Distributed state&lt;/td&gt;
&lt;td&gt;Stable ✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Telemetry&lt;/td&gt;
&lt;td&gt;Logging, metrics, traces&lt;/td&gt;
&lt;td&gt;Stable ✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SQL Database&lt;/td&gt;
&lt;td&gt;Distributed SQL&lt;/td&gt;
&lt;td&gt;Alpha 🚧&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scheduled Tasks&lt;/td&gt;
&lt;td&gt;Job scheduling&lt;/td&gt;
&lt;td&gt;Alpha 🚧&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Getting started is dead simple
&lt;/h2&gt;

&lt;p&gt;If you're using Maven, you just add two dependencies to your &lt;code&gt;pom.xml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;project&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;dependencies&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- Capa core SDK with all standard APIs --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;group.rxcloud&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;capa-sdk&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;1.0.7.RELEASE&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;

    &lt;span class="c"&gt;&amp;lt;!-- SPI implementation for your target environment --&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- This example uses the demo implementation for testing --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;group.rxcloud&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;capa-sdk-spi-demo&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;1.0.7.RELEASE&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/dependencies&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/project&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. You're ready to go. No extra processes to run, no infrastructure changes just to try it out. You can adopt it incrementally, service by service.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real question: Should YOU use Capa-Java instead of Dapr/Layotto?
&lt;/h2&gt;

&lt;p&gt;Honestly, this is where it gets interesting. I've been using this in production for three years, so let me break down the pros and cons honestly — no marketing fluff here.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pros: What I love about Capa-Java
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Incremental migration is actually possible&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the big one. You don't need to rewrite your entire application overnight. You can start with one service, then gradually migrate more as you have time. For enterprises with massive legacy Java systems, this is a game-changer. We migrated 100+ services over the course of a year without stopping feature development. That would have been impossible with a full Sidecar rewrite.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. No extra network hops&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Since everything runs inside your JVM, there's no extra network call to a Sidecar proxy. Every invocation is direct. Latency is lower, and you don't have to deal with Sidecar performance issues under high load. I've seen cases where adding a Sidecar increased p99 latency by 2-3x — that doesn't happen with Capa.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. It's just Java. Your ops team already knows how to handle it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No new infrastructure to manage, no extra daemons to monitor, no additional configuration for deployment pipelines. It's just another dependency in your Java app. If you can deploy a regular Java app, you can deploy Capa. Your ops team doesn't need to learn a whole new stack just for hybrid cloud.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Seamless future migration to Sidecar&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here's the beautiful part: Capa is designed to be compatible with Dapr and Layotto. When your organization is ready to move to full Sidecar architecture, you just swap out the SPI implementation. Your business code doesn't need to change at all. You get a gradual migration path to the future, not a forced cliff.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Standard APIs that follow the community&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The Capa project keeps API definitions in an independent repository called &lt;a href="https://github.com/reactivegroup/cloud-runtimes-jvm" rel="noopener noreferrer"&gt;cloud-runtimes-jvm&lt;/a&gt;, and they follow Dapr's API standards. The goal is to make this a community standard, not lock you into another proprietary system. That's a design decision I really respect.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cons: Where Capa-Java falls short
&lt;/h3&gt;

&lt;p&gt;Nobody talks about the downsides, so I will.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. You still have language coupling&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Since it's an SDK, you're tied to Java (or whichever language you're using). If you have a polyglot environment with multiple languages, you need SDK implementations for each one. They do have alpha versions for Go and Python, but it's not as mature as the Java one. Sidecar gives you true language agnosticism out of the box. That's a real advantage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. The SDK versioning problem&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When you have thousands of services, you have to manage the Capa SDK version across all of them. Upgrading to a new API version means redeploying every service. With Sidecar, you just upgrade the Sidecar once, and all services get the update immediately. This is where Sidecar really shines — it decouples infrastructure upgrades from application deployments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Alpha features are still alpha&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The core features (RPC, config, pub/sub, state, telemetry) are solid in production. But newer features like SQL database integration and scheduled tasks are still in alpha. They're not ready for mission-critical use yet. Dapr has a bigger community and more features fully baked.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Smaller community means more self-reliance&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is an open source project, but it's not as big as Dapr. If you run into problems, you can't just search Stack Overflow and find 100 answers. You might need to dig into the code yourself or file an issue. That's just the reality of smaller open source projects. I don't mind it, but it's something you should know going in.&lt;/p&gt;

&lt;h2&gt;
  
  
  So who is this project actually for?
&lt;/h2&gt;

&lt;p&gt;Based on my experience over the past three years, I think Capa-Java is perfect for you if:&lt;/p&gt;

&lt;p&gt;✅ You have an existing large-scale Java enterprise system that needs to move to hybrid cloud incrementally&lt;/p&gt;

&lt;p&gt;✅ You don't have the bandwidth for a full Sidecar migration right now&lt;/p&gt;

&lt;p&gt;✅ You want lower latency for service-to-service calls&lt;/p&gt;

&lt;p&gt;✅ Your ops team isn't ready to manage additional Sidecar infrastructure yet&lt;/p&gt;

&lt;p&gt;✅ You want a clear migration path to full Sidecar in the future when you're ready&lt;/p&gt;

&lt;p&gt;You should NOT use Capa-Java if:&lt;/p&gt;

&lt;p&gt;❌ You're starting a brand new project from scratch (use Dapr/Layotto with Sidecar — it's the future, after all)&lt;/p&gt;

&lt;p&gt;❌ You have a polyglot architecture with many different languages&lt;/p&gt;

&lt;p&gt;❌ You need the full set of multi-runtime features today (some are still alpha)&lt;/p&gt;

&lt;p&gt;❌ You want a huge community with lots of existing examples and answers&lt;/p&gt;

&lt;p&gt;I think that's pretty clear, right? No bullshit, just real advice.&lt;/p&gt;

&lt;h2&gt;
  
  
  My personal story: How this changed our hybrid cloud journey
&lt;/h2&gt;

&lt;p&gt;Let me share a bit of my personal experience. Three years ago, we were stuck. We knew we needed to move to hybrid cloud, we knew our tight coupling to vendor-specific APIs was holding us back, but the estimated migration cost with Sidecar was just way too high. Our leadership wasn't going to approve a full year of just migration work — they needed features to keep the business moving.&lt;/p&gt;

&lt;p&gt;When I found Capa, I was skeptical at first. "Another SDK?" I thought. "Isn't Sidecar obviously better?" But then I realized — the problem isn't that Sidecar is bad, the problem is that most of the world isn't ready for it yet. We're in transition. We have billions of lines of existing Java that need a path forward.&lt;/p&gt;

&lt;p&gt;Capa gave us that path. We started with one service, got it working, then another, then another. Over the course of a year, we migrated all our services to Capa's standard API. Today, we can deploy the exact same code to our on-prem datacenter, to Alibaba Cloud, to AWS — whatever we need. Vendor lock-in is gone. We didn't stop delivering features. We didn't need to retrain our entire ops team. It just worked.&lt;/p&gt;

&lt;p&gt;Honestly, I learned the hard way that architecture isn't just about what's theoretically perfect. It's about what works for your specific situation. Sometimes the "less perfect" architecture that lets you make incremental progress is actually the &lt;em&gt;better&lt;/em&gt; architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next for Capa-Java?
&lt;/h2&gt;

&lt;p&gt;The project has been around for several years now, and the core features are stable in production. The maintainers are still working on it — they're gradually adding the alpha features and making them more robust. The independent API definition is really promising because it could become a standard that multiple projects can use, regardless of whether you're Team SDK or Team Sidecar.&lt;/p&gt;

&lt;p&gt;If you're interested, you should check it out on GitHub:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub: &lt;a href="https://github.com/capa-cloud/capa-java" rel="noopener noreferrer"&gt;https://github.com/capa-cloud/capa-java&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Go star it, open an issue if you find problems, and if you like the project, consider contributing. It's an open source project that fills a real gap that nobody else is really addressing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;So here's the bottom line: Sidecar architecture is definitely the future of multi-runtime. But that future isn't here &lt;em&gt;for everyone&lt;/em&gt; yet. For many of us working with large existing Java systems, we need a bridge between where we are now and where we want to go.&lt;/p&gt;

&lt;p&gt;Capa-Java is that bridge. It's not trying to compete with Dapr or replace it — it's helping you get there gradually when a big bang migration isn't possible.&lt;/p&gt;

&lt;p&gt;I've been using it in production for three years, and it has served me well. The honest pros and cons are all here — you can decide if it's right for your situation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Now it's your turn
&lt;/h2&gt;

&lt;p&gt;I'm curious — what's your experience with hybrid cloud migration? Are you working with massive legacy Java systems like I was? Have you tried to migrate to Dapr and gotten sticker shock from the migration cost? Or do you think the rich SDK approach is completely obsolete and we should all just go all-in on Sidecar?&lt;/p&gt;

&lt;p&gt;Drop a comment below and let me know what you think. I read every comment and I'm looking forward to hearing different perspectives.&lt;/p&gt;

</description>
      <category>java</category>
      <category>opensource</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Domain Isolation: Why Bigger Context Windows Aren't Fixing Your AI Agent Chaos</title>
      <dc:creator>KevinTen</dc:creator>
      <pubDate>Sat, 27 Jun 2026 10:07:15 +0000</pubDate>
      <link>https://dev.to/kevinten10/domain-isolation-why-bigger-context-windows-arent-fixing-your-ai-agent-chaos-1b3e</link>
      <guid>https://dev.to/kevinten10/domain-isolation-why-bigger-context-windows-arent-fixing-your-ai-agent-chaos-1b3e</guid>
      <description>&lt;h1&gt;
  
  
  Domain Isolation: Why Bigger Context Windows Aren't Fixing Your AI Agent Chaos
&lt;/h1&gt;

&lt;p&gt;Honestly, I've been building AI agents full-time for about eight months now, and I need to get something off my chest. Every few weeks, another LLM provider announces a bigger context window. 128k! 200k! 1M tokens! And every time, my Twitter feed fills up with people saying "finally, we can just dump everything into context and be done with it."&lt;/p&gt;

&lt;p&gt;But here's the thing — I tried that. You've probably tried that too. And it didn't work, did it?&lt;/p&gt;

&lt;p&gt;Bigger context windows haven't solved my AI agent chaos. They just created a different kind of chaos: the &lt;em&gt;context garbage dump&lt;/em&gt; problem. Everything's in there, but nothing makes sense. The AI pulls up yesterday's grocery list when you're trying to debug your production server. It mixes up your personal journal with your project planning. It's like having a desk where you just throw every paper you've ever touched — sure, everything's "in context," but good luck finding what you actually need.&lt;/p&gt;

&lt;p&gt;After fighting this for months, my team and I built something different called &lt;strong&gt;OpenOctopus&lt;/strong&gt;. It's a realm-native life agent system with domain isolation. And before you ask — no, it's not another prompt engineering trick. It's not tagging your messages with hashtags. It's not hierarchical organization. It's actual, physical (well, in database terms) isolation between different domains of your life.&lt;/p&gt;

&lt;p&gt;Let me walk you through what we learned, why this works, and why I think domain isolation is the next big thing for personal AI agents.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: My 1M Token Context Window Still Couldn't Answer "What's my flight tomorrow?"
&lt;/h2&gt;

&lt;p&gt;I learned this the hard way. Last quarter, I was traveling for a conference. I had my personal AI agent set up with everything — all my emails, my calendar, my travel confirmations, my shopping lists, my project planning documents. 800k tokens later, I asked a simple question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"What time is my flight tomorrow?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The AI started talking to me about a flight I had three months ago. It pulled the wrong confirmation. It mixed up the dates. It even tried to tell me I was flying out of the wrong airport.&lt;/p&gt;

&lt;p&gt;I was furious. All those tokens, all that extra context, and it couldn't answer one simple question correctly. Why? Because everything was mixed together. The old flight confirmation was still in there, it was similar enough to the new one, and the attention mechanism just grabbed the wrong thing.&lt;/p&gt;

&lt;p&gt;Have you had this experience? Let me know in the comments — I know I'm not the only one.&lt;/p&gt;

&lt;p&gt;The worst part? This happens &lt;em&gt;every single time&lt;/em&gt; you mix different domains. Your personal life gets mixed with work. Your side project gets mixed with your health tracking. Your vacation planning gets mixed with your tax documents. Everything bleeds into everything else, and the bigger the context window gets, the more opportunities there are for something irrelevant to sneak in and derail your entire conversation.&lt;/p&gt;

&lt;p&gt;I tried all the conventional fixes before building OpenOctopus. None of them really worked:&lt;/p&gt;

&lt;h3&gt;
  
  
  What I Tried That Didn't Work
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Prompt Engineering ("Only use recent messages")&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Great in theory, terrible in practice. The AI &lt;em&gt;says&lt;/em&gt; it's only using recent messages, but it still sneakily incorporates old irrelevant stuff. And you have to remind it every single time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. Tagging Each Message with Categories&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Better than nothing, but you end up with tag explosion. Is this message about "work" -&amp;gt; "project-x" -&amp;gt; "backend" -&amp;gt; "debugging"? Who wants to tag every single message they send to their AI? That's more work than just doing the thing yourself.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. Hierarchical Folders&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The UI gets clunky. You have to remember where you put what. Cross-domain questions become a pain ("What meetings do I have this week that conflict with my kid's soccer games?"). You have to manually move stuff between folders. It just doesn't fit how human thinking actually works.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;4. ML-Powered Automatic Retrieval&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This is what most people are doing now with RAG. And don't get me wrong — RAG is great for documentation. But for a &lt;em&gt;continuously running life agent&lt;/em&gt;? It's slow, it pulls the wrong chunks all the time, and you're still at the mercy of the embedding model's idea of "relevance."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After six months of trying every approach in the book, I had an epiphany: what if we just stop trying to make everything work together? What if we actually &lt;em&gt;isolate&lt;/em&gt; different domains from each other by default, and only let them talk when we explicitly want them to?&lt;/p&gt;

&lt;p&gt;That's exactly what OpenOctopus does.&lt;/p&gt;

&lt;h2&gt;
  
  
  How OpenOctopus Does Domain Isolation
&lt;/h2&gt;

&lt;p&gt;So here's the core idea: every domain of your life gets its own &lt;em&gt;realm&lt;/em&gt;. A realm is a completely isolated context space with its own database, its own conversation history, its own vector index. By default, realms don't talk to each other. There's a context firewall between them.&lt;/p&gt;

&lt;p&gt;For example, I have these realms right now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;personal/family&lt;/code&gt; (kid stuff, family planning, home stuff)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;work/openclaw&lt;/code&gt; (my main open source project work)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;work/openoctopus&lt;/code&gt; (this project, obviously)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;health/tracking&lt;/code&gt; (workouts, diet, doctor appointments)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;travel/planning&lt;/code&gt; (current and future travel)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;finance/taxes&lt;/code&gt; (you don't want this getting mixed with anything else, trust me)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;side-project/english-agent&lt;/code&gt; (another AI project I've been working on)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When I'm working in the &lt;code&gt;work/openoctopus&lt;/code&gt; realm, the AI literally cannot see anything from &lt;code&gt;personal/family&lt;/code&gt; unless I explicitly pull something over. The context is clean. It's small. It's focused. No garbage. All the relevant stuff is there, and nothing else.&lt;/p&gt;

&lt;p&gt;Does that sound restrictive? At first, I thought it would be too. But actually, it's exactly how human thinking works. When you're sitting at your desk working, you're not actively thinking about your kid's dentist appointment next week. You're focused on work. When you're at home with your family, you're not thinking about that tricky backend bug you're debugging. Your brain naturally switches between different contexts and keeps them separated.&lt;/p&gt;

&lt;p&gt;We're just copying that into AI agents.&lt;/p&gt;

&lt;p&gt;Let me show you what this looks like in actual code. Here's a simple example of creating a new realm and starting a conversation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Initialize OpenOctopus&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;OpenOctopus&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@openoctopus/core&lt;/span&gt;&lt;span class="dl"&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;octopus&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;OpenOctopus&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;dataPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;~/.openoctopus/data&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;defaultModel&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-4o&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="c1"&gt;// Create a new realm for your side project&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;projectRealm&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;octopus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;realms&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;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;side-project/my-cool-app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Building my new React app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;work&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="c1"&gt;// Start a conversation in the isolated realm&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;conversation&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;projectRealm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;conversations&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;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Debugging CORS error&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;await&lt;/span&gt; &lt;span class="nx"&gt;conversation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addMessage&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;I keep getting this CORS error when I call my API from React. What am I doing wrong?&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="c1"&gt;// Only context from this realm is sent to the AI&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&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;conversation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateResponse&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="nx"&gt;response&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See? The conversation is completely isolated. Only messages from this specific conversation in this specific realm are included. No random old stuff from other parts of your life sneaking in.&lt;/p&gt;

&lt;p&gt;When &lt;em&gt;do&lt;/em&gt; you want to share something between realms? That's easy — you explicitly share it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Get the flight confirmation from travel realm&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;travelRealm&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;octopus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;realms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;travel/planning&lt;/span&gt;&lt;span class="dl"&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;flightConfirmation&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;travelRealm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;confirmation-123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Share it to your work calendar realm&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;workCalendar&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;octopus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;realms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;work/calendar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;workCalendar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shareMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;flightConfirmation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;note&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Need to block time for this conference trip&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Explicit is better than implicit. If you want information to cross the context firewall, you have to explicitly allow it. No more accidental leakage.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real-World Edge Cases That Broke Our "Perfect" Architecture
&lt;/h2&gt;

&lt;p&gt;Okay, I've been selling you this idea pretty hard. But this wouldn't be a honest article if I didn't tell you about all the edge cases that broke our initial design. Because when you build something that's supposed to handle real life, stuff gets messy.&lt;/p&gt;

&lt;p&gt;We started with this beautiful, clean architecture. Every realm isolated, strict boundaries, everything neat and tidy. Then we started using it in real life, and we hit problem after problem. Here are the biggest ones we didn't anticipate:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The GPS Problem
&lt;/h3&gt;

&lt;p&gt;Wait, GPS? What does that have to do with domain isolation? Well, when you're out and about, you might be at work, but you need to ask your agent "is there a coffee shop near here that's open?" That question doesn't belong exclusively to any single realm. It's a cross-domain question that needs location data from your system, nearby points of interest from somewhere, maybe your current calendar from work.&lt;/p&gt;

&lt;p&gt;Our initial strict isolation completely broke here. We had to add a concept called &lt;em&gt;global shared facts&lt;/em&gt; — things like your current location, current time, your basic preferences, your contact list, that are available to every realm by default. But they're read-only. They don't pollute your conversation history. That solved the GPS problem without opening the floodgates.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The Data Quality Nightmare
&lt;/h3&gt;

&lt;p&gt;Different domains have different data quality standards. Your personal journal has messy, half-formed thoughts. Your work project documentation should be more structured. Your recipe collection needs different metadata than your travel itinerary. When everything is in one big context, the bad data pulls down the good data.&lt;/p&gt;

&lt;p&gt;Our solution? Every realm can have its own schema and validation rules. You want your journal to be free-form? Fine. You want your project tasks to have status, priority, deadlines? Great. The schema stays with the realm, so you don't get cross-contamination.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The Trust Spiral Trap
&lt;/h3&gt;

&lt;p&gt;This one's interesting. We started with really strict isolation — nothing gets out unless you explicitly approve it. What happened? Users got spammed with "this realm wants to share this information with that realm, do you approve?" every five minutes. It was so annoying that people just started clicking "approve always" and we were back to the original problem. Too much friction killed adoption.&lt;/p&gt;

&lt;p&gt;We learned that perfect security isn't the goal — usable security is. So we added different trust levels between realms. If two realms are in the same general domain (like two different work projects), you can set them to "low-trust" (require approval for sharing) or "high-trust" (automatic sharing allowed). Most of the time, same-domain sharing is fine, so you don't get spammed. Cross-domain sharing (like work -&amp;gt; personal) still requires explicit approval. That's the sweet spot we found.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. The Performance Degredation Curve
&lt;/h3&gt;

&lt;p&gt;We started with every realm having its own separate vector index for RAG. That's great for isolation, but what happens when you have 20+ realms? You end up doing 20 different embedding searches when you do a cross-realm query. It gets slow. Real slow.&lt;/p&gt;

&lt;p&gt;We solved this with a two-layer embedding approach: each realm has its own local index for conversation within the realm, and there's a shallow global index that only indexes the titles and explicit cross-realm shares. That keeps most queries fast (they're just local to the realm) and cross-realm queries still work without being unbearably slow.&lt;/p&gt;

&lt;p&gt;The biggest lesson I learned from all these edge cases? &lt;strong&gt;Perfect is the enemy of good.&lt;/strong&gt; We spent the first two months trying to build the perfect, theoretically pure isolated system. It didn't work in real life. We had to step back, accept that we couldn't handle every edge case perfectly, and build in graceful degradation.&lt;/p&gt;

&lt;p&gt;That's a lesson I carry with me everywhere now. A system that works 95% of the time and is usable is way better than a system that works 100% of the time in theory but is so annoying nobody wants to use it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pros and Cons: Honest Evaluation After Eight Months of Daily Use
&lt;/h2&gt;

&lt;p&gt;I'm not here to sell you on OpenOctopus as the perfect solution for everybody. It's not. It works really well for what it's designed for, but it has tradeoffs. Let me be completely transparent:&lt;/p&gt;

&lt;h3&gt;
  
  
  Pros
&lt;/h3&gt;

&lt;p&gt;✅ &lt;strong&gt;Context actually stays clean&lt;/strong&gt; — This is the big one. After eight months of daily use, I can honestly say that the "wrong information" problem has dropped by like 90%. When I ask my agent something in the work realm, it's only working with work stuff. No more random personal stuff popping up in the middle of a technical discussion.&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Better privacy by default&lt;/strong&gt; — Because everything's isolated, if one realm gets compromised (unlikely, but possible), the damage is contained. Also, you don't have all your eggs in one basket encryption-wise. For sensitive stuff like finance or health, that's a big deal.&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Faster responses&lt;/strong&gt; — Smaller, focused context means fewer tokens sent to the API. Lower costs, faster responses, less chance of hitting context limits. Everybody wins.&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;It fits how I actually think&lt;/strong&gt; — I don't think everything all at once. I switch between different areas of my life. OpenOctopus just gets that. It feels natural, not forced.&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;It's open source and self-hosted&lt;/strong&gt; — All your data stays with you. No third-party AI service holds all your personal life data. That's huge for me. I value my privacy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cons
&lt;/h3&gt;

&lt;p&gt;❌ &lt;strong&gt;It's still early&lt;/strong&gt; — This is beta software. We've been using it daily for eight months, but there are still rough edges. The UI isn't as polished as some commercial alternatives. You need to be a little bit technical to set it up right now.&lt;/p&gt;

&lt;p&gt;❌ &lt;strong&gt;Extra step for cross-domain questions&lt;/strong&gt; — If you frequently ask questions that span half a dozen different domains, you'll need to explicitly pull everything together. That's extra friction compared to just dumping everything into one big context. It's a tradeoff — clean default context vs convenience for cross-domain stuff. In practice, most of my daily questions are within a single domain anyway, so it's worth it for me. But your mileage may vary.&lt;/p&gt;

&lt;p&gt;❌ &lt;strong&gt;More moving parts&lt;/strong&gt; — Multiple databases, multiple indexes, multiple everything. There's more that can go wrong compared to a simpler single-context approach. We've stabilized it, but it's still more complex under the hood.&lt;/p&gt;

&lt;p&gt;❌ &lt;strong&gt;Mobile app isn't done yet&lt;/strong&gt; — We have a working progressive web app, but we don't have a native app in the app stores yet. If you want to use this primarily on your phone, you might be frustrated right now. We're working on it, though!&lt;/p&gt;

&lt;p&gt;So who is this for? If you're a builder, someone who likes to tinker, you value your privacy, you've been frustrated with AI agent context chaos — you'll probably like OpenOctopus. If you just want something that works out of the box with a polished UI and you don't care about self-hosting, this probably isn't for you &lt;em&gt;yet&lt;/em&gt;. We'll get there, but we're not there yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Personal Journey: Why I Kept Working On This Even When It Seemed Stupid
&lt;/h2&gt;

&lt;p&gt;Honestly, there were multiple points where I thought about just abandoning this whole project. It seemed like everybody else was going the "bigger context window" route, and who was I to argue? Maybe I was just solving a problem that didn't need solving.&lt;/p&gt;

&lt;p&gt;But I kept coming back to that flight I mentioned earlier. 800k tokens, and it couldn't even tell me what time my flight was. That experience stuck with me. I knew there had to be a better way. I was the user that wasn't being served by the current approach.&lt;/p&gt;

&lt;p&gt;And the more I talked to other people building personal AI agents, the more I realized other people had the same problem. They just weren't talking about it as much as they were talking about the latest context window size announcement.&lt;/p&gt;

&lt;p&gt;So I kept going. I got up every morning, worked on it, fixed another edge case, refactored another piece of the architecture, and slowly but surely, it started to actually work. Not just in theory, but in my daily life.&lt;/p&gt;

&lt;p&gt;Now I use it every single day. It's how I organize all my AI agent work. It keeps my head clear, and it keeps my context clean. And that's why I wanted to share it with you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Want to Try It?
&lt;/h2&gt;

&lt;p&gt;If you've been struggling with context chaos in your personal AI agent, if bigger context windows haven't solved your problems, if you're tired of your AI pulling up irrelevant garbage from six months ago — go check out OpenOctopus on GitHub.&lt;/p&gt;

&lt;p&gt;The project is here: &lt;strong&gt;&lt;a href="https://github.com/reware-frame/OpenOctopus" rel="noopener noreferrer"&gt;https://github.com/reware-frame/OpenOctopus&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It's completely open source, licensed under MIT, so you can do whatever you want with it. We've got a getting started guide in the README that should walk you through setting it up if you want to give it a spin.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Do You Think?
&lt;/h2&gt;

&lt;p&gt;I'm curious — have you tried building a personal AI agent? Have you run into the context garbage dump problem? What approach did you take to fix it? Do you think domain isolation makes sense, or am I barking up the wrong tree here?&lt;/p&gt;

&lt;p&gt;Drop a comment below and let me know. I read every comment, and I'm always interested in hearing other people's experiences with this stuff. Maybe you've got a better approach, and I'd love to learn about it.&lt;/p&gt;

&lt;p&gt;Happy building!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Building Dog Agent: Why I Built an AI-Powered Community for Dog Walking Adventures (And What Broke Along The Way)</title>
      <dc:creator>KevinTen</dc:creator>
      <pubDate>Sat, 27 Jun 2026 01:12:27 +0000</pubDate>
      <link>https://dev.to/kevinten10/building-dog-agent-why-i-built-an-ai-powered-community-for-dog-walking-adventures-and-what-broke-1a2l</link>
      <guid>https://dev.to/kevinten10/building-dog-agent-why-i-built-an-ai-powered-community-for-dog-walking-adventures-and-what-broke-1a2l</guid>
      <description>&lt;h1&gt;
  
  
  Building Dog Agent: Why I Built an AI-Powered Community for Dog Walking Adventures (And What Broke Along The Way)
&lt;/h1&gt;

&lt;p&gt;Honestly, I never thought I'd be writing a technical article about building an app for my dog. Three months ago, if you told me I'd spend 80+ hours coding something just so my golden retriever could find new walking routes, I'd laugh at you. But here we are — and honestly, it's been one of the most surprisingly fun side projects I've built in years.&lt;/p&gt;

&lt;p&gt;Let me back up. My dog, Max, is three years old and has the energy of a thousand puppies. Every single day we go walking, and every single day he expects a new route. If we repeat the same route too many times, he gets bored halfway through, plants his butt on the sidewalk, and refuses to move. I'm not kidding — that's actually what started this whole project.&lt;/p&gt;

&lt;p&gt;I tried all the usual apps: AllTrails, Google Maps, even some dog-specific walking apps. But none of them really solved &lt;em&gt;my&lt;/em&gt; problem. I didn't need popular hiking trails 50 miles away from the city. I needed to find interesting, new neighborhood walks within a 2-mile radius, shared by other local dog people who actually walked them. And I wanted AI to help recommend routes that matched our energy level — sometimes we want a quick 20-minute loop, sometimes we want a two-hour adventure with lots of grass and places to swim.&lt;/p&gt;

&lt;p&gt;So I did what any engineer does when their dog is bored: I built my own app. That's how Dog Agent was born.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Dog Agent?
&lt;/h2&gt;

&lt;p&gt;Dog Agent is an open-source AI-powered community for dog walking adventures. It lets you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Share your favorite dog walking routes&lt;/li&gt;
&lt;li&gt;Discover new routes near you based on location and distance&lt;/li&gt;
&lt;li&gt;Get AI recommendations that match your energy level and dog's personality&lt;/li&gt;
&lt;li&gt;Save your favorite routes and keep track of where you've gone&lt;/li&gt;
&lt;li&gt;All photos are automatically compressed to save on storage (trust me, you need this)&lt;/li&gt;
&lt;li&gt;Privacy-first: all your personal data stays under your control, we don't sell anything to anyone&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The tech stack looks like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Backend&lt;/strong&gt;: Go (because it's fast, simple, compiles to a single binary — perfect for side projects)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mobile App&lt;/strong&gt;: React Native (so it works on both iOS and Android, I only have to write it once)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database&lt;/strong&gt;: PostgreSQL with PostGIS extension for spatial queries (more on this magic later)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI&lt;/strong&gt;: OpenAI text-embedding-3-small for route recommendations + cosine similarity matching&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage&lt;/strong&gt;: Cloudflare R2 for photos (no egress fees — game-changer for side projects)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can check out the full code here: &lt;a href="https://github.com/kevinten10/dog-agent" rel="noopener noreferrer"&gt;https://github.com/kevinten10/dog-agent&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Honestly, it's still in beta. Android testing is mostly done but we're ironing out a few kinks. But it already works well enough that a handful of local dog owners are using it every day. So I wanted to share what I learned building it — the good, the bad, and the "why did my bill jump $80 in one month" part.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Good Stuff: PostGIS Changed Everything
&lt;/h2&gt;

&lt;p&gt;When I started this project, I knew I'd need to do spatial queries — like "find all routes within 2 miles of my current location". I started off thinking I'd need a separate spatial database like Elasticsearch or something fancy. But then I remembered: PostgreSQL has PostGIS, and it's actually really good at this stuff.&lt;/p&gt;

&lt;p&gt;I learned the hard way that you don't need a fancy specialized spatial database for most side project spatial needs. PostGIS does everything I need, and it's already part of your normal PostgreSQL setup if you enable the extension.&lt;/p&gt;

&lt;p&gt;Here's basically how I set it up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Enable PostGIS extension&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;EXTENSION&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;postgis&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;EXTENSION&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;postgis_topology&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Create routes table with geography column for location&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;routes&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;gen_random_uuid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;location&lt;/span&gt; &lt;span class="n"&gt;GEOGRAPHY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Point&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4326&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;-- 4326 is WGS84, standard for GPS&lt;/span&gt;
  &lt;span class="n"&gt;distance_km&lt;/span&gt; &lt;span class="nb"&gt;DOUBLE&lt;/span&gt; &lt;span class="nb"&gt;PRECISION&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;estimated_minutes&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;difficulty&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;-- 1-5 scale&lt;/span&gt;
  &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="nb"&gt;TIME&lt;/span&gt; &lt;span class="k"&gt;ZONE&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="n"&gt;created_by&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Create spatial index — this makes queries fast!&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_routes_location&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;routes&lt;/span&gt; &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="n"&gt;GIST&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;location&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then querying for nearby routes is &lt;em&gt;so&lt;/em&gt; simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Go code example: find all routes within 2km of current location&lt;/span&gt;
&lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;`
  SELECT 
    id, name, description, distance_km, estimated_minutes, difficulty,
    ST_Distance(location, ST_SetSRID(ST_MakePoint($1, $2), 4326)) AS dist
  FROM routes
  WHERE ST_DWithin(location, ST_SetSRID(ST_MakePoint($1, $2), 4326), $3 * 1000)
  ORDER BY dist
  LIMIT 20
`&lt;/span&gt;
&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lng&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;radiusKm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. That's the whole spatial query. 100x simpler than I thought it would be, and it's fast. Even with a few thousand routes, queries return in 20-30ms. I was shocked.&lt;/p&gt;

&lt;p&gt;The key lesson here: don't overcomplicate things. PostGIS does 99% of what most people need for spatial applications. You don't need a fancy specialized solution until you have &lt;em&gt;millions&lt;/em&gt; of routes. For a side project like this, it's perfect.&lt;/p&gt;

&lt;h2&gt;
  
  
  The AI Part: Simple is Better Than Complex
&lt;/h2&gt;

&lt;p&gt;Here's the thing about AI recommendation for something like this: you don't need to train a whole model from scratch. I thought about fine-tuning a model to learn what kind of routes Max likes, but honestly... that's overkill.&lt;/p&gt;

&lt;p&gt;What I do instead is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;When a user creates a route, they write a short description: "flat walking trail with lots of grass, creek access for dogs, not too crowded"&lt;/li&gt;
&lt;li&gt;I generate an embedding for that description using OpenAI's text-embedding-3-small&lt;/li&gt;
&lt;li&gt;When a user says "I want a flat walk with creek access", I generate an embedding for their query&lt;/li&gt;
&lt;li&gt;I compute cosine similarity between the query embedding and all route embeddings&lt;/li&gt;
&lt;li&gt;Sort by similarity + distance, return the top matches&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's it. That's the whole AI recommendation system. ~50 lines of code, and it works shockingly well.&lt;/p&gt;

&lt;p&gt;Here's the embedding generation code in Go:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"context"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/sashabaranov/go-openai"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;EmbeddingService&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;openai&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;NewEmbeddingService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;apiKey&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;EmbeddingService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;EmbeddingService&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;apiKey&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="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;EmbeddingService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;CreateEmbedding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;float32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateEmbeddings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EmbeddingRequest&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Input&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SmallEmbedding3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Dimensions&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;512&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Embedding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Cosine similarity between two embeddings&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;CosineSimilarity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;float32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;float32&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;dotProduct&lt;/span&gt; &lt;span class="kt"&gt;float32&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;magA&lt;/span&gt; &lt;span class="kt"&gt;float32&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;magB&lt;/span&gt; &lt;span class="kt"&gt;float32&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;dotProduct&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;magA&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;magB&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;magA&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;magB&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;dotProduct&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;magA&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;magB&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;I use 512 dimensions instead of the full 1536 — this cuts embedding size by 2/3, and I haven't noticed any difference in recommendation quality for my use case. The cost? Insignificant. 512 dimensions is 512 floats per embedding — that's 2KB per route. Even with 10,000 routes that's 20MB. Nothing.&lt;/p&gt;

&lt;p&gt;And the cost for the OpenAI API? $0.00002 per embedding. So even generating 1,000 embeddings costs two cents. Are you kidding me? That's nothing.&lt;/p&gt;

&lt;p&gt;I was worried this approach would be too simple, but honestly — it works better than I expected. Users get recommendations that actually match what they're asking for, and I don't have to maintain any complex AI infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Surprising Pains I Didn't Expect
&lt;/h2&gt;

&lt;p&gt;Okay, let's get real. Not everything went smoothly. Here are the three biggest pains that caught me completely off guard.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Mapbox Pricing Got Me
&lt;/h3&gt;

&lt;p&gt;I started off using Mapbox for maps in the React Native app. Their free tier is 50,000 monthly active users — that sounds like way more than I'd ever need for a local dog walking app, right? Wrong.&lt;/p&gt;

&lt;p&gt;Wait, no — actually, I misread the pricing. Mapbox pricing is &lt;em&gt;per month&lt;/em&gt;, and it's based on monthly active users. But the catch: if you go over the free tier, you don't just get charged for the overage — you get charged &lt;em&gt;retroactively for all your users&lt;/em&gt;. So when I had 12 local dog owners start using it regularly, I got a bill for $80. For twelve users. That's more than my entire server cost.&lt;/p&gt;

&lt;p&gt;I was shocked. I knew Mapbox wasn't cheap, but I didn't expect that pricing model. So I'm currently evaluating alternatives — Google Maps has a better pricing model for small apps, and there's also MapLibre which is open source. I haven't switched yet, but that's definitely on the roadmap.&lt;/p&gt;

&lt;p&gt;Lesson learned: &lt;em&gt;always&lt;/em&gt; read the fine print on pricing before you integrate a third-party service. Even if it says "free tier", make sure you understand what happens when you grow past it.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Image Compression is Non-Negotiable
&lt;/h3&gt;

&lt;p&gt;When users upload photos of their walks and their dogs, those photos are huge — like 12MP from a modern phone, 4-5MB per photo. If you just store them as-is and serve them directly, you'll burn through storage and bandwidth super fast.&lt;/p&gt;

&lt;p&gt;I learned this the hard way after a week: 50 photos uploaded, and we'd already used 200MB of storage. That doesn't sound like much, but it adds up quickly — especially if the app grows.&lt;/p&gt;

&lt;p&gt;So I added automatic image compression on the client before upload. In React Native, it's pretty straightforward with the &lt;code&gt;react-native-image-compressor&lt;/code&gt; package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;ImageCompressor&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-native-image-compressor&lt;/span&gt;&lt;span class="dl"&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;compressedImage&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;ImageCompressor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;imageUri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;quality&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;maxWidth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;maxHeight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1200&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;That's it. This drops most photos from 4-5MB down to 200-300KB without any noticeable quality loss for mobile viewing. 10x smaller. Game-changer. I should have done this from day one — don't make the same mistake I did.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Privacy Matters — Even for a Dog Walking App
&lt;/h3&gt;

&lt;p&gt;Because this is a community app, users share their current location and their favorite routes. I thought about making all routes public by default, but then I thought: what if someone shares their favorite secret spot that's really quiet, and then a hundred people show up the next weekend? That ruins it for everyone.&lt;/p&gt;

&lt;p&gt;So I added privacy controls: you can choose for each route whether it's:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Public (visible to everyone)&lt;/li&gt;
&lt;li&gt;Unlisted (visible only to people with the link)&lt;/li&gt;
&lt;li&gt;Private (only visible to you)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This way, people can keep their favorite secret spots secret if they want. I think that's really important — especially for things like hiking and dog walking where overuse can ruin a good thing.&lt;/p&gt;

&lt;p&gt;And because all user authentication is done with good old email/password and JWT, and photos are stored in my own Cloudflare R2 bucket, I don't have to send user data to a third party for storage. That's a win for privacy, and it keeps costs predictable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pros and Cons: Let's Be Honest
&lt;/h2&gt;

&lt;p&gt;I think too many project READMEs just list all the good stuff and ignore the bad. So here's my honest Pros and Cons breakdown for Dog Agent, and for this approach to building an AI-powered community app:&lt;/p&gt;

&lt;h3&gt;
  
  
  Pros
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Solves a real personal problem&lt;/strong&gt;: This isn't a project built for "learn AI" or "get a VC deal" — I built it because I actually needed it. That keeps you motivated when things get frustrating.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Simple architecture, easy to hack&lt;/strong&gt;: No fancy microservices, no Kubernetes, just Go backend + React Native app + PostgreSQL. You can get the whole thing running locally in 10 minutes if you know what you're doing.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Privacy by design&lt;/strong&gt;: Users control their own data, no selling user data, everything is open source so you can self-host if you want.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cost is almost nothing&lt;/strong&gt;: For 100 active users, my total monthly cost is: Server $5, Cloudflare R2 $0.10, OpenAI embeddings $0.50. That's it. Way cheaper than I expected.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AI doesn't have to be complicated&lt;/strong&gt;: Simple embeddings + cosine similarity gets you 80% of the way for most recommendation use cases, and it's super cheap and easy to maintain.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Cons
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Still beta, Android not fully tested&lt;/strong&gt;: I'm an iOS user, so Android testing has been slower. If you're an Android developer and want to help, PRs are welcome!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Mapbox dependency still needs replacing&lt;/strong&gt;: As I mentioned earlier, the pricing is scary for growth. We're working on switching to MapLibre.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No social features yet&lt;/strong&gt;: No comments, no likes, no following other dog walkers. That's on the roadmap, but it's not done yet.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Requires your own OpenAI API key if you self-host&lt;/strong&gt;: That's not a big deal for developers, but it does mean non-technical users can't just spin it up easily.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Recommendation quality depends on user descriptions&lt;/strong&gt;: If people write bad descriptions, recommendations are bad. That's inherent to this approach. I'm working on automatically extracting better descriptions from metadata, but it's not perfect yet.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What I Learned Building This
&lt;/h2&gt;

&lt;p&gt;Honestly, this project surprised me. I started it as a silly side project to keep my dog happy, and I ended up learning a bunch of stuff I didn't expect:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The best side projects solve your own problems&lt;/strong&gt;: If you have a real problem you're actually annoyed by, you'll stick with it longer and build something better than if you build something you don't care about just because it's trendy.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Simple almost always beats complex&lt;/strong&gt;: I didn't need a fancy vector database — PostgreSQL does the job just fine for this scale. I didn't need to fine-tune a large language model — simple embeddings work great. Keep it simple.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Pricing surprises will get you&lt;/strong&gt;: Always check third-party pricing multiple times. I thought I understood Mapbox pricing, and I still got surprised.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Privacy isn't just for "big apps"&lt;/strong&gt;: Even a small dog walking app needs to think about privacy — both for users and for the places they share. Giving users control over who can see their routes is the right thing to do.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Dog owners love talking about their dogs&lt;/strong&gt;: Who knew? Local dog owners have been really excited about this app, and the community is growing faster than I expected. People actually want this.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Try It Out
&lt;/h2&gt;

&lt;p&gt;If you're a dog owner who gets bored walking the same route every day, or you're a developer who wants to hack on an open source AI side project, go check it out: &lt;a href="https://github.com/kevinten10/dog-agent" rel="noopener noreferrer"&gt;https://github.com/kevinten10/dog-agent&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's completely open source, you can self-host it, you can contribute, you can fork it and build your own version. All I ask is that you star the repo if you think it's interesting — that helps other people find it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;So here we are — three months and 80+ hours later, we have a working AI-powered dog walking community app that my dog actually approves of (he still sometimes refuses to move, but that's just him being a golden retriever). It's not perfect, but it works, it solves a real problem, and I had a ton of fun building it.&lt;/p&gt;

&lt;p&gt;I think that's what side projects are supposed to be about, right? You don't have to build the next unicorn. Sometimes you just build something that makes your daily life a little better, and maybe it helps other people too.&lt;/p&gt;




&lt;h2&gt;
  
  
  Your Turn
&lt;/h2&gt;

&lt;p&gt;Have you ever built a side project to solve a really specific personal problem? Did it turn into something bigger than you expected? And if you're a dog owner — have you ever had your dog refuse to walk because they were bored of the route? Let me know in the comments below!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>community</category>
    </item>
  </channel>
</rss>
