con

Problem/Motivation

For #3170204: Add option to remove modules as if they were uninstalled we need to have "partial splits" which track the changes to config that depends on the module we split but don't need to be split entirely. This becomes especially important once #2571235: [regression] Roles should depend on objects that are building the granted permissions is in Drupal core.
So we need this functionality anyway and I feel like this is what the conditional split should have been all along but wasn't because it is hard to do. (It still is hard but now with the config storage transformer events it is a bit better).
Keeping the conditional split (ie split the entire config from the active storage and leave the one in the sync directory as is) seems to be an unnecessary maintenance burden and it will just confuse users why they should use that.
Less configuration options may make it easier to understand what is going on.

Proposed resolution

Instead we would still have the configuration option for partial splits much the same as the current conditional split minus the checkboxes (ie only split when different is enforced and the dependent config is not included)
Then the split would only contain the difference of the active config to the sync store and the sync store would be left as is.
See also #2934170: Add partial split for specific keys

Remaining tasks

I can also see the point of having the entire config copied to the split storage so that it is insulated from changes to it in the sync storage. Ie you split something off conditionally because you need it to be kept in the sync storage too but you don't care how the config in the sync storage changes when the split is active.
Hence the question and this issue to get feedback.

Options include:

  • Replace conditional split with partial split
  • Add a sub-module that can bring the conditional split back
  • Add a third config selection section and keep both the current conditional split and the new partial split.
  • Create a new version 3.x and switch it there. Someone will have to maintain 2.x though

Maybe there are other options too. please feel welcome to suggest other alternatives.

Please provide feedback if you are using the conditional split feature.
And please explain your use case if you would like to keep it in its current form.

Comments

bircher created an issue. See original summary.

bircher’s picture

Issue summary: View changes

Some more prose that will hopefully end up on the drupal planet rss feed has been posted to the Nuvole blog.

https://nuvole.org/blog/2021/aug/04/changes-coming-config-split

It contains some more details of how this could work.

akalam’s picture

I think having partial splits would be an amazing power-up on config_split.
I could help new week on developing a poc, could you give me some guidance?

bircher’s picture

Thanks for your excitement and your offer!
But I already have a proof of concept (This is what generated the yaml snippet in the blog post). What is still missing is test coverage, especially for possible edge cases like two or more splits which affect the same config in different ways. So help with that is for sure welcome. Follow the linked issues where I will post patches in the upcoming month.

For sure partial splits are a cool thing, I don't think anyone would debate that. The question is: do we ditch the current behavior (of conditional splits) and make the new major version of the module functionally different? Or does the conditional split still serve some purpose? Without any community input i would just ditch the old configuration options and only support the new way...

So a comment saying that this is good is also valuable.

akalam’s picture

My first thoughts is that partial configs could completely replace conditional splits (aka graylist). My only concern is the upgrade path. Currently the existing sites could have a a file called node.type.recipe.yml under config/sync and another file called node.type.recipe.yml under the config/sites/my_site which it related to a config split.
According to your post, after the introduction of partial splits there would be a breaking change, so even if the 2.x release is a beta, there are + than 2900 sites using it, so IMO creating a 3.x branch could be justified and would let it be released with a more relaxed upgrade policy without breaking the existing sites.

bircher’s picture

