One application does not require every component to know every other component’s internals. Many services do not automatically create good boundaries either. A modular monolith can offer one deployment with explicit business ownership and contracts. It is a deliberate design approach, not something achieved merely by creating folders.
Start with business ownership
A membership product contains plans, subscriptions, payments and benefits. Technical folders named controllers and services do not tell you who decides that a membership is active or who verifies a successful payment. Assign those decisions to modules before choosing dependencies.
Define a public contract
Memberships can consume a verified payment result without editing payment records directly. A payment adapter should not understand every plan’s benefit rules. NestJS modules organize controllers and providers and expose selected providers through exports. That mechanism supports boundaries, but the business design must still define them.
Payments -> PaymentConfirmed contract -> Memberships
Memberships -> EntitlementGranted contract -> Benefits
Notifications consumes outcomes; it does not decide eligibility.Separate decisions from side effects
Activating a membership is a business transition. Sending a welcome message is a side effect. A mail outage should not undo a valid payment or leave eligibility ambiguous. Persist the transition and deliver notifications through a retryable path. Where delivery must survive a crash, use a durable event record rather than only an in-memory callback.
Address circular dependencies
If orders import payment internals and payments import order internals, review ownership. A small orchestration layer can coordinate public contracts without either module owning the other. A large shared folder often relocates coupling rather than solving it. Share stable contracts and genuine utilities, not every convenient business helper.
Test the boundaries
Test through public interfaces: one confirmed payment grants one entitlement, and a notification failure leaves that decision intact. Where supported, enforce import restrictions on internal module files. Watch how many modules a routine change touches; broad edits may reveal misplaced responsibilities.
Extract services for measured reasons
Independent scaling, team ownership or a specific failure-isolation need can justify extraction. Include networking, authorization, tracing, contract compatibility and deployment coordination in the cost. Good modular boundaries create an option to split later; they do not require doing so. Keep the architecture proportional to the product and the people operating it.
Scenario: adding benefits to a membership plan
Changing plan benefits should not require editing the payment adapter. Plans describe benefits, memberships determine entitlement, and benefit consumption enforces usage rules. Where the product must honor historical terms, keep the relevant subscription terms instead of always reading today’s plan. Resolve that business decision before translating relationships into service calls.
An early warning about weak boundaries
Watch ordinary feature work. If adding a display field requires changes to payments, notifications and membership rules, a shared object may expose too much internal detail. Reduce the contract to what its consumer genuinely needs. Judge modularity by which decisions can change safely without understanding unrelated internals, not by the number of folders.
Official references
These references document the tools discussed. Examples and design decisions are illustrative and should be adapted to the project and its versions.
Prepared by: Noor Yasser
Working through a similar engineering challenge?
I help teams turn architecture decisions into a clear scope and dependable, reviewable implementation.




