CMS, CRM, LLM: a modular stack that scales
Three modules, three domains, one platform foundation. Here is how the orchestration holds up.
Three business modules live side-by-side on Jul6art: CMS for the public site, CRM for the prospect/customer relationship, LLM for assisted generation. Each has its entities, voters, settings, translations, tests. How do we keep them from becoming one giant monolith?
Strict folder separation
Every module lives under src/Modules/<Module>/ with its own Entity/, Repository/, Service/, Controller/, Voter/, Form/, EntityListener/. Cross-module dependencies are explicit: CMS does not know about CRM, but both know App\Modules\Organization and App\Modules\User.
Module-scoped settings
Every module declares a ModuleSettingsDefaultsInterface class that lists its default settings. The seeder provisions rows automatically when the feature is enabled, and a typed reader (CmsSettingsReader, CrmSettingsReader) exposes the values to services. No magic strings spread around.
Per-module permissions
A DefaultRolePermissions::$permissionsByFeature file lists the PermissionCodes::* shipped with each feature. The default role ↔ permission mapping can be overridden per tenant, and per user if needed.
Tests that don't trip on each other
Every module has its own test tree under tests/Unit/Modules/<Module>/ and tests/Functional/<Module>/. Fixtures are pooled (OrganizationFixtures, UserFixtures) but functional tests build their own tenant to stay isolated.
Result: three modules of a few thousand lines each, evolving in parallel, no collisions, with test coverage above 947 green cases.