Modular Boundaries Without Microservices
Comparing Muninn's NestJS modular monolith with the layered .NET backends in Culina and Elysion.
Start with the responsibilities that change together
Muninn, Culina and Elysion have different domains, but their backends share a useful constraint: explicit internal boundaries within a single API deployment.
Muninn is a NestJS modular monolith. Journals, entries, identity, attachments, synchronization and wellbeing form feature areas. Clean Architecture principles separate domain behavior and application use cases from HTTP controllers, repositories, storage adapters and AI providers. Feature-oriented vertical slices keep the code for a capability close together.
Culina and Elysion express related boundaries through .NET projects. Domain types, application use cases, persistence, integrations and HTTP hosting have distinct homes. The language and framework differ, while the underlying question remains the same: which code should know about which external concern?
A boundary should explain a dependency
Culina’s media storage illustrates a concrete boundary. Application behavior depends on storage interfaces, with local filesystem and Azure Blob Storage implementations behind them. A use case can work with media without carrying provider-specific behavior into the domain.
Muninn has a similar need around AI providers and encrypted attachment storage. Elysion separates document and file integrations from its business use cases. These are useful boundaries because the application has real external responsibilities to isolate, not because every class needs another wrapper.
Deployment and code organization are separate choices
A single API can contain modules, interfaces and independently testable behavior. None of those require turning each feature into a network service. Keeping deployment together also keeps inter-feature interactions within the application boundary.
The tradeoff is that the API is still built and deployed as a unit. Module boundaries need enforcement through dependencies, reviews and tests; a folder name cannot enforce them on its own. This is a design constraint to maintain, rather than a claim that the monolith automatically stays modular.
Let differences remain visible
Muninn’s browser encryption and offline synchronization create different pressures from Elysion’s inventory, documents and tenant administration. Culina’s structured recipes and native client create another set of needs.
The useful common ground is separation of domain behavior, application orchestration and external adapters. Beyond that, each product can organize its capabilities around its own workflows. Consistency helps when it makes dependencies predictable; it is less useful when it hides what makes a domain different.
