Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Modular Monolith Bundle Laravel Package

landingi/modular-monolith-bundle

A Symfony bundle from Landingi for building modular monolith applications, offering structure and tooling to organize code into modules while keeping a single deployable unit. Helps teams enforce boundaries and scale development without microservices overhead.

View on GitHub
Deep Wiki
Context7

Getting Started

What you're working with. landingi/modular-monolith-bundle is a Symfony bundle (the -bundle suffix plus the Landingi namespace convention is the giveaway) aimed at structuring an application as a modular monolith: a single deployable, but with code partitioned into self-contained modules that own their services, config, and boundaries. Landingi is a Polish SaaS company, so expect the bundle to encode their internal conventions rather than a generic framework.

Steps to get it running:

  1. composer require landingi/modular-monolith-bundle. Check composer.json inside vendor/landingi/modular-monolith-bundle for the symfony/* and php constraints before you commit — the last release was May 2021, so it will be pinned to Symfony 4.4/5.x era APIs.
  2. Register the bundle in config/bundles.php (there is almost certainly no Flex recipe given the star count and release date, so expect manual registration).
  3. Look for the bundle's DI extension (src/DependencyInjection/*Extension.php and Configuration.php). The Configuration class is the single best source of truth for what config keys the bundle actually accepts — read it before touching YAML.

Where to look first, in order:

  • README.md — if it exists and is substantive, it's the closest thing to docs you'll get.
  • The bundle class itself (*Bundle.php) — build(), boot(), and getContainerExtension() tell you the extension points in ~30 lines.
  • DependencyInjection/ — how modules are discovered and registered.
  • Any Attribute/, Annotation/, or Contract/ (interface) directories — this is where module-defining markers live.
  • composer.json autoload.psr-4 — reveals the expected module namespace layout.

First use case. Don't rewrite the app. Carve out one bounded context — say Billing — as a module: its own namespace, its own services.yaml, its own migrations, a narrow public API (one facade class or a handful of interfaces), and everything else internal. Get the container to compile and the existing tests to pass. That single vertical slice tells you whether the bundle's conventions fit your codebase before you invest in a full migration.

Implementation Patterns

Module as a namespace + a service config. The core pattern in any modular monolith bundle is: one directory per module, one PSR-4 prefix per module, one services file per module loaded by the bundle extension. Register modules explicitly (a list in config) rather than by filesystem scanning — deterministic autoloading and DI matter more than saving a config line.

Explicit public surface per module. Structure each module as:

src/Billing/
  Api/            # interfaces + DTOs other modules may use
  Domain/         # entities, value objects, domain events
  Application/    # use cases / command handlers
  Infrastructure/ # repositories, adapters, HTTP controllers

The rule to enforce socially and in CI: other modules may only use from Billing\Api. Everything else is private by convention.

Communicate via events, not calls. The pattern that actually keeps a monolith modular is in-process messaging through Symfony Messenger (or a simple event dispatcher). Billing publishes InvoicePaid; Shipping subscribes. Neither module holds a reference to the other's internals. If you're already on Messenger, this is nearly free — just keep the message classes in Api/.

Shared kernel, kept tiny. Value objects (Money, Email, UserId), clock/id-generator interfaces, and cross-cutting contracts go in one Shared module that everyone may depend on and that depends on nobody. The moment Shared starts importing Billing, you've reintroduced the coupling you were escaping.

Enforce boundaries mechanically. The bundle may ship tooling for this; regardless, pair it with deptrac or phparkitect so cross-module imports fail the build. A module boundary nobody checks is a comment.

Per-module test suites. Mirror the source layout under tests/. Test a module through its Api; contract tests on the facade catch accidental API breaks early.

Integration tips:

  • Tag module-provided services (e.g. module.event_subscriber) and collect them with a compiler pass if the bundle doesn't already.
  • Keep Doctrine migrations per module in separate directories and generate them from module-owned entities only.
  • If the bundle exposes a module registry service, inject it rather than hard-coding module names.

Gotchas and Tips

Maintenance risk is the headline. Last release 2021-05-18, 0 stars, 0 dependents. Treat this as a reference implementation to read, not a dependency to lean on. Specifically: Symfony 6+ made Bundle::load() and Extension methods strongly typed, so a bundle written against Symfony 5 will fatal on modern Symfony unless it was forward-compatible. Diff the bundle's composer.json constraints against your symfony/framework-bundle version before requiring it.

No recipe, no auto-config. Without a Flex recipe you must register the bundle and hand-create any config file. Confirm the config alias by inspecting getAlias() on the extension, then verify with bin/console debug:config <alias> and debug:container --tag=<any tags it defines>.

Circular module dependencies surface as container compile errors. That's the feature working. When Billing needs something from Shipping and vice versa, the fix is almost never a service-locator workaround — it's extracting the shared concept into Shared or moving to an event.

PHP won't stop cross-module imports. Nothing in the language prevents use App\Shipping\Infrastructure\Foo. Only static analysis (deptrac layers, phparkitect rules) catches it in CI. Set this up the same day you create module #2.

Config caching bites during setup. After editing module registration config, bin/console cache:clear before debugging — stale compiled containers produce baffling "service not found" errors.

Debugging module wiring: debug:container --tag=... to see what's collected, debug:config for the tree, and lint:container in CI to catch missing service args before runtime.

Extension points to prefer over forks: Symfony compiler passes, tagged services, and the bundle's Configuration tree. If you need behavior the bundle doesn't offer, a compiler pass in your app is usually cheaper than maintaining a fork — especially for an unmaintained package.

Have an exit plan. Given the project's health signals, budget for the bundle being replaced by ~100 lines of your own DI extension plus a deptrac config. If that swap is easy, the bundle is a fine accelerator; if it isn't, that's a sign you're coupling your architecture to an abandoned dependency.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle