Drupal core comes with a built-in structure called an installation profile. An install profile is a specific set of features and configurations that get built when the site is installed. Drupal has almost always had some variety of install profile, but with Drupal 7 they became a whole lot easier to create and understand. Due to this, install profiles are really good ways of creating products based on Drupal, and many companies have put significant resources into creating their own install profiles to be used in specific product domains.
Every site is an install profile
Although install profiles are a good vehicle for productizing Drupal, it can actually be incredibly useful to integrate the install profile concept into the ordinary development workflow of all your Drupal sites.
Several years ago, James Walker wrote a probing article that suggested that all Drupal sites can be install profiles. In it, he points out that it makes sense to make every site an installation profile because:
- Every Drupal site actually contains a relatively small amount of code that's specific to just that site: a theme, custom code, and a list of contrib modules and libraries. An install profile can encapsulate all of these components.
A Drupal site rarely gets installed in only one place. At minimum you'll usually have a development, staging, and production environment, in addition to multiple developers' local environments. The explicit purpose of an install profile is to make it easy to install the same site in many places.
Being able to reliably install and replicate your site will actually make things much easier - whether it's just you or your whole team.
Drush make works well to build sites when given a list of contrib modules and libraries to set up, meaning that there are already advanced tools to help work with the install profile framework.
- By splitting out all unique components and removing Drupal core and contributed code from source control for each site, site repositories are smaller and more manageable.
When working on a lot of Drupal sites, and especially when working with a team of developers, these benefits can save massive amounts of time and help to focus efforts on just the important and unique parts of the site you're building.
Inheritance is key
One of the keys to why the install profile structure is so useful is because baked into its fabric is the concept of inheritance. A Drupal site can be installed from an install profile, inherit all of its code and configuration, and still have site specific code and configuration located in the /sites directory of the codebase. This is why install profiles work well for products, because it still affords site builders their own place to add code to the site in addition to what comes by default from the profile.
However, while this works well for install profiles as products, it doesn't maximize the utility out of making every site an install profile.
- Each site still only has one parent-child inheritance relationship, meaning that you can't have multiple tiers of inheritance, which limits reusability.
- Individual sites still live in /sites and this makes it much harder to keep site repositories small and manageable, limited to only the code that's unique to the site.
To really take advantage of all the benefits of using install profiles, we need to solve this by constructing an inheritable profile architecture.
Inheritable profile architecture
Imagine that you have a client who you're building one site for, but who will probably want you to build them several additional sites down the road. Imagine that you also have many other clients and have worked with Drupal for a long period of time. You probably have your preferred way of configuring Drupal with your preferred list of contrib modules that allow you to best satisfy the needs of your clients. Each of your clients is different, but most of them still share a large set of common needs, which means you end up installing the same modules and setting the same configurations over and over again.
Now imagine a scenario where you had a multi-tiered setup in which you could:
- create a base install profile that has all the common features and configurations across all your clients
- create a client specific base install profile that contains all the common features and configurations across all of one particular client's sites
- create a site specific install profile that contains all the common features and configurations for just one of your client's specific sites
- layer each of these install profiles on top of each other so the children inherit the parent features and configurations all the way up the chain of profiles
This is an inheritable profile architecture. In your /profiles directory you have several install profiles and each defines its own "base profile" much like subthemes define their own base themes. You can then chain these profiles together, leading to the ability to create layers of install profiles, each catering to a specific scope of responsibility. Each install profile is tracked in Git under its own repository and contains only the components pertinent to its area of concern.
Pioneered by our Product and Development Lead, Aaron McGowan, we have been working on a project called ImageX InstallKit, which is an implementation of the inheritable profile architecture. InstallKit is the base install profile that all of our Drupal sites are built upon, and each specific site itself is an install profile as well. This means that each of our sites are composed of at least two install profiles inheriting from each other, and often times more.
While the inheritable profile architecture works conceptually, it does not work well with Drupal core out-of-the-box. Individual profiles are not allowed to inherit from each other and dependencies are unable to be nested multiple layers deep, as the inheritable profile architecture necessitates. For this reason, a lot of our work on InstallKit has been dedicated to developing the critical changes necessary to enable this functionality, and to adding utilities and hooks to strengthen the installation process (especially when installing a multi-layered install profile site).
Why this architecture?
Reusable and generic code
It's not hard to grasp the importance of reusable and generic code. An inheritable profile architecture dictates that all profiles be built on top of each other, and that ideally a new profile is created for every site. This means that during every project and every site build, part of the development process is determining if a component can be generic, and placing it in the appropriate profile in the hierarchy of profiles if it can be used in more than one site.
In principle, organizing development efforts around re-usable and generic components does not specifically require an inheritable profile architecture. Just simply breaking out individual components into individual repositories and including them in a project also achieves this aim. However, this requires the use of Git submodules or Git subtrees, which are notoriously difficult and painful to work with, making this undesirable as a foundational building block of an architecture.
A profile properly encapsulates everything unique about that individual Drupal site. An individual Drupal site is composed of a list of contrib modules, feature and custom modules, a list of themes, custom themes, and a list of libraries. A profile contains the ability to house all of these items, and as a result it makes sense that an individual Drupal site doesn't need to be considered more than this.
In practical terms, this gives us the opportunity to change what is tracked in version control. Instead of tracking the entire site including Drupal core, when using a profile architecture we only need to track the profile in question. By using Drush make to build all non-custom components, this dramatically reduces the size of the repository and allows it to track only the components that are actually being developed. This cuts down on the cognitive load of organizing and navigating through the entire codebase and introduces a proper separation of concerns. By having multiple profiles built on top of one another, each encapsulating a unique layer of a site, the structure of the architecture itself communicates where components should be placed and what level of reusability and "genericization" those components should have.
The inheritable profile architecture helps to make it easier to follow a proper development workflow. It allows us to pair down the unique components of any given Drupal site and then run a build process to setup a site from scratch. This affords a number of benefits, perhaps most importantly though, it makes it easier to build and deploy sites to multiple environments with both ease and high confidence of exact replication. Part of the reason why this is easier is because it provides a more structured way to enable and install all the necessary modules, configurations, and other components during the site installation. When not using profiles, this can be quite a chore because while you can store configurations in modules, there's no well-structured way to define dependencies and make sure they're checked properly. All this is to say that it's easier to setup local, dev, staging, production, and whatever other environments may be necessary, which makes it easier to follow a proper development workflow that utilizes those environments.
By reusing and building on top of the same foundational codebases, this architecture helps teams to work together. This is a simple concept, but it's a revelation in practice. The key to this is that the structure itself enforces shared and collaborative development efforts because it makes projects rely upon one another.
Since you're sharing codebases and building on top of base components, you have to rely upon a collective effort to keep those components working properly and understand what they are used for and how they change over time. One of the side-effects of this is that you have to define process about how development work should and will be done, and then follow that pattern across the team. This helps to unify a development workflow, and collaborate on refining that workflow to follow best practices and learn from what does and does not work well.