Updated: Comment #29
Problem/Motivation
We have two distinct implementations of entity storage controllers: ConfigStorageController and DatabaseStorageController (with some subclasses).
These are each used for one of ConfigEntityInterface and ContentEntityInterface. You cannot store a content entity in Config, and you cannot (AFAIK) put a config entity into the DB.
This has led to some assumptions in the entity storage code, tying each controller to a specific subset of entity types.
Proposed resolution
Provide a third implementation that works for all entity types.
Do not use this storage out of the box, except in tests.
Remaining tasks
N/A
User interface changes
N/A
API changes
API additions only.
| Comment | File | Size | Author |
|---|---|---|---|
| #59 | interdiff.txt | 17.87 KB | tim.plunkett |
| #59 | kv-entitystorage-2208617-59.patch | 46.35 KB | tim.plunkett |
Comments
Comment #1
dawehnerTechnically it would be a feature request.
Comment #2
tim.plunkettOne patch with just these changes, one with the other issues from https://drupal.org/project/issues/search?issue_tags=KV%20Entity%20Storage
Comment #3
tim.plunkettDuh, the standalone won't fail until I use it somewhere...
Let's try this!
Comment #6
tim.plunkettLeaving NW until the last blocker goes in.
Comment #7
tim.plunkettThis would be really nice if normalizer was not in an optional module.
Comment #8
tim.plunkettComment #9
tim.plunkettOkay, here it is. I'm still changing FilterFormat here until I write tests.
Comment #11
tim.plunkettThat was quite the interdiff I provided there...
Ideally this will only Drupal\system\Tests\Entity\ConfigEntityImportTest and I can remove the FilterFormat change
Comment #13
tim.plunkettHad to put an ugly hack in to deal with ContentEntityBase::__construct() being lazy and assuming that FieldableEntityStorageControllerBase::create() will handle all of its values :(
But we have tests! And I found a cool ConfigEntity bug that I'll probably split out.
Comment #14
tim.plunkettComment #15
berdirYeah, not sure about the create() flow going through the storage controller.
#2096899: Add $EntityType::create() to simplify creating new entities and #1867228: Make EntityTypeManager provide an entity factory and #1867228: Make EntityTypeManager provide an entity factory are related to that, but I'm also a bit worried about too many public static methods on an entity class (with those two issues, you need to be aware that Node::create() is supposed to be called from the outside but not Node::createInstance().. huh?)
Comment #16
tim.plunkettThis has 4 explicit blockers (and contains those patches), see the "Referenced by" section in the sidebar.
The 2 issues in the "Related issues" section are nice-to-haves, and have corresponding @todos in code.
Those @todos are also the only places I have checks for ConfigEntityInterface and ContentEntityInterface.
I rebased on the 4 blockers, so there is no interdiff. But I am adding a patch without those other patches applied, to see the scope of "changes" here (additions only!)
Comment #17
sun+1,000 — This represents an actual proof that it is possible to use a key/value store for entities in D8. As such, I consider this a task, not a feature.
It looks like the storage controller has to copy/duplicate a lot of code, which we should be able to provide in an abstract base class or trait...? — Given how much common/standard logic code appears to be copied/duplicated here, that would certainly be a very good idea (separate issue).
Is there a particular reason for why revisions are not supported?
Do we actually need to normalize entities? Is a serialization not sufficient? (Related: #2216527: Inject a serialization format into database key/value storage)
Comment #18
tim.plunkettGlad you agree, I didn't at first but by #8 I'd come around :)
Yeah maybe 90% of KeyValueEntityStorage is boilerplate, we absolutely need to provide *something* to make that less. I had no idea when I started how many assumptions there were about what entity storage is responsible for.
Because I'm mostly familiar with ConfigEntity, which doesn't support them, and I didn't want to make this harder than it had to be.
I'm open to adding revision support, but I'd like that to be a follow-up.
Well we're working in other issues to have an EntityInterface::toArray() method, which is really what we want.
Both entity types right now load back all of the values to pass back through the constructor, which is different than unserializing the entity itself...
Comment #20
tim.plunkett#2188565: Test coverage for Comment, Custom Block, Node, Taxonomy Term and User entity cache tags added another method with a \Drupal::entityManager() call, mocking that fixes the test.
Comment #22
tim.plunkettEasy enough fix, but I'm going to stop posting patches until these blockers get in.
Comment #23
tim.plunkettThis should not be committed until #2223361: Rename ComplexDataInterface::getPropertyValues() to toArray(), remove setPropertyValues() goes in, but I need to rework this patch to use that when it happens anyway. Just retesting now that the four other blockers went in!
Comment #24
tim.plunkettAll of the API changes have been split out to other issues, this is purely an addition.
Postponing until #2223361: Rename ComplexDataInterface::getPropertyValues() to toArray(), remove setPropertyValues() is in, here's the patch.
Comment #25
tim.plunkettAll blockers in!
This is just nice proof that this is even possible.
Comment #27
tim.plunkettRerolled after The Great Entity Storage Rename
Comment #28
tim.plunkettContext conflict with changes in core.services.yml, rerolled.
Comment #29
tim.plunkettComment #30
larowlanCan this be expanded, as this behaviour is different to eg DB storage which most are used to. Something like 'The entity does not have an ID, in order to use KeyValueStorage for entities you must provide and ID.'?
this will only support a single sort, perhaps we should throw an Exception if two are passed?
Other than that, looks ok to me.
What's the next step - enabling this in more tests to save on DDL - anywhere we don't need revisions?
Comment #31
berdirAs a result of the ID thing, this only supports config entities, and this won't make them faster :)
As discussed, we could make a Sequence service that by default is a simple wrapper for $database->nextId() to solve that.
I'm not sure exactly where we want to go with this. Core currently already has 4 entity storage implementations (if you cound the old database one and null ;)) but not an alternative config entity storage, as this issue showed on the other hand.
Comment #32
tim.plunkett#30.1 See #31, we probably need to address this.
#30.2 Not sure what you mean, that part was copied straight from the config entity query.
As far as using it in more tests, I don't know if it will help.
#31 I guess I'll open an issue for a Sequence service, unless someone beats me to it.
The null doesn't count for sure, and the old DB one will be going away after MenuLinks are converted, AFAIK.
Yes, this mostly benefits config entities, but ideally there would be no real difference!
Comment #33
sunAwesome! :-)
This looks pretty much ready to me — aside from the ID thing being discussed above, I guess...?
Can we add a description to the class to clarify that revisions are not supported currently? (possibly as a @todo even?)
Ideally plus any other high-level information on potential pitfalls and noteworthy stuff that you think would be worth to share for ensuing ages :-)
Oha. :) I found the location of this class and cross-component class inheritance a little bit confusing here — but huh, yeah, I assume you wanted to bundle all the related tests into a single spot, and I guess the situation is going to be weird regardless where we place 'em... ;)
So I think this is fine, even tho a little bit hard to spot
Any particular reason for why this is a WebTest and not DUTB?
The test doesn't seem to contain any web/requests, just API level assertions? Therefore, it should work as DUTB?
That said, regarding tests, I wonder whether we should actually add one web test that extends an arbitrary existing web test that covers some basic add/edit/delete operations of a common entity that isn't revisionable (e.g., user or comment). — I.e., just extending that existing test, add
$modules, and overriding the content entity storage in the test module; the existing test should pass. It would certainly not be a complete coverage, but at least some basic proof that it actually works with non-test-entities, too? (also happy to defer that to a follow-up issue tho)Comment #34
sunDiscussed some more with @tim.plunkett today:
This patch actually presents a working proof of concept for a key value entity storage implementation as-is. — It is awesome that we actually managed to make it possible.
Due to current feature constraints of our
KeyValueStorebackends, it is only able to work with string identifiers (machine names) right now. — However, that is sufficient for config entities.For content entities - or to be more precise, entities using serial IDs - the storage backend must be capable of understanding a concept of
nextId():nextId() = max() + 1.Due to that, #2161643: Add a KeyValueStore\IterableKeysInterface (allowing to retrieve keys with a given prefix) is very closely related:
The new interface introduced there only focuses on retrieving a filtered sub-set of key/value pairs for now, but technically the requirement here is asking the k/v backend to be capable of iterating over keys, too; just with a slightly different purpose:
Ignoring values, just focusing on keys, enable
nextId()within a collection.As such, that could be added to the new
IterableKeysInterfacein a separate follow-up issue, once it has landed.I think it's awesome that we are actually this close already, and I'm confident that we're able to figure out that last remaining issue regarding
nextId().I think we can move forward in independent steps - since the current incarnation works at least for config entities already.
→ Enabling it to additionally work for content entities (or rather, entities with serial IDs) would make most sense as a separate follow-up issue.
Comment #35
tim.plunkett+1 on the path forward described in #34.
Addressed #33.1 and 33.3. Not concerned much about 33.2, nothing really to do about that.
Comment #36
sunThanks! :-)
Comment #38
sun35: kv-entity-storage-2208617-35.patch queued for re-testing.
Comment #40
tim.plunkett35: kv-entity-storage-2208617-35.patch queued for re-testing.
Comment #42
tim.plunkettAh, broken via #1709960: declare a maximum length for entity and bundle machine names. Back to RTBC.
Comment #43
berdirSpeaking of that and the related issues, this should come with a validation of the id length similar to ConfigEntityStorage, as this is limited to 128 characters right now in HEAD.
Comment #45
tim.plunkettYep.
Also updated the existing test to allow my subclass to pass.
Comment #47
sun45: kv-entity-2208617-45.patch queued for re-testing.
Comment #48
tim.plunkettThis is blocked by #2240709: ConfigImportUITest::testImport fails when the module list changes now, it seems :(
Comment #50
jibranWhy are there no test methods in this class?
Comment #51
tim.plunkettBecause they're all in the parent class. It just re-runs that tests but with the different storage backend.
See keyvalue_test_entity_type_alter().
Comment #52
tim.plunkett45: kv-entity-2208617-45.patch queued for re-testing.
Comment #54
tim.plunkettFixes after #2182239: Improve ContentEntityBase::id() for better DX.
Comment #55
sunYay, finally green again! :-)
Comment #56
alexpott54: kv-entity-2208617-54.patch queued for re-testing.
Comment #57
tim.plunkettJust thought I should call this out:
This is the only change, the rest are all additions. It's not a functional change, just allowing the subclassed test to specify its own max length (128 vs 166).
We have 1 unit test, and two webtests, one for entity_test and one for config_test (content vs config entity)
Comment #58
tim.plunkettThis was RTBC first, but #2225955: Improve the DX of writing entity storage classes overhauls how entity storage classes work, and will need a bit of adjustment once that goes in. So I'm preemptively postponing this on that one.
Comment #59
tim.plunkettRerolled since that went in. Here are the relevant changes (removed 80 lines from the storage class!).
Comment #60
neclimdullets do it.
Comment #61
xjmI think this might merit a change record for the feature addition?
I started to ask why we were overriding this, but then realized we aren't -- ConfigEntityStorage specifies its own and content entities in DB storage have serial IDs. :) Right.
Comment #62
sunI don't think this is in a state to be announced as a feature of its own — as long as content entities aren't supported yet (cf. #34), this implementation only covers <50% (= entities using a machine name string/key as IDs). IMO, we should only announce this when content entities are supported (requires separate follow-up issues).
Comment #63
xjmThanks @sun. @Berdir said something similar in IRC. Makes sense.
Comment #64
berdirBased on my discussion with @timplunkett earlier this week, I would go even further and say that this is 90% a test-only implementation (and will stay that way), a fancier version of the array based test field storage that we had in 7.x, if you will. (as the issue summary says)
You can't really use it for config entities, as they have to be in config, and it can not scale for content entities that need any kind of querying or even listing...
Comment #66
catchRead through this a few times and couldn't find much to complain about, looks like it'll come in useful and help us avoid introducing assumptions into the other entity storage.
Committed/pushed to 8.x, thanks!
Comment #67
tim.plunkettThanks!