Well I am not concerned about the upgrade path, in fact when you import the split folder that was created with config split 1.x then everything will be just merged like it was before (provided it doesn't contain config_split config since the schema will change). Then when you export the config things in config/sync/ will remain the same but things in the split folder will be changed the way described in the blog post.
The only thing that won't work is to import config created with config split 2.x on a site where you downgraded to 1.x again. But as with everything else in drupal, downgrading is not supported.

The only reason not to ditch the conditional split is if you need a copy of the config in the split folder for some config for some reason. I would like to understand that reason, because otherwise I don't have the motivation to maintain a feature that makes the UI more confusing and I don't think anyone would want.

Also beta means there will be a update path, not that there wouldn't be any changes. But thanks a lot for the feedback! It means a lot to me.

rkoller’s picture

@bircher thank you for the write up and the work that went into the poc. to me the changes for 2.x with partial splits read easier to comprehend and to wrap the head around for people, especially those getting into configuration splits (the the name partial split is also more approachable). and the quasi-patch form has a way better legibility and is way more explicit imho. the cognitive load is way less than in the current implementation. i like it a lot. especially that "added" and "remove" are named and distinguished and the changes are listed underneath each. i like that a lot! double thumbs up!

and one question in regards of partial splits: are they more robust, less error-prone and especially less maintainence effort for module maintainers compared to conditional splits especially if core api is used now?

bircher’s picture

RE #7: Thanks!

I don't know what you mean by how it affects module maintainers. It should not affect module maintainers at all. This config patch is a "virtual" config that only exists in the split folder and never in the active config. You can not add it to config/install or config/optional of your module. That would be the domain of the config_actions module.

If you mean for the maintainers of config split, then it makes it more complex because we now have to merge individual config arrays and not just merge the folders. But we don't get around that if we want to split config as if the modules had been uninstalled. There are likely going to be some bugs especially with sorting and empty arrays but we will address those when we know more about the bugs.
The usage of the core API makes this possible because with the Config Filter API it is not possible to know if the config is imported or exported, just if it is read or written to the storage.

If you mean site builders, then it should not make a big difference (apart from additional bugs) except when the config in the sync folder changes (ie from an export where the split is not active). in 1.x the whole copy from the split folder would be used when the split is active, on 2.x the patch will be applied to the new config so things that were not different when the patch was created (ie exporting the split) will be updated. This can be desired or not. This is the behavioral change I talked about. But I guess we could also retrofit it again in a new form if it is something important to many. So far I have not heard that it would be a problem, but maybe it is difficult to grasp in this abstract way without a piece of code that does it.

Thanks for the feedback.

akalam’s picture

Hello @bircher. Thanks for the updates. Then no concerns from my side regarding upgrade path.
I can't wait to see this feature in action. It's gonna be a game changer.

pyrello’s picture

Really looking forward to seeing the proof of concept code! I'm also interested in helping.

lukus’s picture

Looks really interesting.

For my own understanding would the main benefit be that it's easier to interrogate a split manually to see what the difference is?

akalam’s picture

I see many use cases when having several splits for the same config entities. Here is an example:
You have 2 splits: A and B splitting 2 fields on the article content type.
Split A add the field A, and Split B adds the field B to the site. Now let's take a look on the minimum config entities involved:
- field.storage.node.field_a
- field.storage.node.field_b
- field.field.node.article.field_a
- field.field.node.article.field_b
- core.form_display.node.article
- core.view_display.node.article

for field.storage and field.field it is clear, there are independent, the full file need to be split in each config_split, but what happen with core.form_display core.view_display config entities? they have information needed for these fields to work. In the current implementation, using conditional split you will have 2 copies of the core.form_display.node.article config entity in both config_splits with the whole configuration, and you don't have control to merge them on config import (please correct me if I'm wrong). If you try to export configuration it will be even worst, the config entity will be placed in only one of both config_splits with the complete configuration.

I wonder partial splits will save the day for these use cases.

johnpicozzi’s picture

@bircher Thanks for you work on Config Split! This module has been great and super helpful in multiple instances.

I think the changes you are proposing make sense and the cleaner yml files will help people understand what's going on.

One question I did have - These changes seem mostly under the hood (code changes) will this impact UI/UX of using and configuring config split at all?

I have done a number of trainings on config management with config split and I'm happy to help with module and functionality testing if needed.

Thanks again & be well!

james.williams’s picture

Fascinating!

I think there is a case for the partial config / quasi-patch to need to be able to not care what the values in the original sync directory version for a piece of config is. For example, taking the snippet from the nuvole article:

added: {  }
removed:
  dependencies:
    module:
      - menu_ui
  third_party_settings:
    menu_ui:
      available_menus: {  }
      parent: ''

 

What if the third_party_settings.menu_ui.parent had been changed to a different version in the original/upstream version of the config? Is the expected behaviour that the split/patch refuses to apply?

I have a use case where I use the same codebase for multiple brands' sites, but each brand has its own split, to override some things like some fields' default values. If the 'normal' value in the usual sync dir is changed, I'd still want the brands' specific value to be applied. This feels close to what you've hinted at, where there's a case for keeping config in a conditional split. But I think this case could still be fulfilled with the partial split / quasi-patch model, as long as the values in the config are ignored. For example:

added: {  }
removed:
  dependencies:
    module:
      - menu_ui
  third_party_settings:
    menu_ui:
      - available_menus # no longer mapped to a value
      - parent # no longer mapped to a value

 

I realise that's not simple either, as the menu_ui dependency there is a value to be removed, rather than being a key!

So although that's not a complete solution, hopefully that's a helpful use case to hear about?


Another use case: I've used the conditional split to attempt to keep reasoning about config simpler where possible. So, for example, changes to a webform might be imported via config sync, but as soon as changes are made manually on a production environment, I want to preserve those changes (without really needing to care as a dev). So having the webform in a conditional split, means that it's only once the production environment's version is different, that the webform would end up in the split, and therefore preserved. Until then, it is effectively 'controlled' by the version in the main sync directory, i.e. by developers using config management, rather than editors on production.

