When we set out to build a modern core banking platform, a microservice architecture might have seemed like the natural path. We took a different one.
The system needed to support institutional-scale banking environments, with digital assets as a first-class capability.
At the time, microservices were the prevailing approach for systems that needed flexibility and scale.
But the constraints of this domain pointed in a different direction.
Our system needed to:
Integrate into infrastructure already in place at the institutions using it
Remain straightforward and safe to operate, where complexity directly translates into cost and risk
Support financial operations where accuracy and consistency are critical
Those constraints changed the problem.
We built Lana as a modular monolith: a single system with strong internal boundaries. Here’s why.
SimplicityOperational simplicity is part of the product
The first constraint was operational simplicity and safety.
How do we ensure that operating the system remains simple and safe for the client?
If the client cannot run the system reliably, everything else becomes irrelevant.
Every additional service becomes another component the client has to deploy, monitor, and support.
Lana runs as one coherent system by default. Deployment, monitoring, and recovery are handled as one unit. There is no internally distributed application topology to coordinate across separately deployed services.
This keeps the system straightforward to operate, even in large institutional environments, and reduces the long-term operational burden for the client.
But operational simplicity should not come at the expense of scaling flexibility.
Strong module boundaries still preserve flexibility in how the system is deployed and scaled. Specific workloads, such as background processing and asynchronous jobs, can be separated when scale or operational demands justify it.
Unlike a microservice architecture, distributed deployment complexity is introduced selectively where it provides clear value, rather than becoming the default from the start.
IntegrationIntegration on the client’s terms
The second constraint was integration.
How do we ensure that the system fits into the client’s existing environment, rather than forcing a specific architecture?
Integration requirements vary. Some clients operate event-driven systems. Others rely on synchronous APIs. Many use both.
Because Lana runs as a single system, it does not depend on an internal integration model between services. No broker or communication pattern is imposed by the architecture.
This allows integration to be defined at the system boundary.
Capabilities can be exposed through event streams such as Kafka or RabbitMQ, or through REST APIs, depending on what fits the client’s environment.
The integration model stays flexible. The delivery mechanism adapts to the client’s infrastructure.
Consistency
Ensuring consistent financial operations
The third constraint was consistency.
How do we ensure that ledgers and customer balances remain correct, even as operations span multiple domains?
If ledgers and balances are not correct, the system is not usable.
Core banking operations involve customer state, product rules, approvals, accounting, controls, and audit. These flows need predictable behavior. Some require atomic execution across multiple modules.
Splitting these operations across multiple services often turns a single business operation into a distributed workflow spanning several components.
In Lana, operations that require strong consistency can execute within a single transaction across carefully defined boundaries when appropriate. They either succeed fully or fail as a whole.
This avoids large amounts of compensating workflow logic and keeps complexity where it belongs: in the business logic.
Modularity
Modularity that supports real product needs
The fourth constraint was modularity that holds over time.
How do we ensure that the system can adapt to different product needs without becoming tangled over time?
Different clients require different capabilities. Some need custody, while others rely on external providers. Product scope varies, and it changes over time.
In Lana, modules are real architectural units with controlled relationships and explicit contracts. These boundaries are enforced by the compiler.
Dependency direction is defined. Cycles are prevented. Public contracts, including events, are validated at compile time. Violations fail immediately.
The same level of modularity can be achieved with microservices, but it requires substantially more coordination between services and teams.
In Lana, these guarantees are part of the system itself.
This allows capabilities to be included or left out without hidden coupling. The platform can be configured to match the client’s product scope without accumulating unintended dependencies.
EvolutionFast and safe system evolution
The final constraint was change over time.
How do we evolve the system without turning every change into a multi-step rollout across components?
Changes often span multiple domains. A new approval step affects validation, accounting, audit, and APIs.
In many systems, this requires careful sequencing across services and managing compatibility during rollout.
In Lana, it remains a single change.
The full flow is updated, tested end-to-end, and deployed as a unit. There is no cross-service coordination, no version compatibility to manage, and no partial rollout risk.
A single codebase allows system-wide changes to be made and verified together.
This also enables more effective AI-driven development workflows. Engineers and AI agents can reason about the system as a whole, follow execution paths across modules, and safely apply changes without having to manage as much context and complexity at once.
This reduces the cost of change and increases confidence in every release.
Conclusion
Why this architecture fits Lana
Microservices are effective in environments with very large teams or strict service ownership boundaries.
Lana is built for institutional-scale banking environments and client-controlled infrastructure.
It is designed for financial operations that span multiple domains, where predictable behavior, integration flexibility, and operational simplicity are critical.
What this enables
Operational simplicity for real deployments: one coherent system to deploy and operate in client-controlled environments.
Flexible integration into existing infrastructure: works with Kafka, RabbitMQ, or REST-based integrations without imposing a specific internal architecture.
Consistent financial operations: critical flows remain predictable instead of becoming distributed workflows across multiple components.
Configurable capability boundaries with enforceable guarantees: capabilities can be included or left out without hidden coupling, enforced through explicit module boundaries.
Fast and safe system evolution: system-wide changes can be developed, verified, and deployed with lower coordination overhead and reduced cognitive complexity.
Further reading:
Shopify’s architecture: Deconstructing the monolith
A real-world example of a large-scale system that focused on strong internal boundaries within a monolith instead of distributing complexity across services.
Microservices
Martin Fowler’s overview of microservices, including the challenges of defining boundaries, maintaining consistency across services, and managing the operational complexity of distributed systems.
Microservices on-premises is an epic mismatch
Dejan Glozic’s perspective on why microservices are often poorly suited for software deployed into client-controlled environments, where operational complexity becomes the client’s responsibility.