Isolated Containers
Ore's Isolated Containers (a.k.a Modules)
Ore supports the creation of isolated containers, enabling advanced use cases such as Modular Monolith Architecture, where each module maintains its own independent dependency container. This feature ensures clean boundaries between modules, helping you enforce separation of concerns and avoid accidental cross-module dependencies.
Key Features of Isolated Containers
-
Independent Dependency Graphs:
Each container has its own registration and resolution process, isolating modules from each other. -
Default and Custom Containers:
- The Default Container is sufficient for most use cases.
- Custom containers can be created for modular architectures or advanced scenarios.
-
Minimal Overlap:
Containers are completely independent. To enforce module isolation, avoid cross-container access.
Use Cases
-
Modular Monolith Architecture:
Separate containers for each module, such asBroker
andTrader
, allow individual dependency graphs and better encapsulation. -
Testing and Isolation:
Custom containers are useful in unit tests to isolate dependencies and mock certain services.
How It Works
-
Creating a New Container:
Useore.NewContainer()
to create a custom container. -
Registering Services to a Container:
Register services using functions likeRegisterFuncToContainer
,RegisterSingletonToContainer
, orRegisterPlaceholderToContainer
. -
Resolving Dependencies from a Container:
Use functions likeGetFromContainer
orGetListFromContainer
to resolve dependencies. -
Validation and Sealing:
- Seal the container to prevent further registrations using
container.Seal()
. - Validate the dependency graph with
container.Validate()
. - Disable runtime validation (if needed) with
container.DisableValidation = true
.
- Seal the container to prevent further registrations using
Example: Modular Monolith with Multiple Containers
Broker Module
// Create a container for the Broker module
brokerContainer := ore.NewContainer()
// Register a Broker service
ore.RegisterFuncToContainer(brokerContainer, ore.Singleton, func(ctx context.Context) (*Broker, context.Context) {
brs, ctx := ore.GetFromContainer[*BrokerageSystem](brokerContainer, ctx) // Resolve dependencies from the Broker container
return &Broker{brs}, ctx
})
// Resolve the Broker service
broker, _ := ore.GetFromContainer[*Broker](brokerContainer, context.Background())
Trader Module
// Create a container for the Trader module
traderContainer := ore.NewContainer()
// Register a Trader service
ore.RegisterFuncToContainer(traderContainer, ore.Singleton, func(ctx context.Context) (*Trader, context.Context) {
mkp, ctx := ore.GetFromContainer[*MarketPlace](traderContainer, ctx) // Resolve dependencies from the Trader container
return &Trader{mkp}, ctx
})
// Resolve the Trader service
trader, _ := ore.GetFromContainer[*Trader](traderContainer, context.Background())
Best Practices
-
Module Isolation:
- Ensure each module interacts only with its own container.
- Avoid exposing internal container references to other modules.
-
Dependency Validation:
- Use
container.Validate()
to check for circular dependencies and other issues. - Seal containers after registration to prevent accidental modifications.
- Use
-
Cross-Module Interaction:
- If interaction between modules is required, expose only the required interfaces or services, not the entire container.
Comparison: Default vs Custom Containers
Default Container | Custom Container |
---|---|
Get | GetFromContainer |
GetList | GetListFromContainer |
GetResolvedSingletons | GetResolvedSingletonsFromContainer |
RegisterAlias | RegisterAliasToContainer |
RegisterSingleton | RegisterSingletonToContainer |
RegisterCreator | RegisterCreatorToContainer |
RegisterFunc | RegisterFuncToContainer |
RegisterPlaceholder | RegisterPlaceholderToContainer |
ProvideScopedValue | ProvideScopedValueToContainer |
Summary
Ore's Isolated Containers provide a robust mechanism for creating modular applications with strict boundaries between components. By leveraging this feature, you can:
- Maintain clean dependency graphs per module.
- Avoid unintended coupling between modules.
- Build scalable, maintainable applications with clear separation of concerns.