惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Engineering at Meta
Engineering at Meta
J
Java Code Geeks
I
InfoQ
腾讯CDC
Vercel News
Vercel News
IT之家
IT之家
V
Visual Studio Blog
P
Proofpoint News Feed
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
G
Google Developers Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
博客园 - 叶小钗
有赞技术团队
有赞技术团队
月光博客
月光博客
Martin Fowler
Martin Fowler
量子位
L
LangChain Blog
B
Blog
Last Week in AI
Last Week in AI
博客园 - 司徒正美
Microsoft Security Blog
Microsoft Security Blog
博客园 - 聂微东
Microsoft Azure Blog
Microsoft Azure Blog
A
About on SuperTechFans

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Architecture as a Cost Lever: Cutting Build Times by Spli...
The AX code · 2026-06-12 · via DEV Community
Cover image for Architecture as a Cost Lever: Cutting Build Times by Splitting a KMP Monolith

The AX code

Build time is a tax you pay twice: once on every engineer's attention each time they wait, and again on the CI bill every time a pipeline rebuilds the world. As the a Kotlin Multiplatform app grows — Android, iOS, desktop, and web builds; Camera implementations; a Bluetooth sync stack — a clean build turns into a coffee break, and CI minutes piled up on every push. Fixing this requires better architecture, not a better machine.

This is how splitting a monolith into modular KMP libraries with a framework-free core cut build time from a recurring cost into a rounding error — and why that's a cost decision as much as a cleanliness one.

The monolith tax

A single large module has three properties that quietly wreck build performance:

  1. Any change rebuilds (nearly) everything. Touch one file, recompile a huge compilation unit. Kotlin/Native and iOS compilation in particular scale badly with module size, so the worst case is the whole app.
  2. No parallelism. Gradle can run independent modules in parallel; one module is, by definition, a single lane.
  3. Cache misses everywhere. When unrelated code lives together, a trivial edit invalidates the build cache for things that didn't actually change — including in CI, which then rebuilds and re-tests the world on every PR.

Multiply that by a team and a busy CI queue, and you're paying real money and real flow-state for work the computer didn't need to redo.

The refactor: a core + capabilities

I split the app along its seams into independently versioned Kotlin Multiplatform libraries:

CoreLib        ← framework-free domain kernel (ports, use cases). Depends on nothing.
  ├── BluetoothLib    (BLE + encrypted sync)
  ├── CameraLib       (capture, synchronized triggers)
  ├── LocationLib / -NotificationLib / -HapticLib / -BackgroundJobLib
  ├── StorageLib / -PermissionsLib / -UILib
  └── App  ← composes the libraries

Two principles did the heavy lifting:

  • A framework-free core (hexagonal). CoreLib holds domain logic and port interfaces only — no Android, no platform SDKs. Everything else depends inward on it; it depends on nothing. That keeps the dependency graph a shallow tree instead of a tangle, so a change in one capability library can't cascade rebuilds across unrelated ones. I enforce the boundaries with ArchUnit, so they don't erode over time.
  • One responsibility per module. Bluetooth doesn't know about cameras; the UI library doesn't know about persistence. Small, focused modules are small, focused compilation units.

The result is a graph where most edits touch one leaf and its direct consumers — not the universe.

Why this is faster, concretely

  • Only what changed recompiles. Edit the Bluetooth library and Gradle recompiles Bluetooth + the app, while Camera, Location, UI, and the core are pulled from cache untouched.
  • Modules build in parallel. Independent libraries compile on separate workers instead of single-file.
  • Cache hits across builds and machines. Stable libraries' outputs are reused locally and via a remote build cache — so CI frequently doesn't compile unchanged modules at all, it downloads their cached outputs.
  • Smaller worst case. The largest thing you can be forced to rebuild is now one library, not the whole app — which matters most for the slow native/iOS targets.

Composite builds: kill the publish-to-test loop

Each library publishes as a versioned package, but during development you don't want to publish → bump version → consume just to test a one-line change against the app. Gradle composite builds let the app include a library's source directly, as if it were a local module, while keeping it a real published artifact in CI. You get the iteration speed of a monolith with the build isolation of separate modules — this is where most of the day-to-day time savings came from (roughly [~40%] lower local-iteration + CI/CD overhead in my case; measure your own with build --scan).

The cost translation

Faster builds aren't just nice — they're cheaper, on two budgets:

  • The CI / cloud bill. On GitHub Actions, Cloud Build, or similar, pipeline minutes are billed minutes. Rebuilding and re-testing only changed modules cuts minutes per PR, and a remote cache means unchanged libraries cost ~zero. Over a team and a month, that's a line item.
  • The bigger budget: engineer-hours. A developer who waits less per iteration, dozens of times a day, is the real saving — engineer time dwarfs CI dollars. Shorter feedback loops also keep people in flow, which is its own multiplier.

And there's a reuse dividend: a published BluetoothLib or LocationLib drops into the next app without recompiling or re-solving the problem.

The honest trade-offs

Modularization isn't free, and over-doing it backfires:

  • Boundaries cost up-front design. You have to find the real seams; bad ones create churn instead of removing it.
  • Too many tiny modules add overhead. Each module has configuration and graph-resolution cost; past a point you're paying more in Gradle bookkeeping than you save. Granularity is the skill.
  • Versioning + publishing discipline. Independent libraries need real release hygiene (semantic versioning, a package registry), or you trade build pain for dependency pain.
  • Composite builds have a learning curve and some sharp edges with native targets.

The win comes from splitting along capabilities with a framework-free core — not from splitting as finely as possible.