6

For a REST api, that has no dependencies between requests and is using ASP.NET Core DI.

I have heard conflicting arguments when choosing the most appropriate:

  • Prefer transient and then scoped and avoid singleton due to memory and multi-threading issues and also because singletons should not be injected with other lifetimes due to captive dependencies
  • Prefer singletons to save time instantiating objects and prevent opening multiple connections

I understand they work differently but is there a 'go-to' lifetime? Should it start with 'transient' and move to others as required?

Are there any investigations proving the instantiation time saved by singleton is actually relevant?

Perbert
  • 465
  • 1
  • 7
  • 17
  • 2
    There's no single answer to this. It will depend upon the type of dependency. – Sean Mar 06 '20 at 17:11
  • Don't think so @TylerHundley this is specific to .net core DI so the service being injected doesn't necessarily have all those issues – Perbert Mar 06 '20 at 17:19
  • That's fair @Sean and it could be the answer. But for a specific example say there is a small validator service that has no dependencies or external calls. Would you make it singleton to prevent multiple instantiations or make it transient because it's lightweight? – Perbert Mar 06 '20 at 17:22
  • 1
    Answer is much older than I realized, so I removed my comment. I do generally think avoiding Singletons is the right way to go, but they have their time and place. I think @StriplingWarrior does a good job answering below. – Tyler Hundley Mar 06 '20 at 17:29
  • 1
    This question is not about the singleton pattern (ie `SomeType.Instance.DoStuff()`) it's about service lifecycle in DI, so not a duplicate. – David Browne - Microsoft Mar 06 '20 at 22:47

2 Answers2

6

I'm not going to say there's a right or wrong way here, but I'll share my personal experience.

I've tried both ways, and ended up finding that it's best to start with transient and then expand the scope as necessary.

Think in terms of the Single Responsibility Principle and Reasons for Change. The reason that your service's lifetime scope may need to change will likely be linked to a change you make to the service implementation itself. When that Reason happens, you want to only need to change a single class.

If you have a service that needs to be longer-lived than most of your other services, all you have to do is make it longer-lived and inject factories for any shorter-lived dependencies into it.

On the other hand, if you have a class that needs to be shorter-lived than most of your other services, you'll end up having to inject it as a factory into all the longer-lived services. So one Reason for Change impacts code all over your code base.

Of course, this is assuming you're doing things correctly: avoiding captive dependencies and remembering to make non-thread-safe services transient. I find it's far easier to remember which services need to be long-lived and override the defaults in that direction, than it is to remember which services shouldn't be long-lived and override the defaults that way. And I've found that failure to do the latter leads to much more insidious bugs that are harder to notice and result in greater damage.

With regards to performance: I haven't done performance testing with the ASP.NET Core built-in DI framework, but I've found that SimpleInjector is fast enough that the time and memory overhead of creating new instances of services is trivial in comparison with the other work my app does (like querying databases).

With regards to preventing opening multiple connections: SQL Server connections are pooled automatically so the cost to creating a new SqlConnection() and disposing it several times in a request is generally pretty trivial.

StriplingWarrior
  • 151,543
  • 27
  • 246
  • 315
3

Should it start with 'transient' and move to others as required?

Yep. Transient services are simpler to implement correctly, and simply instantiating an object every time a dependency is resolved would normally not cause a significant performance problem.

David Browne - Microsoft
  • 80,331
  • 6
  • 39
  • 67