
Software Development
A Modular Application for a Growing Product
Owned data and explicit module interfaces separate business capabilities within a shared application, supporting focused changes, testing and controlled migration.
Explore the solutionSolution design
Feature changes have a smaller, understandable reach
The application architecture separates business capabilities through owned data and explicit interfaces within a manageable shared deployment. Modules expose supported operations instead of direct access to internal tables. Tests protect caller expectations and important customer journeys during migration. Independent deployment remains tied to a specific workload or operating need, with decisions recorded alongside the boundaries they govern.
Business capabilities own their rules, records and public operations within a shared deployment. Callers use documented interfaces instead of internal tables. Teams can change implementation within those boundaries and assess independent deployment when a specific workload or operating requirement warrants it.
- Business rules sit with the capability that owns them
- Cross-module data changes use explicit operations
- Service extraction remains a deliberate operating decision
- Business context
- A product business extending an established application into additional customer workflows and commercial offerings.
- Core capability
- Software Development
A small feature can reach into unexpected places
A revised subscription rule affects reporting, administration screens and background jobs that read the same tables. The requested feature may be small, but engineers have to discover those dependencies before they can release it safely. As the team grows, fewer people can explain all the places that rely on an internal field.
The architecture gives each business capability a defined responsibility and a public interface. Its purpose is visible in the next change: the team can find the rule, identify affected callers and test their agreement. The product continues to run while those boundaries are introduced, rather than waiting for a complete rewrite.
Business decisions define the boundary
Customer journeys, record changes and support incidents are mapped to the code that handles them. Capabilities with distinct rules and vocabulary form candidate modules. Billing may depend on account identity without owning the account itself. That distinction separates a necessary business relationship from the convenience of reading another module's tables directly.
Proposed boundaries are checked against planned features. A new product option, account transfer or approval rule shows whether the interface expresses a useful operation or simply exposes internal fields. Common work should not require excessive negotiation between modules. The boundary changes when these examples reveal that related decisions have been split in the wrong place.
Solution scope
- Product journey and dependency assessment
- Module interfaces and data ownership
- Application events and reporting access
- Incremental migration of existing capabilities
- Contract checks and release guidance
The modules can share a deployment
A modular monolith retains one deployable application while modules own their business logic and data access. Calls stay within the process where appropriate, keeping local development and transactions manageable. Dependency checks prevent unrelated code from bypassing a module's interface simply because the implementation is available in the same repository.
Each module documents its public operations, records, allowed dependencies and failure responses. A folder name alone does not establish independence. Some boundaries can remain provisional while product needs become clearer. Independent deployment is considered when a capability needs its own scaling, release or security arrangements, with the added operating work included in that decision.
Other modules ask for an operation rather than write a table
A module controls writes to its records. Callers request a defined business operation or consume an approved representation of the data. Read models combine information where a screen needs it, with freshness made explicit. Reporting has a supported route to the information it requires instead of relying indefinitely on every internal schema remaining unchanged.
Immediate decisions use synchronous calls when the user needs an authoritative answer. Follow-on tasks, such as updating search or sending a message, can use events. Those events describe completed facts, have versioned contracts and are published durably. Consumers recognise repeats so a retry does not perform the same business action twice.
The operational flow
Map the change
Identify the journeys, rules and callers associated with a business capability.
Define the interface
Specify owned records, public operations and error behaviour.
Redirect callers
Move access behind the contract while checking existing behaviour.
Release safely
Verify data changes, customer journeys and recovery actions.
Review the boundary
Use feature and incident evidence to refine the module or assess separate deployment.
The interface describes what happens when a call fails
Operations define validation errors, permissions, conflicts and unavailable dependencies as well as successful responses. A subscription decision does not wait indefinitely for a notification service. The core state change and its follow-on message have separate completion requirements, allowing the application to preserve the decision while making the failed notification available for recovery.
Authorisation applies where protected data is read or changed, including calls that do not originate in a browser screen. Request identifiers connect diagnostic records across modules without copying sensitive payloads into logs. Support can distinguish a business rejection from a technical failure and determine whether repeating the operation is safe.
One capability moves behind its interface
The migration establishes an interface around one understood capability, then moves its rules and redirects callers in stages. Checks capture important existing behaviour before the change. Contract tests focus on what callers are entitled to expect, including error cases. Moving files without changing direct access would leave the original dependency problem in place.
Data changes follow compatible steps: introduce the new structure, move and verify records, switch callers and retire the old path after the recovery window. Unexpected dependencies are reviewed rather than forced into the initial design. Each release demonstrates the customer journey and the ability to diagnose or reverse a consequential failure.
Teams can change internals without reopening every agreement
Module owners review public contract changes and respond when their capability fails. Short decision records explain significant choices, alternatives and the conditions for revisiting them. Automated checks catch forbidden dependencies early. These controls make routine internal changes possible without requiring a central architect to approve every feature.
Consumers receive a defined migration path when an interface changes. Deprecation identifies what will be removed and what replaces it. The architecture backlog stays tied to specific delivery or operating problems, such as repeated coordination on a billing change or difficulty diagnosing an import failure, rather than accumulating refactoring work without an observable purpose.
The next feature tests whether the boundary works
Feature reviews record the modules touched, the coordination required and unexpected regressions across a boundary. A high test count is less useful than a check that exposes a broken agreement between callers. Incident reviews also examine whether the responsible capability and recovery action were clear from the available diagnostics.
A module becomes a candidate for extraction when its operating needs differ enough to justify a separate service. That assessment includes network failure, deployment coordination, monitoring and support responsibility. Until those needs exist, the shared application can retain its simpler operating model while continuing to enforce useful boundaries inside it.
The choices behind the solution
One deployment with internal boundaries
Use a modular monolith where separate services have no clear operating benefit.
Data ownership and interface discipline address hidden coupling without immediately adding network coordination.
Owned writes and supported reads
Modules expose business operations and deliberate read representations.
Direct table access keeps callers dependent on details the owning team should be able to change.
Capability-by-capability migration
Move complete business responsibilities behind reviewed contracts.
Each step preserves working behaviour and tests the boundary against real product changes.
How the solution is evaluated
These measures define the evaluation criteria for the workflow, its controls and the quality of completed tasks.
Reach of a feature change
Measure: Review touched modules and coordination required for representative product work.
Success criteria: Related rules change together and cross-module work has an explicit reason.
Contract-related regressions
Measure: Classify failures caused by undocumented assumptions or bypassed interfaces.
Success criteria: Checks detect broken agreements before dependent capabilities fail in use.
Release and recovery clarity
Measure: Rehearse deployment and diagnosis for the capability being changed.
Success criteria: The team can identify the affected responsibility and apply a safe correction.
The architecture gives the team a way to make the next feature easier to reason about. Rules have a home, data changes have an owner and callers have an agreement they can test. A separate service remains available where it earns its operating cost, without becoming a prerequisite for a maintainable product.
