Context
At Mews, I prepared a proposal for how the boundaries of the Billing domain inside the monolith could be gradually clarified. It was not a finished rewrite of the system into a separate service, but rather a path toward clearer boundaries over time.
The value of the proposal was mainly in the analysis: understanding why Billing was hard to separate, where the hidden dependencies were, and what would need to change for the domain boundaries to become more visible.
Problem
The problem was not only the size of the codebase. Billing was connected to the rest of the system through shared entities, Entity Framework loaders, navigation properties, a shared context, and other implicit dependencies.
Changing a loader in one private method could make the application fail somewhere completely different, in a part of the system that did not seem related to the change at first glance, but worked with the same entities and quietly relied on someone else having already loaded the required relationships.
Another problem was that domain logic worked directly with the database model. Entity Framework entities were used in the business layer, so the boundary between persistence and domain logic was blurred. It was not easy to narrow the domain, hide some properties, or run the same logic over a different data source. If the database entity required a property, that requirement also leaked into places where business logic ideally did not need to see it at all.
In a monolith, this was made harder by ownership across teams and domains. For example, the reservation domain, the billing domain, and accounting could all work with the same order items, but each domain needed to understand them slightly differently.
For reservations, an item could represent what the guest ordered. For Billing, it mattered because of how it was charged, split between accounts, or placed on an invoice. For accounting, another view could emerge around price, changes, or financial correctness.
The side effect was that some features became difficult to design well. One item had to satisfy the expectations of reservations, billing, and accounting at the same time. As soon as a new feature helped one domain, it could start breaking the rules of another.
Even seemingly simple things, such as changing a price or splitting an item, were not simple. It was not only a matter of changing one value. The change had to be considered from the perspective of charging, invoicing, accounting, and historical data.
The result was that Billing did not have its own clearly bounded model. It worked with objects that also carried the expectations of other domains.
Proposed solution
The proposal was not to immediately cut Billing out into a new service. In a system like that, such a move would have been too risky. It made more sense to first create a clearer boundary inside the monolith itself.
The main idea was to stop giving billing logic direct access to the entire shared entity graph. Instead, it should work with narrower contracts that describe which data a given rule actually needs.
That is why I proposed using interfaces to trim large entities and their navigation properties. It was not the final target model, but a transition step: business logic could still receive data from existing Entity Framework entities, but it would no longer see everything those entities contained.
The next step was to replace implicit loaders with manual loading and mapping of data for a specific flow. Instead of a universal object graph that could keep expanding through navigation properties, a specific use case would receive its own data tree: for example, a view of items from the Billing perspective or a view of items from the reservation perspective.
That would separate what an “item” means for a given use case. Billing logic would not have to work with a generic shared entity that also carries expectations from reservations and accounting. It would receive a model where it is clear which relationships are loaded, which properties are needed, and where the tree ends.
The proposal also included a Billing module facade. The point was not to hide complexity behind one large method, but to give other parts of the system a clearer entry point into billing logic. If surrounding code stops reaching directly into Billing’s internal entities and services, it becomes easier to see what is the public contract of the domain and what is an implementation detail.
Outcome
The proposal was not fully implemented in the end, but for me it was one of the most complete architecture pieces I prepared at Mews. It was not just an idea to “separate Billing”. It was an attempt to describe a concrete path from a connected entity graph toward clearer domain boundaries without a large rewrite of the system.
The most valuable part for me was that the proposal was not useful only for Billing. Billing was the concrete domain where the problem was easy to show, but the same principle could be applied elsewhere in the monolith: narrow inputs, name contracts, replace implicit loaders with explicit mapping, and gradually turn the general entity graph into smaller models for specific use cases.
That is where the proposal felt strong to me. It did not solve only one class or one service, but described a way to gradually change the shape of a large existing system without having to rewrite everything at once.
I intentionally keep the description general, because the most important part for me is not the internal implementation plan, but the transferable principle for working with a large existing system.