Closed oluatte closed 3 years ago
There are probably more ways to skin a cat, but you could consider the following:
IControllerActivator
to decide which Container instance to use for resolving the controller.Given there's a way to have independent containers, what is a good way to still share one container?
I'm afraid I don't understand your question.
@dotnetjunkie I really appreciate your response (fast and detailed), so thank you.
These are fantastic ideas, and I think I understood most of them. A few follow questions/comments.
Thanks again!
EDIT: Thought it might be helpful to illustrate to show an example of cross module communication that i'm struggling with. I first ran into this while trying to implement an Orchestration Saga between modules. This is usually something seen in microservices, but i'm trying to do it locally and in memory, hopefully without the network related downsides of microservices.
If you have in-process, inter-module communication, where each module has its own Container, there must be some shared state. This typically means there is some Mediator class that contains the list of all Module Containers and dispatches to them. Say, for instance, you have an IRelatedItemsProvider
service with an IEnumerable<RelatedItem> GetRelatedItemsFor(string entityName, Guid entityId)
method.
The provider implementation should forward the request to all modules, thus meaning it should have access to those modules:
public class InterModuleRelatedItemsProvider : IRelatedItemsProvider
{
public List<Container> ModuleContainers { get; } = new List<Container>();
// As an example, this method executes the calls to the containers in parallel, which might not
// be required, but as you can see, easily implemented.
public IEnumerable<RelatedItem> GetRelatedItemsFor(string entityName, Guid entityId) => (
from container in this.ModuleContainers.AsParallel()
from item in this.GetRelatedItemsForContainer(container, entityName, entityId)
select item)
.ToArray();
private RelatedItem[] GetRelatedItemsForContainer(
Container container, string entityName, Guid entityId)
{
// Wrap the resolve in a Scope, which is likely required
using (AsyncScopedLifestyle.BeginScope(container))
{
return (
from provider in container.GetAllInstances<IRelatedItemsProvider>()
from item in provider.GetRelatedItemsFor(entityName, entityId)
select item)
.ToArray();
}
}
}
Example registration (per module)
public static Container BuildContainer(
InterModuleRelatedItemsProvider provider // global single instance)
{
var container = new Container();
// Register the global mediator for usage
container.RegisterInstance<IRelatedItemsProvider>(provider);
// Adds itself to the provider
provider.ModuleContainers.Add(container);
Assembly[] moduleAssemblies = // fill with module's assemblies
// Register all module-specific providers as collection.
container.Collection.Register<IRelatedItemsProvider>(moduleAssemblies);
}
Such module-specific provider could be implemented as follows:
public RelatedOrdersForCustomerProvider : IRelatedItemsProvider
{
public IEnumerable<RelatedItem> GetRelatedItemsFor(string entityName, Guid entityId)
{
if (entityName != "customer") return Array.Empty<RelatedItem>();
return
from order in this.context.Orders
where order.CustomerId == entityId
select new RelatedItem
{
Type = "order",
Description = order.Number,
Link = "/orders/" + order.Id
};
}
}
I'll get back to you on your different questions.
This is super helpful!
So the single instance of the mediator that knows about all the containers is passed into the composition root for each module to be registered there as instance. That feels like a really great way to set things up.
Thank you.
I was planning to use MVC, but in a completely separate project that communicates with the API over http (could be hosted on the same machine or different ones). I was still planning to have the MVC app fan out to all the necessary modules (regardless of if they were separate instances or just separate endpoints inside a single API). Does that bring up any red flags for you?
No, that certainly doesn't raise any red flags with me, although I would say that the HTTP-hosted communication between MVC and your API should be optional. If you pick your design right, whether or not you use in-process communication between MVC and the 'API' layer, or do a full-fledged HTTP web service calls to a different machine should be an implementation detail. You already seem to be using a design similar to what I proposed above, since you are using a completely message-based approach (using MediatR in this case). Although the default IMediator
implementation of MediatR uses in-process messaging, replacing that with an implementation that sends the messages over HTTP to a web API is not difficult. For ideas I would, again, like to refer to this.
Thanks a ton! I think I'm all set now. Really appreciate all the help.
PS.
I spent some time going through the Solid Services repo at your suggestion and it is very interesting (and new) to me. Will definitely be integrating some of those ideas as move forward.
Thanks for all your great work on Simple Injector.
I am building a ddd style modular monolith with several independent modules/bounded contexts used by a single api project.. For the most part, modules are completely independent and should not share instances.
The exceptions are of course when we need cross module communication e.g. while orchestrating a cross module business process.
So we would use the private container for internal (to the module) registrations ( e.g. domain events and handlers) and the shared container for cross module uses (e.g. integration events).
Questions:
What is a good way to tackle the independent container per instance? Should each module have it's own composition root (in addition to the API composition root)? Should each module just be a package?
Given there's a way to have independent containers, what is a good way to still share one container?
Thanks!