During a recent run of my Advanced Web Application Architecture training, we discussed the distinction between infrastructure code and non-infrastructure code, which I usually call core code. One of the participants summarized the difference between the two as: "everything in your vendor directory is infrastructure code". I don't agree with that, and I will explain why in this article.
Not all code in vendor is infrastructure code
Admittedly, it's easy for anyone to not agree with a statement like this, because you can simply make up your own definitions of "infrastructure" that turn the statement false. As a matter of fact, I'm currently working on my next book (which has the same title as the training), and I'm working on a memorable definition that covers all the cases. I'll share with you the current version of that definition, which consists of two rules defining core code. Any code that doesn't follow both these rules at the same time, should be considered infrastructure code.
Rule 1: Core code doesn't directly depend on external systems, nor does it depend on code written for interacting with a specific type of external system.
Rule 2: Core code doesn't need a specific environment to run in, nor does it have dependencies that are designed to run in a specific context only.
Following this definition means that as soon as a piece of code reaches out to something outside of the running application (e.g. it connects to the network, touches the file system, requests the current time or random data), it should be considered infrastructure code. As soon as a piece of code could only runs in a particular environment (a web application, a CLI application, etc.) it should also be considered infrastructure code.
These rules don't say anything about whether core code lives in
src/ or in
vendor/, and rightfully so.
Imagine you have a piece of code you are allowed to call core code because it matches its definition.
If you now move this code to a separate repository on GitHub, publish it as a package, and install it in your project's
vendor/ directory with Composer, would that same piece of code suddenly become infrastructure code? Of course not. The location of code doesn't determine what kind of code it is.
So whether or not something is vendor code doesn't determine if it's infrastructure code. What makes the difference is whether or not you can run that code in complete isolation, without making external dependencies available, and without preparing the environment in some way.
Unit tests and core code
This may remind you of Michael Feather's definition of a unit test:
A test is not a unit test if:
- It talks to the database
- It communicates across the network
- It touches the file system
- It can't run at the same time as any of your other unit tests
- You have to do special things to your environment (such as editing config files) to run it.
Tests that do these things aren't bad. Often they are worth writing, and they can be written in a unit test harness. However, it is important to be able to separate them from true unit tests so that we can keep a set of tests that we can run fast whenever we make our changes.
In fact, following my definition of core code, we can conclude that core code is the only code that can be unit tested. This doesn't mean that you can't test infrastructure code, it only means that such a test could not be considered a unit test. These tests are often called integrated or integration tests instead.
Most, but not all code in vendor is infrastructure code
So there is no strict relation between being-infrastructure-code and being-inside-the-vendor-directory. However there is somewhat of an inverse relation: much of your application's infrastructure code lives in your vendor directory. You could also say that you write most of the core code yourself.
Let's take a look at some examples of code that lives in vendor, but would (according to my rules) not be called infrastructure code:
- An event dispatcher library
- An assertion library
- A value object library
Libraries that only deal with transforming data (like some kind of data transformer, mapper, or serializer) could be considered non-infrastructure code as well.
In practice, you can use the following checklist to find out if code (wherever it lives, in
vendor) is non-infrastructure code:
- You can use the class anywhere and freely call any of its methods.
- It has no static dependencies (façades, service registries, etc.), dependencies on global state (
$_POST, etc.). All of its dependencies are provided as constructor arguments. None of the dependencies are infrastructure code itself.
The first rule ensures that the code has no external dependencies (network, filesystem, etc.) that need to be available when the code gets called. The second rule ensures that you don't need to prepare any special context to run this code in.
When all of these things are true, this means that if you run the code and encounter a problem with it (in a test or during regular use), you know it's a problem with the code. It's not related to any infrastructural concern, so you should be able to fix the problem by fixing the code you're running.
- Is mapping configuration (e.g. Doctrine annotations) infrastructure code?
- How big is the unit that we unit-test?
- Should we still not mock the things we don't own?
I'll leave those for another post.