But I suspect this is a case where config_ignore could be used together with config_split. I suspect I'd look for workarounds, rather than being disappointed that config_split would no longer support that.

guptahemant’s picture

Issue summary: View changes

hi @bircher
Adding support for partial diff is an amazing enhancement to the config split module and whole config management flow in general.
For the backward compatibility point i would say as long as complete config can be replaced in partial diffs it should be fine.

I would also like to highlight https://www.drupal.org/project/config_update and https://www.drupal.org/project/update_helper module which uses a similar concept of config diffs to provide config updates, Going through the module might be helpful from implementation perspective.

At the end i would say a huge thank you for initiating this discussion.

bircher’s picture

RE #10:
The code for the quasi-patch is essentially the following:

class PatchItem {
  public static function createFromData(array $original, array $new): self {
    $item = new self();
    $item->added = DiffArray::diffAssocRecursive($new, $original);
    $item->removed = DiffArray::diffAssocRecursive($original, $new);

    return $item;
  }

  public function applyToArray(array $config): array {
    $changed = DiffArray::diffAssocRecursive($config, $this->removed);
    $changed = NestedArray::mergeDeepArray([$changed, $this->added], TRUE);
    return $changed;
  }
}

Some details are omitted here, there is some special casing and maybe we have to do something smarter altogether. We need lots of edge cases tested.

RE #11: Well, no that is not the benefit, if it is easier to understand then that is great but I think it is also not difficult to understand that there are several versions of the same config and the one in the split wins. Also this concept is not going to go away. The benefit is that you can use a split to track changes instead of just a whole copy.

RE #12: Yes this is an interesting use-case and we should write an automated test for that. This should be possible in 2.x but not because of this feature discussed here but because of #3170204: Add option to remove modules as if they were uninstalled which necessitates the quasi patch that allows the partial splits feature in the first place. So for this use case you would NOT use partial splits.

RE #14: The quasi-patch would still be applied, see the code-snippet from above. So probably you can make your brand sites work the way you want also with 2.x As for what is "patched" it is all, keys and values but there might be some issue with empty arrays.
As for your webform example, this would work almost like that with the "lenient" mode from #3223748: Allow to configure different behaviors. but if you want to update it as long as it hasn't changed on production then it is maybe easier to write a small module that does just that. I would help review the code for that module if you want.

RE #13: Yes this would be affecting mostly the code, but the form would loose some obsolete checkboxes and conditional split would be renamed to partial split. But all these comments made me think that maybe I should add a collapsible fieldset and call it advanced and collapse the partial split by default. The conditional split was firsts made "because we can" and was probably mostly used to address shortcomings addressed by #3170204: Add option to remove modules as if they were uninstalled or to create "overrides" for cases like described in #14. So the partial split serves to address the same kind of use-cases and also "because we can" since the quasi-patch is already needed anyway.

RE #15: Yes I am aware of those modules and they essentially transform the config into a string and pass it to \Drupal\Component\Diff\Diff. One to display diffs (where this makes a lot of sense) and the other (extending from the first) to create patches that can also not apply. The use case is a bit different and I think we can keep it simpler. But also we need to do more because our "patch" needs to always cleanly apply. When you export the config and the split is splitting things apart you would expect there not to be a diff when you straight import the config again. So the merging needs to re-create the original with the same sorting etc. Something that is still an unsolved problem in Drupal as far as I know, so some in-satisfactory and hacky code is likely needed.

nedjo’s picture

I haven't taken the time yet to study the overall proposal so this comment is just about the config 'quasi-patch' portion.

Rather than comment extensively here, I've updated a relevant core issue, #2960941: Provide an API for persistent configuration alters, including for extension-provided config. tl;dr: Selective and persistent alters to existing config is a need that's occurred all over Drupal since the first 8.x release. The proposed format looks fine but it would be great if we didn't have to limit it to config_split or add yet another config diff format to the growing list of attempts in contrib. The ideal would be to fix this in core. Short of that, or as an initial step, to try to standardize in contrib.

bircher’s picture

RE #17: Thanks for linking to the core issue, I am following it. I would love to use the core service that can do the following:

  • diff two config arrays and return a patch,
  • apply the patch to a config array with a given name.

The blocker for all of this is that even if I try to do some array arithmetic there is no easy way to sort or make sure the result is valid. The core issue for that I think is #2852557: Config export key order is not predictable, use config schema to order keys for maps. Or a follow up if that issue doesn't expose that functionality.

