3

Authors such as Dave Farley advise us to "only build packages once", so that we can "be sure the thing we’re deploying is the same thing we’ve tested throughout the deployment pipeline".

On the other hand the Composer manual suggests that we should list dependencies required only "for developing this package, or running tests, etc" separately from dependencies also required in production, and use the --no-dev option to the install command to build a version of the package not suitable for use in development or test, and more suitable for production use. This implies building at least twice - once to run tests, and once for installation in production.

How should these apparently contradictory recommendations be reconciled to make a good deployment pipeline for a PHP based application?

I imagine there may be similar issues in many other languages with things like testing libraries and options to include debug symbols in compiled code.

3 Answers 3

2

I think your confusion comes from the assumption there must be only one way of developing software "the right way". To my experience, that's a pretty unjustfied assumption.

Different teams and organizations are having different ways of working, and a high-frequency continuous-delivery pipeline maybe great for some teams, but not for others.

The --no-dev option from Composer aims for a process where the team uses packages which are required as dependencies for full developing, debugging and testing, but not for running the software in production. That can indeed mean to

  • have tests which can only be run before packaging with --no-dev

  • and other tests which can still run afterwards.

This allows, for example, to implement certain kind of tests by using tools which would otherwise not be available, or tests which produce more detailed debugging information when they fail, so making it easier to find the root cause of a certain failure. However, this comes for the cost of a certain risk of creating code which runs fine before packaging, but not afterwards, if one is not be careful enough.

When going towards a CD pipeline, however, Dave Farley recommends a process where lots of the tests - specificially the automated ones - are run after packaging (with --no-dev). That means, there still might be some additional dev packages necessary for developing and debugging, but non for testing (at least not beyond "explorative testing"), and none of the automated tests should require additional dependencies in the "subject under test". This all aims at a higher throughput, but it may require to write tests differently, have more tests at all, and may require a better design of the software towards testability.

So in short, I would not read the Composer options of a "recommendation" to use extra dev dependencies for testing - it is an option you can make use of, if it fits to your development process, and not make use of it, when it doesn't.

Note also even when the team is using extra dependencies for testing, this does not necessarily mean to package "twice" - the tests in this scenario can be run perfectly without any packaging at all. However, for larger products, developed by several subteams, it may become useful to use packaged libraries from other teams - that is the scenario described in Bart van Ingen Schenau's answer: for development, the subteams share "dev-packages" (created without --no-dev), but when it comes to push code into the CI pipeline, only --no-dev packages are used.

1
+200

Commit your composer.lock file to version control.

"Building packages (only) once" can be interpreted as "do not upgrade vendor packages during your CD cycle" -- after all, developing against version x.11 of a package, but then installing x.13 on your development environment, can potentially break said environment. The key value in that advice is consistency (note 1) -- in this case, consistency in the version (or binaries) of the packages used.

Composer offers that consistency through its .lock file. With a lock file present, running composer install becomes an idempotent action - Composer will ensure the package version matches the one you've used in development, which at this point should be known to work. So while you could technically "build" over and over continuously, the build result of your package ecosystem will be locked in a consistent state.

The one caveat here, though, is that you might end up relying on packages only present in a Dev environment (i.e. not using the --no-dev option), and Composer does little to nothing to prevent this. This is something you will need to keep in mind - for example, by having a test/acceptance environment that also excludes dev-only packages.

Note 1: All four of the recommended practices in the article boil down to matters of consistency:

  • Only build packages once. - Be consistent in what you deploy - this goes for your own code, as well as vendors'. Differences in deployed codebases / binaries can break environments.
  • Deploy the same way to every environment. - Be consistent in how you deploy. Differences in deployment methods/systems can break environments.
  • Keep your environments similar. - Be consistent in where you deploy. Differences in environment, such as installed dependencies, can break environments.
  • Smoke test your deployments. - Be consistent in when you deploy? Confirm the build, at least, is succesful, before deploying anything. Blindly deploying a corrupted build will break environments.
3
  • Great answer - will wait for other possible answers and see if I can think of ways you might not get enough consistency simply by committing composer.lock before potentially accepting. Committing the composer.lock file is another thing that almost everyone does, including me, but doesn't feel ideal. They say it's so that "in six months when reinstalling the project you can feel confident the dependencies installed are still working", which makes sense but I'd like to try handling that by keeping the composer.lock with the build in an artifact store for six months or more, not in git.
    – bdsl
    Commented May 12, 2021 at 8:48
  • @bdsl In that case, I'd suggest interpreting "build only once" as "build only as many times as absolutely required". Yes, your Testing environment might require those Dev dependencies (PhpUnit or whatever testing framework you prefer), but you can (and should) reuse the single 'release' build on Acceptance, Staging, and whatever other environments you have set up in your specific CD pipeline. But Composer was pretty much created so you don't have to store build binaries indefinitely, so I'd have to disagree on it not feeling ideal. ;)
    – Duroth
    Commented May 12, 2021 at 11:19
  • 1
    I just meant it doesn't feel ideal to me - surely you can't disagree with that? 🙂
    – bdsl
    Commented May 12, 2021 at 12:04
0

The separation between dev and no-dev dependencies is primarily aimed at library packages that are intended to be used by other developers in their library or application. The separation ensures that those others who just want to use your package, rather than improve it, are not burdened with the dependencies you use exclusively for developing and testing your package.

This can be important, because those other developers might want to use the same packages in their testing, but may want to use a different version that is incompatible with the version the testing code of your package relies upon.


If your dev dependencies are all related to your testing code and your testcase can actually be run against the exact same package that you would distribute to others who just want to use it, then you can follow the advise from Dave Farley at the same time as not burdening your user with unneeded dependencies, at the cost of a slightly more complicated package structure for yourself.

The way to do this is to separate the production code and the testing code into separate packages (my_package and my_package-test). my_package would only list non-dev dependencies and would be what you deliver to your users. my_package-test would contain the testing code and declare a dependency on my_package. my_package-test would only be needed by those who want to develop my_package further (or to run the tests on my_package) and how my_package-test declares its dependencies does not really matter.

The complication is that you now have to maintain two packages in tandem.

3
  • "primarily aimed at library packages" So for non-libarries would you say it's fine to deploy dev dependencies to prod? "separate the production code and the testing code into separate packages" Do you do this? Or are you aware of people using this technique? Is it worth the extra maintenance cost?
    – bdsl
    Commented May 10, 2021 at 8:43
  • @bdsl, for non-libraries, it is less of a problem to deploy dev dependencies into prod. You will have a bunch of packages hanging around that are not used, but are eating disk space and resources when it comes to vetting them for security issues. But they won't block users of your application in using it. Commented May 10, 2021 at 8:49
  • Our testing code is not just in a separate package, but even in a different language than our production code. But then I am working in the niche of safety-critical, resource-constrained embedded systems. Commented May 10, 2021 at 8: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.