DEV Community

Cover image for The Night I Discovered I'd Built Something Revolutionary (And Didn't Know It)

The Night I Discovered I'd Built Something Revolutionary (And Didn't Know It)

HomelessCoder on September 24, 2025

"Oh Saint Cats. What Have I Done?" 🤯 It was 11:47 PM on a Tuesday. I was writing documentation for my PHP framework, exploring plugin ar...
Collapse
 
xwero profile image
david duymelinck • Edited

Distributed containers is a good pattern when the application needs to scale up.
I'm just wondering if the added boilerplate helps a modular monolith?

I think the interactions between modules are more prone to failures than having a single container.
I agree it is tempting to use everything available, but when you have a recipe it will be less likely to add other ingredients.

I'm also wondering what the cost is of the distributed containers? When you need to scale that cost is reduced by the need to bring more to the table than the application can handle.

It certainly is a very interesting idea to do it from the ground up.

Collapse
 
homeless-coder profile image
HomelessCoder

Hey David,

Thank you so much for the comment and questions. These are really valuable moments for me, and I appreciate you diving in! I will try to address your points one by one, but please feel free to continue the discussion if you have more thoughts.

Regarding your first concern, you are right to question this. As developers, we value simplicity and often want to avoid extra work. My argument is that this framework's approach ultimately leads to a greater form of simplicity. The "boilerplate" of imports/exports is the core feature. It turns the implicit, invisible, and fragile dependencies that exist in a single-container monolith into explicit, visible, and validated contracts. Well, I believe you got the point from the post series :) But let me try to elaborate. For example, in TypeScript, the "boilerplate" of adding types feels like extra work at first, but it prevents a huge class of runtime errors.
In the same way, my framework's "contracts" (the imports/exports) are validated when the application boots. If a dependency is unmet (because it's not explicitly exported or imported), the application won't even start. It fails fast with a clear error, preventing the very inter-module failures you're rightly concerned about. The philosophy is to make dependencies explicit and validated, even if it requires a little more definition upfront. It pays off in the long run.

On a technical level, there's a neat implementation detail that makes this efficient. When a module exports a service, the root container doesn't duplicate the service or its configuration. Instead, it creates a lightweight reference that simply points back to the module's own container. When you request an exported service from the main application, the root container delegates the request to the module that owns it. This preserves accurate encapsulation: the module retains full control over its service's lifecycle and avoids any duplication of services.

Regarding your "recipe" metaphor. I love it, and it applies on multiple levels. My vision is that good architecture doesn't hide complexity; it manages it by making it explicit. In a typical kitchen (a single-container app), all ingredients are on one big counter, and you just have to trust the chef not to grab the salt instead of the sugar. My approach is like giving each chef their own pre-measured ingredient kit (imports) for the specific dish they're making. It's impossible for them to grab the wrong thing because it's not in their kit.

So, yes, it might seem more complex at first glance, but it actually reduces the cognitive load when working on a specific module. Each module has a clear contract of what it needs and what it provides, making it easier to reason about and work with in isolation.

Regarding the cost: again, perfect question. There is a small, one-time cost, but it only happens during the application's very first boot. In this initial step, the framework performs a topological sort to build the module dependency graph, and then caches the result (for now it's just a psr/cache filesystem implementation). On every subsequent request, the application boots almost instantly by reading the pre-computed graph directly from the cache. The graph is only ever recalculated if the list of registered modules changes.

This tiny, one-time cost is negligible compared to the immense long-term "cost" of maintaining a large monolith with hidden, scattered dependencies. The framework pays a tiny upfront price for massive gains in safety, testability, and long-term maintainability.

And yes, you've perfectly summarized the goal! I'm really enjoying working on the framework which started as a learning project, and my hope is that it will help others as well.

Thank you again for your thoughtful questions and insights—they really help refine and clarify the ideas behind the framework!

Collapse
 
xwero profile image
david duymelinck • Edited

It turns the implicit, invisible, and fragile dependencies that exist in a single-container monolith

I don't think the dependencies are implicit or invisible, IDEs try to hide them but the use lines in the PHP files are very visible. And dependencies are very explicit in the method/function arguments.
I agree that the dependencies are fragile in the sense that they can be used everywhere. But that can also be a blessing when a refactoring is needed.

With the recipe analogy, I was thinking more about the patterns that are used with a single container and module interactions. It can go from the tight coupling of APIs between modules, over gateways/facades, until the loose coupling of events.
I think the multiple containers are in gateway/facades category because the relations are pushed to the edges. But the only concern is the relationship, as the classes are used as is.

In your white paper you mention a modular monolith with services in the future. I think if you assume that future you are over-engineering. For me a modular monolith is about separating functionalities and easy discoverability.
Breaking up a modular monolith in services in not as straight forward as moving modules to services. For example you have to reduce the chatter between modules if they become a service because network calls are expensive.

