Modules and dependency injection

Why Bustan resolves dependencies from a module graph rather than a global registry.

Unatazama toleo 0.0.1. Toleo jipya zaidi ni 0.0.2.

Tazama 0.0.2

Most Python web frameworks resolve dependencies per-handler: you declare what a route needs, and the framework supplies it. That works, and for a small service it is hard to beat.

It stops working when you want to know what depends on what without reading every handler.

The module graph is the answer#

In Bustan, a provider belongs to a module. A module states what it imports and what it exports. Those two lists are the entire visibility rule:

  • A provider is visible inside its own module
  • A provider is visible to another module only if it is exported and that module imports the owner

So the dependency structure of the application is a graph you can read off the module declarations, without opening a controller.

exportsusesAppModuleTaskModuleReportModuleTaskServiceexportsusesAppModuleTaskModuleReportModuleTaskService

Why private by default#

The alternative — everything global — is more convenient for about three weeks. After that, any provider can be reached from anywhere, the graph is complete, and "what would break if I changed this" has no answer short of running everything.

Defaulting to private means the graph stays sparse, and adding an edge is a deliberate act that shows up in a diff.

Resolution is constructor-based#

The container reads constructor annotations. This is why @Injectable() exists: it marks a class as something the container is allowed to build, so a missing registration fails loudly at startup rather than mysteriously at request time.

Resolution happens once, at startup. The graph is walked, every provider is constructed in dependency order, and cycles are reported as errors:

BustanResolutionError: circular dependency
  TaskService → ReportService → TaskService

Catching this at startup rather than on first request is the point. A framework that resolves lazily turns a structural mistake into an intermittent one.

What this costs#

Honestly: ceremony. A three-endpoint service is genuinely more code in Bustan than in a bare router, and if that is what you are building, the bare router is the better tool.

The trade pays off at the point where a second person joins, or a module needs to move. Structure is a cost you pay early to avoid an unbounded cost later — and if the codebase never gets there, you have simply paid.