Module Boundaries Inside a Shared Rails Application
An anonymous property-management engagement choosing module interfaces within a shared Rails application while deferring independently deployed services.
Keep the application, change how its parts depend on each other
A property-management platform combined tenant communications, maintenance requests, rent collection and vendor coordination in a Rails application. Changes crossed business areas, making it difficult for engineers to understand the scope of a feature. Leadership was considering a rewrite or a move to microservices.
Ampity's work on this anonymous engagement focused on module boundaries inside the existing application. The selected approach retained Rails, PostgreSQL and Heroku, kept the shared database, and introduced explicit interfaces between modules. Extracting a separate service was deferred rather than treated as the first step.
That distinction matters to the case: it concerns changing code dependencies within a retained operating environment, not a completed migration to independent services and databases.
Existing contracts constrained the change
The platform connected property-management workflows with accounting, payment and mobile-app interfaces. The engagement treated those external contracts as responsibilities to preserve. It also required continued product work and no new infrastructure in the initial phase.
Those constraints made an internal boundary change worth considering before a deployment change. A maintenance feature should not require an engineer to understand unrelated payment implementation simply because both live in one application. Conversely, moving that feature into another process would not remove a shared schema dependency or define who may change it.
The database was explicitly retained. Module ownership therefore described application responsibilities, not proven database-level isolation. Separate folders would not by themselves prevent one module from reading or writing another module's tables.
Compare modularization with distribution
The team rejected a full microservices migration and a rewrite in another framework. The recorded concerns included the operating work of distributed services and the effort of reconstructing existing business behavior. These were reasons for the choice, not proof that either alternative would fail.
The chosen modular-monolith approach used interfaces within the application. A maintenance-service extraction remained an option to revisit after the interfaces were understood. It was not necessary to promise independent deployment for each module to begin that work.
AWS's shared-database guidance describes schema coordination and runtime coupling across services sharing a database. That general distinction helps explain why distributing code would not, alone, settle this platform's data dependencies. It does not identify the platform as an AWS deployment.
Map dependencies before changing callers
The work described in the engagement included domain mapping, interviews about business terminology, cross-domain dependency analysis and prioritization by change frequency. Existing code was wrapped behind module interfaces, with changes organized as separate reviewable units.
The account also describes static-analysis rules and a code-review checklist intended to discourage new boundary violations. It does not establish the rules' coverage, exceptions or effectiveness. The important design choice was to give callers an explicit entry point rather than make a module name stand in for an access contract.
An interface still needs a definition: accepted input, returned result, possible failure and ownership of any write or downstream action. A shared object passed across modules can expose implementation assumptions even when the call itself uses a named facade.
Use a dependency comparison, not a new topology diagram
For a comparable application, begin with authorized read-only inspection of one proposed change, its callers and the data it touches. Do not copy tenant or payment records into a public example. The following constructed comparison is a design exercise, not a test performed for this engagement.
Assume a proposed maintenance change needs to request a payment adjustment. The interface name and dependencies below are stipulated for the example, not recovered implementation details.
| Proposed dependency | Intended contract | What the review finds | Disposition |
|---|---|---|---|
| Maintenance calls the payment module's public adjustment interface | Payment logic owns validation and the adjustment decision | Caller supplies the agreed input and handles an explicit rejection | Candidate boundary to examine, not a passed behavior test |
| Maintenance updates payment tables directly | No direct write is allowed in this proposed contract | A caller bypasses payment validation | Hold that change for the module and data owners |
| An earlier caller uses the revised interface | Required input and result remain compatible | Compatibility has not been checked | UNKNOWN; do not label the refactor behavior-preserving |
| An earlier release reads state written by the revised code | Earlier reader can interpret the persisted representation | A new representation is unreadable by the earlier release | Hold the proposed rollback and assign state recovery |
Any executable follow-up requires an explicitly authorized isolated environment, synthetic or approved sanitized data, scoped identities and non-delivering payment, accounting and notification receivers. No live rent collection, production schema change or fault injection is authorized. Name the module owner and recovery owner first; stop if a receiver can reach a live destination or the observations needed for comparison disappear.
Amazon's rollback-safety discussion explains why earlier and later versions must be able to coexist with relevant data formats. Restoring an application artifact is not the same as undoing accepted writes or external effects.
Scope retained, outcomes not asserted
The engagement account supports the modularization choice, retained platform constraints and described interface/dependency work. It does not substantiate uninterrupted service, faster delivery, shorter onboarding, quicker tests or a higher deployment frequency. No completed database split or independent service rollout is asserted.
Start with one boundary that a real feature crosses
This case fits a decision about code entanglement inside an application that the team intends to keep operating. Independent scaling, security separation or different release ownership may still justify service extraction; they need their own requirements and operating plan.
Choose one feature and trace its permitted calls, shared data, external contracts and state-recovery condition. Resolve the first bypass or unknown compatibility requirement before claiming a module is independent. The service-consolidation case covers the different choice of bringing already extracted services back together.
Technical reference
AWS shared-database guidance and Amazon's rollback-safety discussion were checked on 18 October 2026 for the general dependency and version-compatibility explanations linked above. They do not establish this engagement's exact implementation, completed tests or results.