I consider the patch format and all the logic around it that I need for implementing the feature #3170204: Add option to remove modules as if they were uninstalled an internal implementation detail. Config Split is not going to provide this as an API. The moment Drupal core has the API which is needed for this Config Split 2.x will move over to that and I will remove the workarounds without any BC concerns. (maybe just to make sure we can still read the files in the split)

That said, this is of course meant to be a playground for the eventual inclusion in core. See also the related #3170680: [meta] Add features which serve as a proof of concept for config_environment in core.

So far none of the other modules I checked have this problem solved in a re-usable way.

But we digress. I feel like the discussion here is mostly about details of what seems to be the foregone conclusion to indeed replace the conditional split with a partial split. I am happy with that. In fact I have already committed #3228506: Remove 1.x feature parity tests

bircher’s picture

Status: Active » Fixed

Hi everyone wanting to help: you can test the latest commit!
If it works well for you I can tag a new beta before I add more shiny new things.
(ie update their composer.json with for example "drupal/config_split": "dev-2.0.x#6549ec57bb5ec9e3f060da60e81833529a744a7f",)

Thanks everyone for the feedback!

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

jastraat’s picture

@bircher - unfortunately the title of this issue was misleading as to the scope of the change that was just pushed in the last release. The newest release does not just replace conditional split with partial split (which I can understand the reasoning). It seems to also replace the complete split with the quasi-patch approach you outlined.

In comment 6 you said:

The only reason not to ditch the conditional split is if you need a copy of the config in the split folder for some config for some reason. I would like to understand that reason, because otherwise I don't have the motivation to maintain a feature that makes the UI more confusing and I don't think anyone would want.

And there may not be a reason to keep conditional split separate. But for complete splits, there are multiple use cases.

We've been using config split very successfully for feature bundling with multi site. In addition to splits by environment, we've created splits for specific packages of webforms per site. We have migration splits when we easily create a package of migration scripts. We use feature splits for new development. And the previous method where all the config files are still present and readable just as though they were in the main sync directory is crucial to all those use cases.

(And yes, I know that you can wrap migration scripts in a module instead, but config split worked well and was fast and convenient.)

We had a very unpleasant experience this week where we were running composer update locally and watched as all the custom migration scripts in the migration split folder were deleted. We always pre-backup and post diff the configuration directory for updates so there were no major consequences for us. But we all know there are site maintainers out there who apply minor module updates without those precautions. If any of them are using similar strategies they could have a very nasty surprise on their next set of contrib updates.

I've really loved the config split module and have found it versatile for a variety of use cases. But I do think that if you were going to make such a massive direction change and actually wanted feedback, the comment period should have been more than a few weeks and the title of this issue could have been significantly more clear that more than conditional splits would be affected. And then that massive change really should have coincided with a major version change to 3.x since folks tend to be more cautious when applying a major version change.

Edit: I do love the UI changes in the latest release.

I have actually gotten inconsistent results when upgrading. The first time I upgraded it removed my config directories. That upgrade was combined with the recent core security upgrade if that could be relevant.

The second time I upgraded only the config split module to more closely look at exactly what had changed and it did NOT remove my split directory configuration. Instead it simply added the "storage" key set to folder.

I'm going to continue doing more fine grained testing to see what the effect of this upgrade is on various use cases. My only caution to folks is this may be an instance where you want to isolate this module upgrade by itself.

bircher’s picture

RE #21:
I am sorry if this was not clear enough. But it in fact is really only for the conditional split. The complete split still completely splits the config you specify and "moves the yaml file to the split folder". The difference is only how those config items are selected. In 1.x everything that was directly or indirectly depending on something you split was split off automatically as well. Now in 2.x things that you list explicitly or things that would be deleted if you would uninstall a module you split will be split completely, the other config which depends on those will be changed to not depend on it any more and the change is saved as a config patch.
This is the real motivation for the config patches. And now since we have those patches the question was: why don't we use it also for the other scenario where you want to split some things that are different in some simple config object that needs to remain in the sync storage.

If you have seen some config being removed from the split folder then maybe you have not run the update hook. The config schema of the splits changed a bit and the keys changed. The update hook should update your splits, so all you need is to run the update hook and then export the configuration. The other reason could be that you were relying on the dependencies chaining, in which case you have to list all the things explicitly.

jastraat’s picture

@bircher thank you for providing more background on exactly what changed.

The rationale for the partial splits completely makes sense. The dependencies chaining is likely something that could have caught me, (e.g. split just the migration modules and assume all migrations would be split) and I'll make sure my splits list all split configuration explicitly.

bircher’s picture

RE #23 thanks for the feedback, I will try to remember putting something about this in the release notes of 2.0.0 and on the project page next time I update it with 2.x info.