About this talk
This talk discusses alternative approaches to configuration management in Drupal, specifically focusing on the use of recipes to streamline development and maintenance. The speaker, Phil Frilling, shares his experiences as a lead back-end engineer and reflects on the challenges faced when maintaining various Drupal projects over the years. He highlights the prevalent issue of 'developer guilt' associated with outdated personal projects and proposes transitioning from traditional configuration management to a model centered on defining site intent. By using recipes as the primary source of truth, developers can automate maintenance while allowing editors more flexibility in managing configurations without constant oversight. The session outlines a workflow integrating CI environments, automation tools, and end-to-end testing, fostering an environment where updates can occur seamlessly without the manual burdens of previous configurations.
Full transcript
Good afternoon everyone. Can everybody hear me? Okay. >> Yes. >> Awesome. Thank you. Well, welcome to Drupal Con. You're currently at the session misusing or uh repurposing recipes to achieve development bliss. My name is Phil Frilling. I'm currently a lead back-end engineer at Oomph, Inc. and I've been working with Drupal since 2007. And today I'd like to introduce an alternative way to think of configuration management in
the era of Drupal CMS project browser and recipes. So how many developers do we have in the room? Most of us. That's awesome. And how many of you have ever been asked by a family or a friend to build them a side project or a website? >> Yeah, all of us. Um, and how many of you actually built that side project with Drupal? Okay, less but good.
I personally built all of my side projects with Drupal because that's what I know. I've been doing it for 20 years, right? That's what I know I can do it the fastest. And so this is a true story of one of those side projects for me. Maybe you have had a similar experience with what I'm calling the developer guilt cycle. So imagine it's the year 2013. You're
younger. You still have your hair. And you just launched a brand new side project for a friend on Drupal 7. It's on a cheap VM running the latest version of your favorite flavor of Linux. It was exciting. You were proud of it. Your friend was excited about it. He had this new site. And then you get busy with client work and time went on. Then a few
years pass and your friend asks you to add a new feature to this project. So you dust it off and realize it's still in Drupal 7, probably 30, 40, maybe 50 versions behind at this point. The OS is so old the software can't even be updated. And you start to feel bad. You start feeling guilty about the state you left this project in, right? And so being
the good developer that we all strive to be, you think this is unacceptable. So you spend the weekend bu rebuilding the project in Drupal 8 with proper configuration management, you know, version control. You put it on a new VM and you start feeling good about this project again. You know, I did this the correct way. I added my the feature my friend asked me to add. You
know, it feels good again. And then a few more years pass and you check in on that project. It's still in Drupal 8, probably five minor versions behind at this point. Definitely two major versions behind. And you just can't update this site to Drupal 11 anymore without going through like the whole MA minors majors. Heck, the VM is so old you can't even get PHP and MySQL
to the latest versions. And so another weekend and another rebuild ensues. And it just got me thinking, you know, why is maintenance of Drupal so hard? You know, why do my personal projects become digital graveyards? You know, why can't this process be automated? Who maintains a Drupal 10 website now? Yeah, most of us. Have you updated to Drupal 11? Okay, a lot of us have. So, this
is the story of a recent client project of mine where I updated it from Drupal 10 to Drupal 11. I think we've all probably experienced this. And this is what I'm calling the the upgrade uh moment. So, you know, I went through the project and I got all of my custom deprecations fixed. You know, I got all the composer dependencies updated. I installed Drupal 11. You know,
I updated the database locally with DDEV and I exported the and 161 files have changed and I think all of these changes were related to config validation in some way, shape or form. You know, empty strings just changed to nulls. You know, default values were added to certain configurations. A lot of them the order of the keys in configuration changed. All very important changes to Drupal, but
not necessarily to me and as they relate to my project. And so you export those export and commit those 161 files because that's what we have to do with modern configuration management. And it just got me wondering, why must we babysit all of these configuration files? You know, why must I manually verify and commit all of these minor schema tweaks? And so those two scenarios had me
longing for the Drupal 7 days. You know, config lived in the database. Drupal kind of maintained itself. You know, you committed the core files or FTP them like I used to do and you ran updates on the production server and the CMS and let the CMS manage itself. You know, it wasn't an ideal situation. You know, you had to make updates to lower environments in production manually
or with complex deployment scripts. But for some reason, it just sure felt easier. And does anybody remember the features module? >> Yeah. Yeah. Features was meant to allow us to package intent. You know, a blog and a corresponding functionality was packaged up into into code and that could just easily be installed and transported across sites. And this was quickly misused by all of us as a substitute
for modern configuration management. It worked. It was painful to work with, but it worked usually. But it got me thinking, you know, did we go too far? We moved from like the wild west of configuration management to the strict bureaucracy that we're in now. You we gained consistency, but we lost freedom to let the CMS manage itself. And so last summer I had some downtime to like
truly think about this configuration and specifically how it rates relates to automation. And the questions I wanted to answer were how can I keep my side project out of the digital graveyard sacrif without sacrificing my evenings and weekends? You know, how can I automate the mundane so I can focus on the fun parts of being a developer again? or more importantly, how can I automate the maintenance
of Drupal so I can focus on bringing more value to our clients? And from that ideation, the thought I had was if we can shift site building from a point of maintaining system state and instead focus on building building our sites with intent, we can liberate ourselves from maintenance and focus on features And so that idea shifting from state to intent felt like a personal breakthrough for
my side projects. But as I looked to where the community is headed, I realized this isn't just about my weekends. This theory applies to more Drupal projects than just my own. You know, with Drupal CMS, we are inviting a more no code or low code experience. We are giving content editors project browser and telling them to go ahead and install what you want. You know, configure your
site. It's yours. Do what you want with it. But as a developer who has relied on modern configuration management, that terrifies me because in our current system state model, if an editor installs a module or tweaks configuration via the UI, my next deployment with config import is going to systematically erase all of their changes. So right now we are essentially telling our users, you can have freedom
or I can have a stable deployment pipeline, but we can't really have both. And so how do we bridge that gap? How do we give our editors control of their sites while keeping the sanity of our developers? And to do this, I believe we have to stop trying to control every single YAML file. You we have to stop being configuration custodians. Sorry, forgot to advance my slide.
Basically, yeah, we want to try to stop controlling every YAML file. So how do we make that shift? How do we become feature architects? You know the answer lies with intent I think. So instead of exporting a snapshot of the entire database state, we can start defining the intent of our site. So with in the current state system, you know, we say the configuration the configuration of
the site must match identically to what's in the file system, right? But if we focus our our thinking to one of intent, we can start thinking, I don't care what else is happening in the database, but this site must have a news content type with specific fields and a search page, right? And so by focusing on intent, we can create a safe zone for both developers and
site editors. The editor can start to use project browser and explore new features, and the CMS can handle its own internal schema updates. you know the nulls and the zeros and the reordering of of config and but our defined intent can ensure that our core business logic remains untouched and that the governance we set up when we when the site was built remains intact. So, which had
me asking, can we misuse I I mean repurpose recipes to manage our site's intent. And let me be clear, I say repurpose recipes because recipes were never meant to be used for ongoing They were meant to be used as a starting point for a project. But I wanted my weekends back. And I and I also wanted my side project up to date. So I asked myself, what
if I stopped treating our site configuration as a monolithic snapshot? And what if I misused recipes to act as the primary source of truth for the things I care about? And by doing that, can we keep the essential intent of our site in code and remove the strict configuration management? And can we achieve a workflow that feels like Drupal 7 with the modern configuration power of Drupal
11? And so that led me to reimagine the whole development workflow of a Drupal project using recipes as the source of truth. You know, a workflow that stops having us be custodians of configuration and starts focusing on the intent of the important. So here's what this workflow of bliss looks like in theory, right? So it starts with detection, a trigger of some sort. In this automation example
I'm I'm talking about it's it starts with like a dependabot. You know a bot identifies that a package needs updated in your in your project. That's going that it doesn't ask for permission. It just starts the process and updates the package. But from there we need a we need almost a digital twin of production. We need to simulate what a developer would do locally. And we can't
test these updates in a vacuum, right? The CI environment needs recent access to a production database that will allow us to test against reality, not just a clean install or a three-month old database dump. It needs to be next to real-time clone of production. This really needs to locally with DDEV. And from so from that step, it kind of moved on to the the operation step or
the updates. So the CI environment will do the boring things like composer install and drush update database and Drupal core and contrib can perform their necessary schema updates the nulls the zeros the reordering of of config keys. So then the next step of that is what I'm calling the the relication step. And so this is the pivot. This is this is kind of where this talk came
from is instead of doing a full config import at this point which will wipe out a content editor's changes. What if we selectively reapplied our core recipes of intent? This forces our business logic or our intent back onto the newly updated database in a CI environment. And if that update broke our intent or if the recipe fails to apply here, the process can stop and manual manual
in intervention would would ensue. But if the recipe succeeded, then we can move on to how do we validate that our intent remains And so that's the next step of this is validation. So here is is we can use an endto-end testing tool such as playrite or cypress or Drupal test traits to take the place of the human developer validating nothing's changed or nothing's broken with with
that update. You know, these tools don't look at YAML, but they inter interact to with the site through a browser, right? You know, does the submit button exist? Is the testimonials block we enforced with our recipe still visible? You know, if the machine sees what it it expects to see, we have proof of health with with a real database. And then from there, it can move on
to the deployment step. So only after end to end tests prove the intent is intact do we merge and deploy automatically in some cases. you know, the site is up to date, the boring config changes are accepted, and our features are preserved through the recipe relication step. And so, the beauty of this architecture is that the 161 configuration files I was complaining about earlier, they still exist.
They still get updated, but I never had to look at them. I never had to read them. I never had to dip them. I never had to commit them. The pipeline validated, the site is still healthy, and they and they just get deployed. So that's the theory. Can recipes be misused to take the place of And I wanted to test this theory out. So back to my
recently rebuilt side So I started my my side project is you know hosted in GitHub or the version control is GitHub. So I enlisted dependabot to manage the dependencies for me and I configured it to run weekly and make pull requests for each dependency and to assign a PR to me to review. You know there's nothing new about this project. I'm sure we all know what dependabot
is. But the point is the dependabot updated a package for me. I didn't have to touch it. And so the next step is how can I how can I give dependabot the context of my site. You know dependabot is great at opening pull requests but it's context blind. It knows Drupal core went from 1133 to 1134 but it has no idea what that update does to your
data in your database. And a code update is meaningless without a database update behind it. And we can't just hope the update hooks run correctly. We need we need to prove it. But how do you trust a complex Drupal update against an empty database or a three-month old export? So, we really need to test against reality. We need a very recent copy of And to obtain that
database copy, we can enlist the MySQL toolkit library or MTK for short. And so MTK is an open source tool and it's defined by their website as a toolkit for exporting, sanitizing, and packaging MySQL databases. And so with this tool, we can create a Docker container that contains our production database sanitized, scrubbed, safe for use within a CI environment. You know, MTK is fully configurable and can
be set to truncate the noise like watchdog logs and cache tables. It can scrub users field data of email addresses and passwords. It can remove PII from custom tables based on our specific rules. But the result becomes the digital twin of our production database. You know, it has the same configuration, the same content structure, the same state as the live site, but it's completely safe to use
in a transient CI container. So now when dependabot creates a pull request for the site, we can run deployment operations against this recent copy of the database. And so the next step is the enforcement of our intent. So we have a pull request with package update. We have a recent copy of the database for context. So in the normal workflow, this is when we would, you know,
run Drush update database and then wonder if Drush config export was going to make any changes. If it did, we would have to diff that config commit and push it to our repo. And if we don't commit those changes, our site is out of sync. But if you do commit them, we're just back to babysitting configuration again. But instead of config export and config import, what if
we just needed to run drush recipe my recipe? And with this mindset, we are not asking Drupal what its current state is, but rather we are telling Drupal what our intent is. So think of the recipe as the DNA of your site's specific business needs. Your recipe says regardless of what core does with its internal schema, this specific field must remain required and this view must use
this specific filter. And by reapplying the recipe, we ensure that our custom features survive the update process. You know, we let the CMS handle its own housekeeping while we protect our specific business requirements. And if all that passes, then we go and move on to the validation step. You know, at this point, CI has done all of the work of a package update. It updated the code.
It updated the database. It enforced our intent by apply reapplying the recipes. And now we need to validate that intent by testing the experience and not the schema. So with an endto-end testing tool like Playr or Cypress, we can automate the testing within our CI But really each we need to validate that each recipe should have its own sorry each recipe should have its own end to-end
test that validates the important features. You know does field config match the recipe? Does the submit button work? Is the hero placed correctly? Here would be a great time to run visual regression tests. But if endto-end testing gives us the green light, we don't need a second opinion. We can just merge and deploy the automated update. The 161 config changes, our reapplied site intent all flow into
production. It's the dream. It's an automated update, secure website, our business intent validated and confirmed working all while I literally did anything else. And if you don't believe me, I did have a quick demo of my side project, which this clearly is not going to be readable on this screen. But it starts off by this is the read me of that project of my side project. Basically
saying I took the original config management and converted all of it to use recipes and basically took the content model and turned it into a recipe. And basically, yeah, like I like I said, I started the process by defining the important pieces of my configuration from this current site, focusing on the content model and the layout builder configuration that was already in place. You know, I broke
each recipe into small pieces of functionality. You know, each content type became its own recipe. And each of those recipes I bundled into a recipe called content model. And then I added a user roles recipe to manage roles and permissions. You know, all of the important things that matter to to this site. But then at last the last step is I created a recipe called the essentials
which is just a recipe of recipes and is what is used during the deployment steps in the relication step. And essentially this is the article content type recipe. It's defining storage. The only thing it's the only thing in the recipe that is strict when it comes to configuration is the Everything else is minimally exported but everything else is really focusing on using configuration actions like create if
not exists or set components. You know these are the essential part of how this flow can work. So by relying on exported configuration the recipe relication will fail as a UU ID of an exported config will already exist. And to prevent that from happening, you can declare all of your config as strict false and then rely on config actions to set the values of your config. And
this will allow the relication to occur without failures. This is the dependabot configuration. Basically, it creates a PR for every package and it runs once per week. But then here is the MTK configuration that I mentioned. So this this config lives in your code and it has a rewrite section at the top which essentially uses SQL commands to you know strip you know strip usernames you know
strip email addresses strip it's doing the same thing for the next line down is the comment field data you know it's replacing IP addresses just sanitizing the database to make it safe for use and then there it has a no data section at the bottom which you can hardly But this this is basically no structure or this is structure only. You know, strip out all of the
content and just leave me with the database structure. But then I also created a GitHub action here which this let me back up. This site is hosted on Upson now, not a VM. And this GitHub action actually tunnels into the Upson environment. It gathers the database credentials. It uses MTK to dump that database in a safe way and then it creates a package for of this database
and saves it to GitHub's container registry. And this doesn't only work with Upson. I've proved it out with Pantheon and Aquia as well. And when I converted the configuration for the side project from config management to recipe based workflow, I also ended up creating playwright endtoend tests for it. And basically here GitHub action runs an end toend testing step that takes that MTK generated package and runs
this normal update database um the normal deployment workflows. But the key step here is if you could read that config import is not part of that step. Instead of that, it's doing rush recipe apply. And since implementing this workflow for this side project last summer, all of my packages have updated automatically. Here are the closed pull requests since August. 68 of them altogether authored and closed and
automatically deployed to production by dependabot. And as I mentioned earlier, this site is hosted on Upson. And this is the Upson configuration deployment configuration steps. And again, those steps right there are identical to what CI is running. And Drush config import is not part of it. But wait, there's more. This workflow doesn't stop in the cloud because as we know, as we all know, the biggest hurdle
to a productive Tuesday morning isn't usually the the deployment pipeline. It's an out-of-date local database. So this is the final step using DD plus the MTK plugin. We can use the database that CI already has. You know, it's already been built and proved out in CI. It's been sanitized. And we can bring that exact same database into our local environment. You know, we can connect DD to
the MTK containerized database. And now when I start my day, I don't have I'm not just pulling code. I'm pulling the same sanitized fresh production database that CI is using. You know, it's safe by default. I'm working with real data structures, but I have zero PII risk. There's no emails. There's no passwords. There's no passing database dumps through Slack. There's no stress. I get instant par with
with the CI environment. It's my local environment and CI are identical. And then there's really no more config export and import with this process. You know, when I want to build a new feature, I don't export and import YAML files. I pull the latest NTK dump. I build and apply my new recipe of intent. And if it looks good, I commit the recipe and add it to
the essentials. So, it's it's kind of the full circle. We can bridge that gap from the developer's guilt of a of a neglected side project to a professional-grade automated ecosystem where the developer the CI bot and a local environment are all in alignment. And just a quick recap, you know, in the current way, the strict way, a developer has to manually update a package and export config.
But in the blissful way, you can let automated tools do it for you. you know, instead of manually exporting and diffing your config, you just enforce the intent of your recipes back onto the database. And instead of clicking around and hoping things didn't break, you can focus on the endto-end testing tools that validate that your intent is is matches what you expect it to be. And then
your local dev, there aren't any more lo out of local databases. You just use MTK to pull your containerized database down. So instead of a developer manually updating your packages and babysitting config, automated tools can do this all for you in your sleep without an LLM. And so just some tips that I've found while you know proving this theory out. Um basically your recipes you definitely want
to prefer granular over monolithic You want to rely on config actions as much as possible. you know like set label or set required set components. You want to safeguard the config with ensure exists or create if not exists. Certain configurations should remain strict such as field storage since that affects database structure directly. But the volatile config settings should all be set with config actions. The rule being
if an admin can easily change the value through the UI, then the recipe should use a config action to set that value. And then just remember that with this with this workflow, deploy now really means apply. And then you just want to make sure you test everything in isolation. You know, starting with a minimal installation and just ensuring your recipe applies clean cleanly. in a clean install
but also in your production database clone. And then you don't want to rely on imports as much as possible. Right? So a recipe will import configuration from the config directory. But if you if you rely on that and you set strict equal false and then you try to reapply your recipe, that config is not going to overwrite like you expecting it to. Yeah, basically that's what I
said. You don't want to you want to keep the config out of UI. Um once you install a recipe, you can't like uninstall it. I think everybody who's talking about recipes today will tell you that. And then don't forget the the re reapply step. So if you create a brand new recipe for your project and you apply it, make sure it reapplies cleanly. Oops. And that that
concludes it. You know, I wanted it all and and now I kind of have it. You know, so by shifting our focus of configuration management from state management to intent management, you can bridge the gap between the flexibility of Drupal 7, Drupal CMS, and combine it with the powerful configuration management that we that we have. You know, developers can focus on features and leave mundane things to
automated tools. you know, we can easily access secure copies of production data free of PII and we do not developers don't need to diff their way through YAML during their next round of module updates and websites gain more automated testing this way. They can stay up to date and secure as new releases come available and they can run on modern infrastructure and not have to update VMs.
And yeah, thank you. Um, if you have any questions, please let me know. Here's a bunch of resources from the things we discussed today. And please leave feedback if if you can. >> Question about the database from production. So tool kit but production usually grow to size. So how do you uh compact this to not bring all the terabytes of data from production to the local just
simple features? So you should instead of millions of nodes you should just copy only five for example but if you do this on the master level you should take care of all the references relationships and so on. So it's very >> yeah so the question is about database size right? How do you not have a production database that's many many gigabytes big into the CI environment? And
with the MTK toolkit, you can you can conf configure the SQL to, you know, whatever you want it to be. So you could truncate some of those tables or so there's some of those big tables. It's all very configurable. So you can just say, hey, this table I only want the first 50 rows or 100 rows or really depends on your project and and what that means.
but it's very configurable to to shrink the size of that database if if needed. So I think it's really just depends on the project that you're working on and it would be really be up to to you to make that decision of what what gets stripped out. on the data end. It's very hard to check what you have to for example the some relationship field >> you
should not export some related entities for example and you have no connection without actually so it's almost impossible to go >> better to do some on the side not It's really impossible. >> Yes. And you're right. It it is very hard to know what to strip out. And personally, I don't think I would use this for every site. It's this this is this isn't an idea for
every site, but I think it works well for like the smaller sites, right? You know, when you know it's like a marketing site that you you're going to set up once and it's going to be live for a year or two and it might go away. I think it's a good good candidate for that type of site, right? But for for some of those bigger ones or
some some you know healthcare ones right or that have stricter development flows I don't think it is probably the right >> we implemented the similar approach but yellow and so you on the development stage you create five mods and just export them as a test and is used for all the test and so on. No production is touched at all. >> So this approach I think is
easier than every every time get to build from scratch all your website with all the crucial entities. >> Yeah, I think that makes sense. And like I said, it's probably a case by case basis. >> Thank you. >> Yeah, you're Yeah. suggest more complexity translate into a recipe. Um, >> so you know the question is is my my suggestion of a smaller site you know why is
that and I do think the recipe complexity could get more difficult right you know the examples I have it's just a content type and just some user permissions it's not you know it's not that complex but I think you could get to some more complex data structures but you I think you would have to start from the beginning It's hard to retrofit an existing site into this
kind of model. But I do think you could use some of some of the tools here. You know, the MTK part of it is is really one of the important parts to me, just not having to pass databases around or even connect. So there's bits and pieces you can probably pull out of this, but yeah, I do agree that like a really large site making that recipe
is going to be difficult >> for these small sites like how comprehensive the test coverage you're doing. The reason I'm asking is like play is great but it's also one of the most expensive testing. You got you got Drupal. Yeah. >> And like how are you balancing that so that you can know you got enough test coverage but then not break the bank? >> It's a great
question. You know, how do we the question being how do we know we have enough test coverage being that playright is expensive when it comes to testing? And I I don't know that I have an answer for I think it's whatever you're comfortable with and whatever your project is comfortable with. But I do think that having like the relication part, you know, making sure that your intent
is still there. That's probably the most important part for, you know, for the for the recipe relication kind of testing. You know, anything custom before that would probably be caught in more of a kernel or a unit testing step, which I don't think you should get rid of those. But yeah, I it it's really probably just depends on the project. >> Uh how do we create that
recipe? >> Is there a draft command that can help you or just >> uh how to create the recipe yaml? Um I personally just just did it myself. Just manually created it. I'm not aware of of something that would configure that for you. Um, I think I know like site templates you can export the whole site that way, but not specific recipes. >> So, I think I
think you kind of already answered this, but I just want to make sure I understood correctly. Like you you're not using config export config import at all for these, right? Okay. >> No. Yeah. So basically, I'm not using config export in this example, right? I I it was in config management, you know, like we normally were used to, and I just pulled the pieces out that were
important to this site and turn those into recipes, and then I just stopped using config, export, import. >> Okay. So, you're only kind of managing just the parts you want, >> correct? >> So, a lot of this uh automation maintain dependencies and all that. How would you do something that like where you do actually need to update for let's say a few changes or something like that
actually lives in how do you sync that between environments then do you just apply it right to production and just kind of sync that down or is there a process to get granular config >> I mean if the the question is how do you the config to the lower environments. And in that case, if I knew that config needed to change and I had a recipe that
was managing that, >> I would add that to the recipe and then run it up the deployment, run it up to all environments and let the recipes reapply itself. >> So your recipes become your kind of source of truth. Then >> that's correct. >> For all of it. >> Right. And then I know recipes allow you to use environment variables in them and that's how you can
change certain config when needed specifically thinking like API keys. >> I think you kind of already answered that as well, but uh I would ask about so for example sometimes where you have to specifically have the configs when you're using for example with config split or config ignore those other things that you have to change specifically. >> Uh but I guess you just enter like you can
set some environment variables and things that you would simply ask your recipe to get those values from different force instead of being from the uh config gamma file. >> Right? So yeah, the question is, you know, how do you handle that config split or config ignore? And that's exactly right. I would I would stop using config ignore because you don't care about what's in the database, right?
You don't need to if somebody wants to update a web form, great. Or create a new one, that's great. Let them. And then as far as splitting it, I would rely on the environment variables to change those values as needed. awesome. Well, nobody has any more questions. Thank you.