3

Is it a bad design if we inject dependencies based on the running environment (production, testing, development, etc)?

Consider the following container in Laravel as an example:

$app->singleton(
  \App\Services\Mailer\MailerInterface::class,
  function($app) {
    if ($app->environment() === \App\Support\Environments::PRODUCTION) {
        return $app->make(\App\Services\Mailer\SendGridMailer::class);
    }

    if ($app->environment() === \App\Support\Environments::TESTING) {
        return $app->make(\Tests\Helpers\Mailer\TestMailer::class);
    }
);

Here I am binding different implementations to the MailerInterface based on the application environment.

What is bugging me is that the if else conditions are executed always regardless of the environment. I find this more bug prone and not to my likings to execute such conditions also on production.

Another option that I know to get around this case is using other bindings just for testing.

Only while running the test suites we can append another container as part of bootstraping the test suite. (first all usual code is runned, then the test bootstraper makes additional bindings).

The problem that I face with that solution is that only for changing the MailerInterface binding to TestMailer, we have also to update all the other abstractions that depend on the MailerInterface.

So for doing that I have to update let say also the UserServiceInterface etc, rebind all of them so the change is propagated. (In the case when the other classes are singletons, and in the closures the initial binding of MailerInterface is included). In my case I need to write in the test container 5 bindings, 1 for the Mailer itself and 4 for anything else that depends on that.

What are you thoughts and what is a solution that you can suggest me? Thanks in advance

2
  • Just as an aside - I know your example of email may be for the purposes of asking the question, but there are some apps that will allow you to go through with sending email via SMTP and will catch it for you. Have a look at SMTP4Mail or PaperCut github.com/changemakerstudios/papercut Jun 28, 2018 at 14:34
  • Clarification, a different component or a different configuration? Different configurations are necessary, but different implementations can often hide issues or create issues that exist in one environment but not the other. Jul 27, 2018 at 16:18

2 Answers 2

5

There is absolutely nothing wrong with this. The whole point of a configurable dependency injection container is to allow you to configure it based on your needs.

In this case you are supplying a test e-mail service instead of a real e-mail service in the test environment.

A perfect reason to do precisely what you are doing, and in fact is a good reason why dependency injection is introduced to an application in the first place.

5
  • I would be interested to know your views on having the di container configured via a config file rather than code. Many people seem to object to it.
    – Ewan
    Jul 27, 2018 at 16:58
  • 2
    @Ewan: I've done it both ways. A config is a config. Configuration via a fluent API is useful for languages with good IDE support. Failing that an XML or JSON config file is no less maintainable. I just think frameworks like Spring for Java gave config files for dependency injection a bad name, because of the reams of XML you needed. If you need piles of XML, who is to say you don't need an equally messy pile of fluent API Java calls to do the same thing? The only benefit there is the auto complete tools in an IDE. Jul 27, 2018 at 19:03
  • I like the config because you can change without recompiling as in this scenario. But as you say, it does become complex
    – Ewan
    Jul 27, 2018 at 19:58
  • @Ewan: I don't know if a plain text config file is really any more complicated than a big rats nest of fluent API calls, to be honest. Jul 27, 2018 at 20:20
  • well mine were unity xml, but yes, thats why I dont think its a good idea. di setup is complex enough already anyway you do it
    – Ewan
    Jul 27, 2018 at 20:22
0

I would say its a bad pattern.

I used to be a big fan of unity xml configuration for similar reasons, but I've come around to the opinion that the use case for it in testing is marginal and the complexity of the config can become overly burdensome.

Additionally there is a lack of surety around what is actually deployed and configured when you have effectively taken feature flags to the max and are using your DI framework as a plugin architecture.

Consider the alternative option of simply having an emailer service in your test env. You generate a lot of test emails, but you get the extra confirmation that emails actually are sent.

Where you use external third party services, either link to their staging env or mock their endpoint in the test env, and you can confirm the requests were correctly sent.

5
  • Thanks for the insight. I totally agree for the first part that we need to be sure what is really deployed. If I do not get any other insight that I have not thought yet, I will proceed with what you suggested the second solution. It makes sense also to me to have more test code and bigger container for testing then to potentially mess production. Jun 27, 2018 at 15:31
  • 1
    I don't find this convincing. You state in the last paragraph of your answer that you should mock external services in your test environment. Doesn't this imply some sort of configuration change? Jul 27, 2018 at 16:04
  • @robertharvey what I mean by that is that you deploy apis to your test env which mimic the 3rd party apis. You then use these endpoints in your app when deployed to test. thus the app uses the same dependencies as in live
    – Ewan
    Jul 27, 2018 at 16:08
  • 1
    The use of third-party API's implies the use of web addresses. How do you mock a web address? Do you have your own DNS server in your test environment? Jul 27, 2018 at 16:49
  • I would set the endpoint via configuration, yes. but you would do that for live as well surely?
    – Ewan
    Jul 27, 2018 at 16:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.