Concerns about Serenity 5.0's implementation of .NET Dependency Injection #5873
Replies: 4 comments 2 replies
-
If you want to define interfaces for your repositories, and implement them you are free to do so. In my opinion, having one interface per repository that is not going to be useful for anything else is over engineering. We are not preventing you from doing that if you need that though. IRequestContext is there as we don't want everybody elses code to break in the future if we'll need to add another interface to repositories / base handlers that we'll need in the future. You may subclass that and add more interfaces that are needed in all / most of your repositories but this is not a recommendation. You can always add more parameters (e.g. services) to your repository constructor. If just some methods are going to need some additional interface then pass them as method parameters (at the end) and inject them in the endpoint using [FromServices]. If you don't like that, you are free to define multiple interfaces like |
Beta Was this translation helpful? Give feedback.
-
Btw it is not valid to say base class / framework injecting IRequestContext is violating DI, see how Controller/ControllActivator injects HttpContext into controller classes, do you have a HttpContext in your controller constructor? It is exactly modelled after ASP.NET Core MVC injection mechanism. DI does not mean constructor injection, it is just a way of dependency injection. |
Beta Was this translation helpful? Give feedback.
-
I understand your points and we originally thought about adding defining repository interfaces or classes and registering them in startup (either manually / via automatic discovery through reflection) etc. but the complexity involved does not outweight the advantages offered. So we designed it this way. But this design does not prevent you from using them that way, it is just not the default. Here are some of the reasons we didn't implement that way:
Let's make a simple repository interface: public interface ISaveRepository<TEntity>
...
public interface IRepository<TEntity> : ISaveRepository<TEntity>, IListRepository<TEntity>, .....
{
...
}
// public interface IMySampleRepository // id prefer this
....
public class MySampleRepository : IRepository<MySampleRow>
// or public class MySampleRepository : IMySampleRepository // the version i'd prefer
{
public MySampleRepository(IRequestContext requestContext, IAnotherService another)
{
}
}
services.AddTransient<ISaveRepository<MySampleRow>, MySampleRepository>();
//services.AddTransient<IMyRepository, MySampleRepository>();
public class MySampleController : ServiceEndpoint
{
public MySampleController(IRepository<MyRow> repository)
...
public void Save(IDbConnection connection, SaveRequest<MyRow> request)
{
repository.Save(connection, request);
}
} So it works just fine. |
Beta Was this translation helpful? Give feedback.
-
As a side note, i completely agree with all below points:
And think your design is good. Just i don't want to make it the default. |
Beta Was this translation helpful? Give feedback.
-
Hi there,
We've started to work on a project that uses the newest StartSharp template (5.0.41.0). We are relieved to see Serenity supporting newer .NET technologies, however, we have some concerns about Serenity's Dependency Injection situation (mainly in repository classes). Please let me know at any time if I've misunderstood Serenity's DI implementation or if I am completely wrong.
Here is a snippet of a generated repository for reference:
Personally, I'm pretty new to .NET (Core) dependency injection with only about 6 months of experience, but I feel like I have a decent understanding of how it should work. To my understanding, what I am seeing above is mostly correct:
OrderRepository
currently has one dependency, which is theIRequestContext
, and this context is being injected through the constructor and is stored as fieldContext
(that process is abstracted away from us through the parent class constructorbase(context)
which callsBaseRepository
's constructor). The problem that I am observing is how thisIRequestContext
is being "injected."Here is a snippet of a generated endpoint for reference:
The problem I've observed is that rather than
OrderRepository
being injected into theOrderController
(aka endpoint), the endpoint is manually injecting theContext
field into theOrderRepository
's constructor. This doesn't seem like true dependency injection to me.Here's an example of how I would interpret Microsoft's DI documentation:
This doesn't seem like a big issue until your repository needs additional services (ex: ILogger, IStorageService, INotificationService, etc), because then the current setup would force you to first inject those services into the endpoint, then to the repository.
What if one repository method required a service that another repository method doesn't need (Create vs List)? Would you have to inject services (to satisfy a repository constructor) you don't need in order to use a certain method?
Not to mention if you'd like to call a repository method from another repository, you would also need to inject seemingly unrelated services into an unrelated endpoint class. Without isolated dependencies per service/repository/module, I can see how quickly a project could turn into dependency hell.
I think the root problem I am seeing is that Repositories and ListHandlers should exist within the DI container provided by aspnet, rather than being created at will using a constructor. Letting the DI container construct the Repositories and ListHandlers as singleton/scoped/transient services would make sure that each injected service only deals with its own dependencies, not the dependencies of services it will call. The DI container already handles the different Endpoint/Controller classes, which is why services are able to be injected into them.
I haven't seen any issues or discussions relating to dependency injection, so if you have any thoughts about it at all, comment on this discussion.
Best regards,
Marcus
Beta Was this translation helpful? Give feedback.
All reactions