ADR-0004 — Adopt a modular monolith for MVP-1
Цей контент ще не доступний вашою мовою.
Status
Section titled “Status”Accepted — 2026-04-28
Context
Section titled “Context”MVP-1 has two real users (the author and his partner) and a Tier-2 feature scope (expenses, income, transfers, budgets, simple stats). Throughput is < 100 messages/day. The system must run on a single VPS under docker-compose (ADR-0013) with one operator.
The codebase is brand-new and has to ship within ~4 weeks of part-time work. The portfolio narrative has to support a credible answer to “why this topology”, not “we chose microservices because everyone does”.
Considered alternatives
Section titled “Considered alternatives”| Option | Summary | Pros | Cons | Outcome |
|---|---|---|---|---|
| A — Microservices | Bot, ledger, reports as separate services | Independent deploys; clean fault isolation; trendy on CV | 3× ops overhead; inter-service calls add latency and failure modes; no real reason to split at 100 msg/day; portfolio narrative becomes “I cargo-culted Netflix” | rejected |
| B — Plain monolith | One Python module with no internal boundaries | Fastest to write | Refactoring later is painful; ledger swap to TigerBeetle becomes a rewrite | rejected |
| C (chosen) — Modular monolith | One process, hexagonal layout (domain, ports, application, adapters); all run in one container | All upsides of (B) for ops; (A)‘s clean module boundaries baked in; ledger backend swap is a one-file change in adapters/ledger/ | Requires self-discipline (importing adapters.* from domain.* is forbidden — enforced by mypy & code review) | selected |
Decision
Section titled “Decision”We will run all MVP-1 functionality as one Python process packaged
in one Docker image, with internal modules separated by hexagonal
boundaries (domain, ports, application, adapters). Cross-module
calls happen via Python imports, never network.
Concretely:
- One container in
compose.ymlnamedbot. - One entry point:
python -m finance_bot. bootstrap.pyis the only place that wires concrete adapters to ports; everything else depends on interfaces.- A future split into services is a non-goal; if traffic ever justifies
it, the candidate seam is
application/↔adapters/telegram/(split the bot front-end from the domain backend), not splitting the domain itself.
Consequences
Section titled “Consequences”Positive
Section titled “Positive”- One artifact to build, push, deploy, and observe.
- Lowest cognitive overhead for solo development.
- Clean swap to TigerBeetle (the only hard module-replacement on the roadmap) is a one-adapter change — no service contract negotiation.
Negative / trade-offs
Section titled “Negative / trade-offs”- A bug in one module can crash the whole bot. Acceptable for MVP-1 (Telegram redelivers updates after restart).
- The hexagonal discipline must be enforced by mypy boundaries and code review; the language doesn’t enforce it natively.
Neutral / follow-ups
Section titled “Neutral / follow-ups”- Add an architecture-fitness test in CI (Plan 7) that asserts no
module under
domain/orapplication/imports anything underadapters/.
References
Section titled “References”- Sam Newman, Monolith to Microservices, Ch. 1 — when to split.
- TigerBeetle docs/coding/data-modeling.md — adapter swap is the only planned cross-module replacement, and that’s a library swap, not a service split.