I'm writing a class that works with an API Client object that sometimes becomes corrupted and has to be recreated from inside the object that uses it. What is the best way to do this using Dependency Injection? I'm hesitant to call the DI framework from inside the class since it makes my code dependent on it.
public class MyObject
{
protected IMyAPIClient Client { get; set; }
public MyObject(IMyAPIClient client)
{
Client = client;
}
protected async Task<ReturnType> Run<ReturnType>(Func<Task<ReturnType>> action, int attempt = 1)
{
try
{
return await action();
}
catch(Exception exception)
{
Client = await GetNewClient();
if(attempt > MAX_ATTEMPTS)
{
throw new Exception($"Failed {attempt} times", exception);
}
return await Run(action, attempt++);
}
}
protected async Task<IMyAPIClient> GetNewClient()
{
// what to do here?
}
}
One solution that I came up with was to implement IMyAPIClient in a class that knows the type of IMyAPIClient and recreates it, thus circumventing the DI framework. I wonder if this is sensible or if there is a better way to do it?
I would first try to fix the problems with the ApiClient. Using workarounds for buggy code is rarely a good idea, but might some times be required for third party code. If the problem is in third party code it might also be advisable to put it in a separate process, otherwise you cannot know if failures have some unexpected side-effects.
To create new objects I would suggest injecting a factory of some kind. Exactly how this is done would depend on the DI/IoC framework. Simplest might be a Func<IMyAPIClient>, but an alternative would be an explicit factory-class. You can chose if the factory should use the IoC container to construct the object, or just construct it directly. Also, some IoC framework will handle factories in some special way, while others might require factories to be registered just like everything else. Check the documentation for your framework to see how factories should be managed.
I would also recommend moving the restart logic to a decorator if possible. That way it is separated from the usage, and should be more flexible in case there are multiple users, or the logic needs to be updated. However, registering decorators can be a bit tricky to ensure they are working correctly, but this will also depend on your IoC framework.
I solved the problem by creating a factory class that creates the IMyAPIClient.
This way I can handle the dependency injection in this factory class without making other code dependent on the DI framework.
public class MyObject
{
protected IMyAPIClientFactory ClientFactory { get; set; }
public MyObject(IMyAPIClientFactory clientFactory)
{
ClientFactory = clientFactory;
Client = clientFactory.CreateClient();
}
protected async Task<ReturnType> Run<ReturnType>(Func<Task<ReturnType>> action, int attempt = 1)
{
try
{
return await action();
}
catch(Exception exception)
{
Client = await clientFactory.CreateClient();
if(attempt > MAX_ATTEMPTS)
{
throw new Exception($"Failed {attempt} times", exception);
}
return await Run(action, attempt++);
}
}
}
Related
I have a legacy Asp.net Core 3.1 application which uses Kestrel server and all our GET and POST calls works fine. We have bunch of middlewares already on my legacy application and we use each of those middlewares for different purpose depending on what is the endpoint.
This is how our legacy application is setup as shown below. I have tried to keep things simple by keeping only important things.
Below is our BaseMiddleware class which is extended by bunch of other middlewares we have. Approx we have 10+ middlewares extending BaseMiddleware class -
BaseMiddleware.cs
public abstract class BaseMiddleware {
protected static ICatalogService catalogService;
protected static ICustomerService customerService;
private static IDictionary <string, Object> requiredServices;
private readonly RequestDelegate _next;
public abstract bool IsCorrectEndpoint(HttpContext context);
public abstract string GetEndpoint(HttpContext context);
public abstract Task HandleRequest(HttpContext context);
public BaseMiddleware(RequestDelegate next) {
var builder = new StringBuilder("");
var isMissingService = false;
foreach(var service in requiredServices) {
if (service.Value == null) {
isMissingService = true;
builder.Append(service.Key).Append(", ");
}
}
if (isMissingService) {
var errorMessage = builder.Append("cannot start server.").ToString();
throw new Exception(errorMessage);
}
_next = next;
}
public async Task Invoke(HttpContext context) {
if (IsCorrectEndpoint(context)) {
try {
await HandleRequest(context);
} catch (Exception ex) {
// handle exception here
return;
}
return;
}
await _next.Invoke(context);
}
public static void InitializeDependencies(IServiceProvider provider) {
requiredServices = new Dictionary<string, Object>();
var catalogServiceTask = Task.Run(() => provider.GetService<ICatalogService>());
var customerServiceTask = Task.Run(() => provider.GetService<ICustomerService>());
// .... few other services like above approx 10+ again
Task.WhenAll(catalogServiceTask, landingServiceTask, customerServiceTask).Wait();
requiredServices[nameof(catalogService)] = catalogService = catalogServiceTask.Result;
requiredServices[nameof(customerService)] = customerService = customerServiceTask.Result;
// ....
}
}
ICatalogService and ICustomerService are normal interfaces with some methods in them that their implementation class implements.
Below is one of our middlewares example that extend BaseMiddleware. All other middlewares follow same logic as below one -
FirstServiceMiddleware.cs
public class FirstServiceMiddleware : BaseMiddleware
{
public FirstServiceMiddleware(RequestDelegate next) : base(next) { }
public override bool IsCorrectEndpoint(HttpContext context)
{
return context.Request.Path.StartsWithSegments("/first");
}
public override string GetEndpoint(HttpContext context) => "/first";
public override async Task HandleRequest(HttpContext context)
{
context.Response.StatusCode = 200;
context.Response.ContentType = "application/json";
await context.Response.WriteAsync("Hello World!");
}
}
public static class FirstServiceMiddlewareExtension
{
public static IApplicationBuilder UseFirstService(this IApplicationBuilder builder)
{
return builder.UseMiddleware<FirstServiceMiddleware>();
}
}
Below is how my Startup class is configured -
Startup.cs
private static ILoggingService _loggingService;
public Startup(IHostingEnvironment env) {
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; }
public void ConfigureServices(IServiceCollection services) {
services.AddResponseCompression(options =>
{
options.Providers.Add<GzipCompressionProvider>();
});
services.Configure<GzipCompressionProviderOptions>(options =>
{
options.Level = CompressionLevel.Fastest;
});
DependencyBootstrap.WireUpDependencies(services);
var provider = services.BuildServiceProvider();
if (_loggingService == null) _loggingService = provider.GetService<ILoggingService>();
//.. some other code here
BaseMiddleware.InitializeDependencies(provider);
}
public void Configure(IApplicationBuilder app, IHostApplicationLifetime lifetime) {
// old legacy middlewares
app.UseFirstService();
// .. few other middlewares here
}
And below is my DependencyBootstrap class -
DependencyBootstrap.cs
public static class DependencyBootstrap
{
//.. some constants here
public static void WireUpDependencies(IServiceCollection services)
{
ThreadPool.SetMinThreads(100, 100);
var provider = services.BuildServiceProvider();
var loggingService = provider.GetService<ILoggingService>();
// ... some other code here
try
{
WireUp(services, loggingService);
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
private static void WireUp(IServiceCollection services, ILoggingService loggingService)
{
// adding services here
services.AddSingleton<....>();
services.AddSingleton<....>();
//....
var localProvider = services.BuildServiceProvider();
if (IS_DEVELOPMENT)
{
processClient = null;
}
else
{
processClient = localProvider.GetService<IProcessClient>();
}
services.AddSingleton<IData, DataImpl>();
services.AddSingleton<ICatalogService, CatalogServiceImpl>();
services.AddSingleton<ICustomerService, CustomerServiceImpl>();
//.. some other services and singleton here
}
}
Problem Statement
I have recently started working with C# and asp.net core framework. I have done my reading and it looks like -
Our legacy application doesn't use Dependency Injection properly as we have lot of places using BuildServiceProvider method which causes that warning. I am not sure why we have to do it.
Also do we really need InitializeDependencies method in BaseMiddleware class? If not then how we can initialize dependencies properly? It looks like we are trying to initialize all the dependencies during server startup so that they all are ready when the call comes for any middleware. I'd like to keep this logic as it is if possible.
Currently I am confuse what is the best way to use DI in asp.net core and if my application is doing it wrong then how can I do it the right way? Above code works fine in our application from a very long time but looks like we might be using it totally wrong way of DI.
Calling BuildServiceProvider multiple times can cause serious trouble, because each call to BuildServiceProvider results in a new container instance with its own cache. This means that registrations that are expected to have the Singleton lifestyle, suddenly are created more than once. This is a problem called Ambiguous Lifestyle.
Some Singletons are stateless and for them there is no difference in creating one or a thousand. But other components that are registered as Singleton might have state, and the working of the application might (indirectly) depend on that state not being duplicated.
To make matters worse, while your application might work correctly today, this might change any time in the future when one of the third-party or framework components you depend on makes a change to one of their components in such way that it becomes a problem when that component is created multiple times.
In your example, you are resolving both ILoggingService and IProcessClient from a service provider. If the resolved components are stateless objects without stateful dependencies, there is no real harm done. But this might change when they become stateful. Again, this can happen by a change of one of its indirect dependencies, so this is something you might not be aware of. This can cause you or your team to waste many hours; such problem will likely not be easily found.
This means that the answer "simply" is to prevent calling BuildServiceProvider() to create intermediate container instances. But this might be easier said than done. In your case, however, you seem to require a dependency on ILoggerService before all dependencies are wired. A typical way to achieve this is to split the registration phase into two separate steps:
One step where you manually create those few singletons that you need before hand
Add them to your container builder (IServiceCollection)
Add all other registrations
For instance:
private ILoggingService _loggingService;
public Startup(Confiration config)
{
_loggingService = new MySpecialLoggingService(config.LogPath);
}
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton(_loggingService);
// More stuf here.
...
}
Advantage of this structure is that when a dependency is added to the constructor of this manually built MySpecialLoggingService, your code stops compiling and you're forced to look at this code. When that constructor depends on some other framework abstraction or application abstraction that isn't yet available, you know you're in trouble and need to rethink your design.
Final note, calling BuildServiceProvider multiple times isn't a bad thing per se. It is okay when you explicitly want to have multiple isolated modules in your application that each have their own state and run independently of one another. For instance when running multiple end points for multiple bounded contexts within the same process.
UPDATE
I think I am starting to understand what it is you are trying to achieve in your BaseMiddleware. It is a 'convenient' helper class that holds all the dependencies that its derivatives might need. This is probably an old design and you might be aware of this, but this base class is quite problematic. Base classes with dependencies are hardly ever a good idea, because they tend to become big, ever changing, and obfuscate the fact that their derivatives become too complex. In your case, even, you are using the Service Locator anti-pattern which is never a good idea.
Besides this, there is a lot going on in that BaseMiddleware class that—to me—makes little sense, such as:
It contains complex logic to verify whether all dependencies exist, while there are more effective ways to do so. The most effective way is to apply Constructor Injection because it will guarantee that its necessary dependencies are always available. On top of that, you can validate the IServiceCollection on build. This gives you even greater guarantees over the correctness of your DI configuration than your BaseMiddleware currently provides.
It resolves all its services in background threads, which implies that construction of those components is either heavy on CPU or I/O, which is a problem. Instead composition should be fast, because injection constructors should be simple, which allows you to compose object graph with confidence.
You do exception handling in the base class, while it is better suited to be applied at a higher level; for instance, using an outer-most piece of middleware. For the sake of simplicity, though, my next example keeps exception handling inside the base class. That's because I have no idea what kind of things you are doing in there, that could influence my answer.
As the base class is resolving from the root container, middleware classes are only able to make use of Singleton dependencies. Connecting to the database through Entity Framework, for instance, will be a problem, because DbContext classes should not be captured in Singleton consumers.
So, with the observations and advice above, I would suggest reducing the BaseMiddleware class to the following:
// Your middleware classes should implement IMiddleware; this allows middleware
// classes to be transient and have scoped dependencies.
public abstract class ImprovedBaseMiddleware : IMiddleware
{
public abstract bool IsCorrectEndpoint(HttpContext context);
public abstract string GetEndpoint(HttpContext context);
public abstract Task HandleRequest(HttpContext context);
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
if (IsCorrectEndpoint(context)) {
try {
await HandleRequest(context);
}
catch (Exception ex) {
// handle exception here
return;
}
return;
}
await next(context);
}
}
Now based on this new base class, create your middleware implementations similar to this next example:
public class ImprovedFirstServiceMiddleware : ImprovedBaseMiddleware
{
private readonly ICatalogService _catalogService;
// Add all dependencies required by this middleware in the constructor.
public FirstServiceMiddleware(ICatalogService catalogService)
{
_catalogService = catalogService;
}
public override bool IsCorrectEndpoint(HttpContext context) =>
context.Request.Path.StartsWithSegments("/first");
public override string GetEndpoint(HttpContext context) => "/first";
public override async Task HandleRequest(HttpContext context)
{
context.Response.StatusCode = 200;
context.Response.ContentType = "application/json";
await context.Response.WriteAsync("Hello from "
+ _catalogService.SomeValue());
}
}
In your application, you can register your middleware classes as follows:
public void ConfigureServices(IServiceCollection services) {
// When middleware implements IMiddleware, it must be registered. But
// that's okay, because it allows the middleware with its
// dependencies to be 'verified on build'.
services.AddTransient<ImprovedFirstServiceMiddleware>();
// If you have many middleware classes, you can use
// Auto-Registration instead. e.g.:
var middlewareTypes =
from type in typeof(HomeController).Assembly.GetTypes()
where !type.IsAbstract && !type.IsGenericType
where typeof(IMiddleware).IsAssignableFrom(type)
select type;
foreach (var middlewareType in middlewareTypes)
services.AddTransient(middlewareType);
...
}
public void Configure(
IApplicationBuilder app, IHostApplicationLifetime lifetime)
{
// Add your middleware in the correct order as you did previously.
builder.UseMiddleware<ImprovedFirstServiceMiddleware>();
}
TIP: If you start to notice that a middleware classes get big constructors, that's likely because such class does too much and gets too complex. This means it should be refactored into multiple smaller classes. In that case, your class is exhibiting the Constructor Over-Injection code smell. There are many possible refactoring patterns and design patterns available that can get you out of this situation.
I have a part of my program that sends me an email and/or a push message to my iphone when something occurs. This is done through calls to two seperate WCF services using MSMQ. I followed this guide (model 4.0) in order to make it generic and test friendly. I like the generic channel creation, but my question is wether the proxies and channel factories are really closed/disposed correctly, or if this will blow up when it reaches a 10000 user environment (which it eventually will). The code works perfectly in my 50 user test environment.
Therefore, please review the following code:
Service Proxy
public class ServiceProxy<TChannel> : IServiceProxy<TChannel> where TChannel : ICommunicationObject
{
private readonly TChannel InnerChannel;
public ServiceProxy(TChannel innerChannel)
{
this.InnerChannel = innerChannel;
}
public void Execute(Action<TChannel> operation)
{
try
{
operation(InnerChannel);
InnerChannel.Close();
}
catch (CommunicationException)
{
InnerChannel.Abort();
}
catch (TimeoutException)
{
InnerChannel.Abort();
}
catch (Exception)
{
InnerChannel.Abort();
throw;
}
}
public TResult Execute<TResult>(Func<TChannel, TResult> operation)
{
TResult result = default(TResult);
try
{
result = operation(InnerChannel);
InnerChannel.Close();
}
catch (CommunicationException)
{
InnerChannel.Abort();
}
catch (TimeoutException)
{
InnerChannel.Abort();
}
catch (Exception)
{
InnerChannel.Abort();
throw;
}
return result;
}
}
Service Proxy Factory
public class ServiceProxyFactory : IServiceProxyFactory
{
public IServiceProxy<TChannel> GetProxy<TChannel>(string endpointName) where TChannel : ICommunicationObject
{
var factory = new ChannelFactory<TChannel>(endpointName);
return new ServiceProxy<TChannel>(factory.CreateChannel());
}
}
Making a service call (without return type for simplicity)
public class MessageSender : IMessageSender
{
private const string PushServiceEndpoint = "PushEndpointName";
private const string MailServiceEndpoint = "MailEndpointName";
private readonly IServiceProxyFactory ServiceProxyFactory;
public MessageSender()
{
ServiceProxyFactory = new ServiceProxyFactory();
}
public void NotifyMe(*some args*)
{
ServiceProxyFactory.GetProxy<MailServiceChannel>(MailServiceEndpoint)
.Execute(a => a.SendEmail(*some args*));
}
The questions are:
Should I close the ServiceProxy after the Execute?
Is it wise to create a ChannelFactory every time I call GetProxy(), and should this ChannelFactory then be closed again if so?
Is it really performance friendly to generate a ServiceProxy for every call? (it seems really heavy to me, but maybe someone can prove me wrong).
I left the interfaces out from this post, but they are really simple, and this whole setup with proxies and interfaces works really well with unit and integration testing.
I hope some of you coding wizards have an opinion about this, and will share this.
Thanks in advance!
The main performance impact has the creation of a ChannelFactory.
Creating ChannelFactory instances incurs some overhead because it involves the following operations:
Constructing the ContractDescription tree
Reflecting all of the required CLR types
Constructing the channel stack
Disposing of resources
https://msdn.microsoft.com/en-us/library/hh314046%28v=vs.110%29.aspx
WCF team has implemented caching for ClientBase<TChannel> class, it is suitable when you have auto generated proxy classes.
As you are using pure ChannelFactory you have to be careful about creating factories it on each call in order to have a better performance.
A good solution would be to implement caching of ChannelFactory<TChannel> your own (there is a good idea on how to do that). So at the end in your ServiceProxyFactory instead of having new ChannelFactory<TChannel>(endpointName); you should use cached instances like CachedChannelFactory<TChannel>.GetInstance().
Edit: There is another good article written by Michele Leroux Bustamante, that explains when To Cache or Not to Cache
I have a service for creating, saving and sending different types of orders where some types of them will be able to carry attachements.
The service will send orders to another external service by using IExternalService which is used by several other services with different external endpoints.
IExternalService contains a getter for a external IRepository which is used to send orders to external services.
I've created a new interface for those repositories which will be adding attachements IRepositoryWithAttachement.
I'm providing some sample code below where i left out unimportant stuff:
interface IRepository //Standard repo used by different external services
{
string Create(Order order);
void Update(Order order);
}
Orders with attachements use
interface IRepositoryWithAttachement : IRepository //attachable repo
{
string AddFile(Attachement file);
void UpdateFile(Attachement file);
}
Repo that must send attachements aswell as orders
public class Repository : IRepositoryWithAttachement {...}
Service used by many implementations of external services
interface IExternalService
{
string Name { get; }
....
IRepository Repository { get; }
}
Main service class for creating, saving and sending orders
public class OrderService
{
public string Create(Order order)
{
...
IExternalService eService = _externalServices.GetExternalService(id);
try
{
eService.Repository.Create(order);
}
catch (Exception e)
{
....
}
...
}
Now this particular ordertype will be adding attachments and when it gets the repository with IExternalService it will get an IRepository back and trying to call eService.Repository.AddFile(file) but the AddFile method doesn't exist because the return type is IRepository which i want. But my IRepositoryWithAttachement is extending IRepository so i got confused how i would reach it and i managed to do this:
public string AddFile(Attachement file) {
IExternalService eService = _externalServices.GetExternalService(id);
try
{
((IRepositoryWithAttachement ) eService .Repository).AddFile(file);
}
catch (Exception e)
{
...
}
}
}
Question
Am i doing this wrong or is it an ugly solution to my problem of getting hold of the addfile method by typecasting?
The two biggest issues I see are that a) you seem to be using exception handling to protect against repositories that don't implement the interface you need, and b) you are catching Exception, rather than InvalidCastException and/or other specific exceptions which you can anticipate and handle correctly.
IMHO, a better implementation would look something like this:
public string AddFile(Attachement file) {
IExternalService eService = _externalServices.GetExternalService(id);
IRepositoryWithAttachement repository = eService.Repository as IRepositoryWithAttachement;
if (repository == null)
{
// report error in some appropriate way and return, or throw an
// _informative_ exception, e.g.
// new NotSupportedException("repository does not support attachments")
}
repository.AddFile(file);
}
Even better would be to categorize your available repository IDs and restrict access according to capabilities so that the AddFile() method is never called in the first place unless you know that the repository implements the necessary interface. Then you can safely cast without ever having to worry about an exception being thrown.
Unfortunately, without a good, minimal, complete code example to clearly illustrate the question, it would be hard or impossible to offer advice any more specific than the above with any assurance of relevance. It is entirely possible that there's a better approach available than what you're using now, but without more context it's not really possible to say what that would be.
I have a question about implementing the Dependency Injection pattern. I have a class that requires access to a web service. According to this pattern I shouldn't have my class instantiating the service as this causes a strong dependency with it. This lead me to creating a factory class that constructs my class and in its constructor passing the correct service it requires, i.e. dependencies.
What is troubling to me is that I am passing the instance of the web service client to my object but wouldn't this leave the service open?
Should I pass the entire client in as opposed to only the interface? This way I can implement IDisposable and close the connection to the service?
Thanks in advance.
Please feel free to correct any terminology, don't mean to cause confusion.
For example:
public class ProductService
{
private IProductService client;
public ProductService(IProductService client)
{
this.client = client;
}
public void DoIt()
{
client.MyWebMethod();
}
}
public class Factory
{
public static T Create<T>() where T : class
{
T item = null;
if (typeof(T) == typeof(ProductService))
{
item = new CustomerService(**new ProducttServiceClient()**) as T;
}
return item;
}
}
Yes, if you create instance yourself by new ProducttServiceClient(), then you/factory need to dispose it. That's the place where DI containers like Unity, Castle Windsor can help you and release/dispose it.
Assuming we are talking about generated service client ProducttServiceClient - subclass of ClientBase, please be aware, that if you dispose the client it will try to close opened and not aborted channels - which can lead to an exception.See this link for details
If you just pass in opened channel (System.ServiceModel.ClientBase.CreateChannel()), then you can close/abort it and reuse the client again.
Yes, if your factory class creates the service client instance, it should also be responsible for closing it. But what are you trying to achieve (except trying out dependency injection and the factory pattern)? I don't think the factory pattern gives you much in this case.
If you don't want your client to create and close the proxy every time you want to call a service operation I would recommend creating a extension method on ICommunicationObject that is responsible to do the work and then close the proxy. Then you only have to implement the logic for disposing your client once (and in one place!).
extension might look like this:
public static TResult Using<T, TResult>(this T client, Func<T, TResult> work) where T : ICommunicationObject
{
TResult res = default(TResult);
try
{
res = work(client);
client.Close();
}
catch (CommunicationException)
{
client.Abort();
throw;
} // ... more catch cases might go here...
finally
{
if (client.State != CommunicationState.Closed)
client.Abort();
}
return res;
}
your client would invoke the method like this:
using TheNameOfYourNamespaceDefiningTheExtension
return new ServiceClient().Using(client => client.MethodName(request));
In previous question folks helped me to solve repository lifetime problem, now there's a question how to make it work nicely in composite service.
let's say i have services:
public class OrderService : IOrderService
{
IRepository<Order> orderRepository;
public OrderService(IRepositoryFactory repositoryFactory)
{
orderRepository = repositoryFactory.GetRepository<Order>();
}
public void CreateOrder(OrderData orderData)
{
...
orderRepository.SubmitChanges();
}
}
public class ReservationService : IReservationService
{
IRepository<Reservation> reservationRepository;
public ReservationService(IRepositoryFactory repositoryFactory)
{
reservationRepository = repositoryFactory.GetRepository<Reservation>();
}
public void MakeReservations(OrderData orderData)
{
...
reservationService.SubmitChanges();
}
}
And now the intersting part - composition service:
public class CompositionService : ICompositionService {
IOrderService orderService;
IReservationService reservationService;
public CompositionService(IOrderService orderService, IReservationService reservationService)
{
this.orderService = orderService;
this.reservationService = reservationService;
}
public void CreateOrderAndMakeReservations(OrderData orderData)
{
using (var ts = new TransactionScope())
{
orderService.CreateOrder(orderData);
reservationService.MakeReservations(orderData);
ts.Complete();
}
}
}
Problem is, that it won't work correctly if IRepositoryFactory lifestyle is transient (because you would get two different datacontexts and that would require distributed transactions to be enabled, which we try to avoid). Any ides how to write this correctly?
My observations:
In general, factories should be singletons. If your factory isn't a singleton, then you are probably just hiding another factory behind it.
Factories are meant for creating objects on demand. Your code simply creates a repository in the constructor, so I don't really see the difference between that and simply making the repository a direct injection parameter in the constructor.
These all seem to me like a workarounds around a more fundamental problem (described in your first question) and these workarounds only make the problem more complicated. Unless you solve the root problem you will end up with a complex dependency schema and a smelly code.
IMO - this is a Distributed Transaction scenario.
In the example you mentioned, OrderService & ReservationService use the same data context is an implementation detail hidden in the code.
I don't think it is correct to pass this knowledge up to the CompositionService by wrapping the service calls in a TransactionScope as now the composition service is aware of the shared data context & so needs to use a TransactionScope to run the code correctly.
In my opinion, the composition service code should look like:
try{
if(orderService.TryCreateOrder(orderData)){
if(reservationService.TryMakeReservation(orderData)){
reservationService.Commit();
orderService.Commit();
}
else{
orderService.TryRollbackOrder(orderData);
throw new ReservationCouldNotBeMadeException();
}
}
else{
throw new OrderCouldNotBeCreatedException();
}
}
catch(CouldNotRollbackOrderServiceException){
// do something here...
}
catch(CouldNotCommitServiceException){
// do something here...
}
In this case, the OrderService.TryCreateOrder method will insert an Order with a PendingReservation status or some other relevant status which indicates that the Order is inserted, but not completed. This state will change on the commits are called on the services (UnitOfWork pattern?)
In this case, the implementation details of the services are completely hidden from the consumer of the service, while composition is also possible, independent on the underlying implementation detail.
HTH.