The Pragmatist’s Engine: De-Microservicing and Safe Deletion in Boring Stacks
"A decade of resume-driven architecture convinced engineering teams that modest internal workloads demanded multi-region Kubernetes clusters orchestrating dozen"
A decade of resume-driven architecture convinced engineering teams that modest internal workloads demanded multi-region Kubernetes clusters orchestrating dozens of microservices over gRPC and Kafka. The operational bill for this distributed complexity has come due: fragmented transactional boundaries, compounding network failures, observability bloat, and paralyzed release cycles.
Boring technology is not nostalgic traditionalism. It is a calculated strategy to conserve cognitive bandwidth and maximize uptime. Consolidating distributed services into a modular monolith—anchored by battle-tested engines like PostgreSQL and SQLite—is the definitive architectural correction of the current cycle. Making this transition safely requires disciplined runtime migration patterns that eliminate legacy code paths without downtime or data corruption.
1. The Compounding Physics of Distributed Sprawl
Microservices replace deterministic in-memory function calls with unreliable network hops. When fulfilling a single business operation requires traversing networked services, availability degrades exponentially.
Assuming each independent service exhibits an individual availability metric , the composite upper-bound availability of a synchronous, non-redundant call chain is:
If an operational path traverses services, each maintaining three nines of availability (), the aggregate availability upper bound is:
This topological overhead guarantees over eight hours of downtime per month before factoring in configuration drift, transient network partitions, or bad deployments.
Latency follows the same compound curve. Total latency is not merely the sum of compute times; it is the accumulation of serialization, network transit, TLS handshakes, and transport queues:
In contrast, an in-process call passes memory pointers directly. Execution latency drops to nanoseconds with deterministic reliability.
Distributed Architecture (Compounding Latency and Failure Points):
Client -> API Gateway -> Auth Svc -> Order Svc -> Inventory Svc -> DB
[Network] [Network] [Network] [Network]
Modular Monolith (In-Memory Isolation):
Client -> [ API Layer -> Auth Module -> Order Module -> Inventory Module ] -> PostgreSQL
(Direct Pointer Calls)
2. The Exhaustive Versatility of "Boring" Persistence
Before splitting a data model across isolated document stores, distributed caches, and message brokers, evaluate what modern relational engines provide natively.
PostgreSQL as the Unified Engine
A properly tuned PostgreSQL 16+ instance routinely supersedes three or four auxiliary datastores:
- Relational Core: Referential integrity with partial, expression, and GiST/GIN indexing.
- Document Store: Native
JSONBwith containment operators (@>) matches the performance of dedicated document databases for most access patterns. - Ad-hoc Task Queues: Atomically locked queues via
SELECT ... FOR UPDATE SKIP LOCKEDeliminate Redis or RabbitMQ dependencies for throughput demands below 25,000 tasks per second. - Vector Embeddings: In-tree vector similarity search with
pgvectorremoves the need for standalone vector databases.
-- Atomic, contention-free queue processing inside Postgres
WITH next_job AS (
SELECT id, payload
FROM job_queue
WHERE status = 'queued' AND run_at <= NOW()
ORDER BY priority DESC, run_at ASC
LIMIT 1
FOR UPDATE SKIP LOCKED
)
UPDATE job_queue
SET status = 'processing', locked_at = NOW()
WHERE id = (SELECT id FROM next_job)
RETURNING id, payload;
SQLite for Boundary Caching and Edge Isolation
For read-heavy local caches, configuration distribution, and localized analytics, SQLite running in Write-Ahead Logging (WAL) mode matches or outpaces external memory tiers while eliminating operational infrastructure:
PRAGMA journal_mode = WAL;
PRAGMA synchronous = NORMAL;
PRAGMA cache_size = -64000; -- 64MB memory cache
PRAGMA busy_timeout = 5000;
3. De-Microservicing: Reconstructing the Modular Monolith
De-microservicing does not mean throwing unorganized code into a shared repository. It means assembling distinct domain runtimes into a strictly partitioned modular monolith.
Boundaries are enforced at the compiler level rather than through network interfaces. Language-level module visibility patterns establish domain boundaries:
// billing/ports.ts
export interface InvoicePayload {
readonly accountId: string;
readonly amountCents: number;
readonly idempotencyKey: string;
}
export interface BillingModule {
generateInvoice(payload: InvoicePayload): Promise<string>;
}
// billing/internal/service.ts (Private to domain package)
export class InternalBillingService implements BillingModule {
async generateInvoice(payload: InvoicePayload): Promise<string> {
// In-process transaction execution within shared PostgreSQL instance
return "inv_live_01HZX87...";
}
}
// Package boundary enforcement via package.json / tsconfig.json
{
"name": "@app/billing",
"exports": {
".": "./ports.ts"
},
"private": true
}
By restricting callers to ports.ts, you prevent illegal circular dependencies while swapping network-bound serialization for in-memory references.
4. Zero-Downtime Migration and Safe Code Deletion
Consolidating services under live traffic requires a reliable migration pattern: the in-process shadow pipeline.
Request Pipeline:
+-------------------+
| Incoming Request |
+---------+---------+
|
+-----------------+-----------------+
| |
[Primary Path: Legacy] [Shadow Path: Modular]
| |
v v
Executes Database Executes Database
Transactions Transactions
| (Rolled Back)
+-----------------+-----------------+
|
v
[ Comparator Engine ]
|
Logs Metrics / Parity Drift
|
v
Returns Primary Result
The Shadow Execution Pattern
Run new monolith modules concurrently alongside legacy remote calls to verify behavioral parity under production workloads:
import { Logger } from './logger';
export class SafeMigrationProxy<TInput, TOutput> {
constructor(
private readonly legacyRunner: (input: TInput) => Promise<TOutput>,
private readonly candidateRunner: (input: TInput) => Promise<TOutput>,
private readonly comparator: (a: TOutput, b: TOutput) => boolean,
private readonly logger: Logger
) {}
async execute(input: TInput): Promise<TOutput> {
const legacyResult = await this.legacyRunner(input);
// Fire candidate path asynchronously to avoid blocking the client response
void (async () => {
try {
const candidateResult = await this.candidateRunner(input);
const matches = this.comparator(legacyResult, candidateResult);
if (!matches) {
this.logger.warn("Divergence detected in candidate path", {
input,
legacy: legacyResult,
candidate: candidateResult
});
}
} catch (err) {
this.logger.error("Candidate path threw uncaught exception", { err });
}
})();
return legacyResult;
}
}
The 4-Phase Deprecation Protocol
- Branch by Abstraction: Introduce an explicit interface between existing call sites and the legacy microservice client. Route of traffic through this interface.
- Shadow Reads & Dark Writes: Route asynchronous duplicate calls through the consolidated monolith module. For write paths, use non-committing transactions or shadow test schemas to validate performance and catch data divergence.
- Cutover Authority: Flip routing flags to make the modular monolith the primary source of truth. Retain the legacy service proxy in a secondary shadow role for rapid rollback capability if anomalies arise.
- Surgical Deletion: Remove feature flags, abstraction wrappers, and remote transport code. Drop unreferenced database columns, foreign keys, and deprecated network-facing endpoints.
5. The Radical Simplicity Dividend
Engineering maturity is not measured by the number of distributed components you manage. It is measured by the volume of business value delivered relative to operational overhead.
Consolidating architectures around a cohesive relational database, strict in-process domain isolation, and systematic code deletion provides immediate dividends:
- Reproducible Local Development: Spin up the entire environment locally using a single binary or container command.
- Deterministic Transactions: Native ACID transactions replace complex two-phase commits, saga orchestrators, and eventual consistency workarounds.
- Zero Serialization Overhead: In-memory execution eliminates network marshaling, serialization latency, and connection pool exhaustion.
Build systems to solve business problems, not to reflect distributed systems theory. Simplify your topology, prune dead infrastructure, and let boring technology handle the load.
About the Author
huud
@huud
Systems architect and software engineer building high-performance distributed platforms.