Almost every enterprise system walks the same road: a monolith is cheapest while you're small, distributed is mandatory once you scale. The pain is the step between them — it usually means a rewrite. If a low-code platform can take you from monolith to distributed on the same code and model, it saves you the single most expensive refactor of the project's life. Here's the principle behind it.
Why the traditional path forces a rewrite
Inside a monolith you assume: local method calls, shared memory, single-DB transactions. Go distributed and every assumption breaks:
- local call → remote call (the network is unreliable; you need timeouts and retries)
- single-DB transaction → distributed transaction / eventual consistency
- shared memory → distributed cache / messaging
If your business code hard-codes the "local call" assumption, splitting it means editing every site that made that assumption. That is the root of the rewrite.
The core idea: decouple call style from deployment shape
The key: business code shouldn't care whether the callee is local or remote — the framework decides at runtime.
business call (model/interface-oriented, near/far agnostic)
│
framework call layer (runtime: local dispatch OR remote RPC)
│
┌──────┴──────┐
monolith distributed
(in-process) (cross-process RPC: auto serialize / retry / circuit-break)
Pulling that off takes a few things:
- Program against models/interfaces, never hard-code local calls in the business layer.
- A unified service-call abstraction — the same code dispatches locally as a monolith and over RPC when distributed, switched by config.
- Pluggable consistency strategy — single-DB transaction ↔ distributed transaction / eventual consistency, switched by deployment shape.
- Config-driven splitting — which modules become standalone services is config, not a code change.
Why model-driven fits this naturally
Metadata/model-driven design pulls business logic into the model layer, so call relationships are relationships between models, not hard-coded calls scattered through the codebase. Therefore:
- split boundaries can follow models/domains — clean
- the call layer is uniformly owned by the framework — business code doesn't change
- you change deployment config, not business code — which is what "smooth" actually means
Oinone's design supports exactly this monolith↔distributed switch: start fast as a monolith, split on demand at scale, same model and code, switched by config — no rewrite.
A landing checklist
□ Business layer programs to models/interfaces only — no hard-coded local-call assumptions
□ Service calls go through one abstraction (local/RPC switched by config)
□ Consistency strategy is pluggable (single-DB txn ↔ distributed txn / eventual)
□ Split boundaries follow domain models, config-driven
□ Plus: tracing, circuit breaking / rate limiting, canary releases
Try it (one command, self-hosted, ~5 min)
curl -L https://github.com/oinone/oinone-docker-shared/raw/master/oinone/docker-compose.yml -o docker-compose.yml
docker compose -p oinone up -d
# open http://127.0.0.1:88 admin / admin
"Monolith first, distributed later" saves the most expensive refactor a system ever faces. The principle isn't magic: decouple call style from deployment shape, and let the framework take over at runtime. Model-driven makes that fall out for free. Architecture evolution shouldn't be a teardown — it should be a config change and smooth growth.
Want to see how it's implemented? The source is open — a ⭐ helps more engineers find it:
Top comments (0)