Stop Using Features: A Guide To Drupal 8 Configuration Management
Stop Using Features: A Guide To Drupal 8 Configuration Management
Mike Potter | Principal Engineer, CMS
July 26, 2017
One of the greatest improvements added in Drupal 8 was the Configuration Management (CM) system. Deploying a site from one environment to another involves somehow merging the user-generated content on the Production site with the developer-generated configuration from the Dev site. In the past, configuration was exported to code using the Features module, which I am a primary maintainer for.
Using the D8 Configuration Management system, configuration can now be exported to YAML data files using Drupal core functionality. This is even better than Features because a) YAML is a proper data format instead of the PHP code that was generated by Features, and b) D8 exports *all* of the configuration, ensuring you didn’t somehow miss something in your Features export.
“Drupal 8 sites still using Features for configuration deployment
need to switch to the simpler core workflow.”
Complex sites using Features for environment-specific configuration, or multi-site configurations should investigate the Config Split module. Sites using Features to bundle reusable functionality should consider if their solutions are truly reusable and investigate new options such as Config Actions.
Features in Drupal 8
When we ported Features to Drupal 8, we wanted to leverage the new D8 CM system, and return Features to its original use-case of packaging configuration into modules for reusable functionality. New functionality was added to Features in D8 to help suggest which configuration should be exported together based on dependencies. The idea was to stop using Features for configuration deployment and instead just use it to organize and package your configuration.
We’ve found that despite the new core configuration management system designed specifically for deployment, people are still using Features to deploy configuration. It’s time to stop, and with a few exceptions, maybe it’s time to stop using Features altogether.
Problems using Features
Here is a list of some of the problems you might run into when using Features to manage your configuration in D8:
- Features suggests configuration to be exported with your Content Type, but after exporting and then trying to enable your new module, you get “Unmet dependency” errors.
- You make changes to config, re-export your feature module, and then properly create an update-hook to “revert” the feature on your other sites, only to find you still get errors during the update process.
- You properly split your Field Storage config from your Field Instance so you can have multiple content types that share the same field storage, but when you revert your feature it complains that the field storage doesn’t exist yet. This is because you didn’t realize you needed to revert the field storage config *first*.
- You try to refactor your config into more modular pieces, but still run into what seems like circular dependency errors because you didn’t realize Features didn’t remove the old dependencies from your module.info file (nor should it).
- You decide to try the core CM process using Drush config-export and config-import commands, but after reverting your features your config-export reports a lot of uuid changes. You don’t even know what uuid it’s talking about or which uuids changes you should accept.
- You update part of your configuration and re-export your module. When you revert your feature on your QA server, you discover that it also overwrote some other config changes that were made via the UI that somebody forgot to add to another feature.
- The list goes on.
Why Features is still being used
Given all of the frustrating complications with Features in D8, why do some still use it? After all, up until a few months ago it was the default workflow even in tools such as Acquia BLT.
Most people who still use Features typically fall into two categories:
- “My old D7 workflow using Features still seems to mostly work, I’m used to it and just deal with the new problems, and I don’t have resources to update my build tools/process.”
- “I am building a complex platform/multi-site that needs different configuration for different sites or environments and having Features makes it all possible. I don’t have to worry about non-matching Site UUIDs.”
People in the first category just need to learn the new, simpler, core workflow and the proper way to manage configuration in Drupal 8. It’s not hard to learn and will save you much grief over the life of your project. It is well worth the time and resource investment.
Until recently, people in the second category had valid concerns because the core CM system does not handle multiple environments, profiles, distributions, or multi-site very well. Fortunately there are now some better solutions to those problems.
Handling environment-specific config
Rather than trying to enable different Features modules on different environments, use the relatively new Config Split module. Config Split allows you to create multiple config “sync” directories instead of just dumping all of your config into a single location. During the normal config-import process it will merge the config from these different locations based on your settings.
For example, you split your common configuration into your main “sync” directory, your production-specific config into a “prod” directory, and your local dev-specific config into a “dev” directory. In your settings.php you tell Drupal which environment to use (typically based on environment variables that tell you which site you are on).
When you use config-import within your production environment, it will merge the “prod” directory with your default config/sync folder and then import the result. When you use config-import within your local dev environment, it will merge the “dev” directory with your default config and import it. Thus, each site environment gets its correct config. When you use config-export to re-export your config, only the common config in your main config/sync folder is exported; it won’t contain the environment-specific config from your “dev” environment.
Think of this like putting all your “dev” Features into one directory, and your “prod” Features into another directory. In fact, you can even tell Config Split which modules to enable on different environments and it will handle the complexity of the core.extension config that normally determines which modules are enabled.
Acquia recently updated their build tools (BLT) to support Config Split by default and no longer needs to play its own games with deciding which modules to enable on which sites. Hopefully someday we’ll see functionality like Config Split added to the core CM system.
Installing config via Profiles
A common use-case for Features is providing common packaged functionality to a profile or multi-site platform/distribution. Features strips the unique identifiers (UUIDs) associated with the config items exported to a custom module, allowing you to install the same configuration across different sites. If you just use config-export to store your site configuration into your git repository for your profile, you won’t be able to use config-import to load that configuration into a different site because the UUIDs won’t match. Thus, exporting profile-specific configuration into Features was a common way to handle this.
Drupal 8 core still lacks a great way to install a new site using pre-existing configuration from a different site, but several solutions are available:
Several core issues address the need of installing Drupal from pre-existing config, but for the specific case of importing configuration from a *profile*, the patch in issue #2788777 is currently the most promising. This core patch will automatically detect a config/sync folder within your profile directory and will import that config when the profile is installed and properly set the Site UUID and Config UUIDs so the site matches what was originally exported. Essentially you have a true clone of the original site. If you don’t want to move your config/sync folder into the profile, you can also just specify its location using the “config_install” key in the profile.info.yml file.
This patch isn’t ideal for public distributions (such as Lightning) because it would make the UUIDs of the site and config the same across every site that uses the distribution. But for project-specific profiles it works well to ensure all your devs are working on the same site ID regardless of environment.
Another alternative is to use a recent version of “drush site-install” using its new “--config-dir=config/sync” option. This command will install your profile, then patch the site UUID and then perform a config-import from the specified folder. However, this still has a problem when using a profile that creates its own config since the config UUIDs created during the install process won’t match those in the config/sync folder. This can lead to obscure problems you might not initially detect that cause Drupal to detect entity schema changes only after cron is run.
Config Installer Project
The Config Installer project was a good initial attempt and helped make people aware of the problem and use-case. It adds a step to the normal D8 install process that allows you to upload an archived config/sync export from another site, or specify the location of the config/sync folder to import config from. This works for simple sites, but because it is a profile itself, it often has trouble installing more complex profile-based sites, such as sites created from the Lightning distribution.
Reusable config, the original Features use case
When building a distribution or complex platform profile, you often want to modularize the functionality of your distribution and allow users to decide which pieces they want to use. Thus, you want to store different bits of configuration with the modules that actually provide the different functionality. For example, placing the “blog” content type, fields, and other config within the “blog” module in the distro so it can be reused across multiple site instances. This was often accomplished by creating a “Blog Feature” and using Features to export all related “blog” configuration to your custom module.
Isn’t that what Features was designed for? To package reusable functionality? The reality is that while this was the intention, Feature modules are inherently *not* reusable. When you export the “blog” configuration to your module, all of the machine names of your fields and content types get exported. If you properly namespaced your machine names with your project prefix, your project prefix is now part of your feature.
When another project tries to reuse your “Blog Feature”, they either need to leave your project-specific machine names alone, or manually edit all the files to change them. This limits the ability to properly reuse the functionality and incrementally improve it across multiple projects.
Creating reusable functionality on real-world complex sites is a very hard problem and propagating updates without breaking or losing improvements that have been made makes it even harder. Sometimes you’ll need cross-dependencies, such as a “related-content” field that is used on both Articles and Blogs and needs to reference other Article and Blog nodes. This can seem like a circular dependency (it’s not) and requires you to split your Features into smaller components. It also makes it much more difficult to modularize into a reusable solution. How is your “related-content” functionality supposed to know what content types are on your specific site that it might need to reference?
We have recently created the Config Actions and Config Templates modules to help address this need. It allows you to replace the machine names in your config files with variables and store that as a “template”. You can then use an “action” to reference that template and supply values for the variable and import the resulting config.
In a way, this is similar to how reusable functionality is achieved in a theme using SASS instead of CSS. Instead of hardcoding your project-specific names into the CSS, you create a set of SASS files that use variables. You then create a file that provides all the project-specific variable values and then “include” the reusable SASS components. Finally, you “compile” the SASS into the actual CSS the site needs to run.
Config Actions takes your templates and actions and “runs” them by importing the resulting config into your D8 site, which you then manage using the normal Configuration Management process. This allows you to split your configuration into reusable templates/actions and the site-specific variable values needed for your project. Config Templates actually uses the Features UI to help you export your configuration as templates and actions to make it more useable.
Stay tuned for my next blog where I will go into more detail about how to use Config Actions and Config Templates to build reusable solutions and other configuration management tricks.
While the Drupal 8 Configuration Management system is a great step forward, it is still a bit rough when dealing with complex real-world sites. Even though I have blogged in the past about “best practices” using a combination of Features and core CM, recent tools such as Config Split, installing config with profiles, and Config Templates and Actions all help better solve these problems. The Features module is really no longer needed and shouldn’t be used to deploy configuration. However, Features still provides a powerful UI and plugin system for managing configuration and in combination with new modules such as Config Actions it might finally achieve its dream of packaging reusable functionality.