If it comes down to it, I rather have a looser system with the single container and picking the best connection pattern for module interactions for the specific application. Than starting with multiple containers and having the reassurance things break fast when it comes to relations. I'm sure there will be people who find it helpful for their applications.

Thread Thread
 
homeless-coder profile image
HomelessCoder

I see that you have an excellent understanding, and you definitely have extensive experience in development. I really appreciate your investment of time, and this is exactly the kind of technical dialogue I was hoping to have! :)

I love that we are closely aligned on:

  • the importance of discoverability and separation of concerns in a modular monolith
  • the understanding that evolution from modular monolith to microservices isn't trivial
  • the value of choosing appropriate coupling patterns for each use case/application in general

Some aspects of the framework or overall vision might be opinionated, as I'm quite new to this, and I am very open to feedback and suggestions.

Anyway, let me clarify some points where I may not have communicated clearly (I'm still learning how to do that effectively):

On "implicit" dependencies: you are right. What I meant is that they are not literally implicit, but rather not declared anywhere at the architecture level - there's no enforcement of domain boundaries between modules. This is more about the lack of explicit contracts while separating domains and/or scaling the team, in practice. The idea of this framework was to address that. This also contributes to less friction between teams, in my opinion.

Regarding the microservice evolution, I briefly mentioned it above - I appreciate that we're on the same page here - it confirms my thinking. To elaborate, I'm not assuming that's the only path forward nor that it's the primary goal of the framework. It simply provides you with more flexibility (or less preparation) if you ever need it down the road.

After all, I think there's a philosophical difference here. Some would prefer flexibility to choose coupling patterns per use case (and I recognize that's the prevailing approach in our community). I'm more in favour of a more structured approach. Both are valid, I believe. It comes down to whether you want the architecture to guide you or get out of your way.

I'm curious. In your experience, what patterns have you found most effective for preventing accidental coupling as teams and codebases grow? And what are your thoughts on:

But the only concern is the relationship, as the classes are used as is.

Do you think there is a way to improve it without overcomplicating the architecture?
Looking forward to your thoughts!

Thread Thread
 
xwero profile image
david duymelinck • Edited

Some aspects of the framework or overall vision might be opinionated

I think everything we do is opinionated. It is not a bad thing when the opinions can change once a better solution presents itself.
It is by being opinionated an application, a framework or a library has consistency.

I think having explicit code is good, but I also consider looseness. I always remind myself of the galloping gertie bridge failure. Software needs to have a bit of wiggle room to keep it maintainable. Once it is too much in tune it will break.

what patterns have you found most effective for preventing accidental coupling as teams and codebases grow?

Good communication. It sounds simple but I think a lot of people overlook that aspect, and that is why there are a lot of manager roles.
I'm mainly doing backend code, but I still like to talk about design and SEO/GEO with experts in those fields. At the end of the day our work comes together to create the product.

Do you think there is a way to improve it without overcomplicating the architecture?

I think that focus is inherent for the problem your framework solves. If I were you I wouldn't extend that focus too much.
An option could be to export/import interfaces, and check the import method for the actual class. I'm just thinking out loud, I don't know if it is feasible.

Thread Thread
 
homeless-coder profile image
HomelessCoder

Absolutely lovely to have such a conversation! I will definitely take some time to read about the bridge in detail, but I think I got your point.

An option could be to export/import interfaces, and check the import method for the actual class. I'm just thinking out loud, I don't know if it is feasible.

That's brilliant. You'll be happy to know the framework already supports exactly this:

  1. It is documented as a best practice in Advanced Patterns guide: "Use interfaces: Export interfaces, not concrete classes when possible.". And the Getting Started examples demonstrate this in action - modules export LoggerInterface::class rather than the concrete Logger::class.
  2. This is precisely how the ImportItem class works. It accepts two arguments: the module from which the item is imported, and the class or interface name. It then checks if the module actually exports the requested class.

I'm absolutely thrilled you brought this up, because it means the framework is on the right track. Thank you so much for your insights!

Collapse
 
homeless-coder profile image
HomelessCoder

I'm building this project in the open, and I'd love to connect with other developers who are passionate about software architecture. If you have questions, ideas, or just want to say hi, please feel free to reach out in the comments or connect with me on LinkedIn. Your feedback and perspective are incredibly valuable as this project grows!

Collapse
 
prime_1 profile image
Roshan Sharma

Wow, what a story, that “built something revolutionary and didn’t realize it” moment gives me chills.

What made you finally see it as “revolutionary” was there a specific user reaction or a tipping point in feedback?

Collapse
 
parag_nandy_roy profile image
Parag Nandy Roy

This is wild...in the best way..

Collapse
 
homeless-coder profile image
HomelessCoder

Thank you! This reaction really warms! :)

That moment was pretty wild for me too! What was the most exciting from your perspective?