New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enh: Introduce FindInstanceInterface
- Part 1.a: Creating base classes
#6503
base: statable
Are you sure you want to change the base?
Conversation
@luke- could you please update @gevorgmansuryan this is the first step towards #6482, hence I'm including you here, too! |
Originally posted by @gevorgmansuryan in #6457 (comment):
I've been looking a bit into the possibility of using a behavior here. It looks like an elegant solution at first, specially with the idea that behaviors can be attached and detached to a component dynamically without requiring modification of the component class. However, I have not found a way to actually do attach a behavior to let's say a ActiveRecord instance without modifying the thing. The reason being, that there is no event called when objects are instantiated so that I could listen to that event and then attach the behaviour. So we would have to
But with that behavior we would have to listen to events again (after save and update) and we cannot add static functions (as So we could do the cache cleaning using a behavior, but we would still need to change the classes in order to implement Or is there anything I've missed here? |
Yes, I don't think we can add e.g. a In general, I frankly don't know if the current solution, which is really very nicely worked out, doesn't introduce too much new complexity and non standard (Yii2) behavior for me. I basically like the idea of a But I would have liked to implement this with 2-3 new methods. This PR really covers a lot of cases: Relations, When I look at the "Membership" model as an example, I really have trouble understanding right away what exactly is going on in the Currently, I think it's good that we rely very heavily on well-documented Yii 2 standard components. If we now start to change or introduce other approaches, we have to document it somehow but also maintain it in the long run. So I would prefer to introduce the simplest/clearest possible caching, which may only covers 80% of the cases, rather than something complex to achieve a bit higher coverage. For example, it is probably clear for most Yii 2 developers that I'm not sure if I'm being too critical here... maybe we should discuss this with the team... |
@luke- Thank you for your review and comments!
I guess that's your job and I'm grateful you are a critical reviewer!
Yes, that's true. Some explanatory thoughts:
I hope, my new comments in the code help. See also above.
Agree.
Sure.
Was that looked into? I mean, why not using the built-in db-query cache?
See my comments above. I don't think we can ignore the
Sure. Appreciate any feedback! |
f3da8da
to
933c4dc
Compare
933c4dc
to
7813a81
Compare
@martin-rueegg Thanks for the response. I'm soon in a meeting and need some time to reply. In the meantime an untested/buggy but totally simple/stupid example of a Of course there is (or it need) much room for improvements. (Interface, Behavior, Trait, Multi PK,..........) |
@luke- Thanks! I had a look into the dependency stuff but did not get my head around. That's helpful in your example. Well, helpful in the sense of understanding the usage of dependencies. However, since we do not currently serialize the cached values, the dependency would simply be ignored: But, yes, I get your point. And it could be a reasonable solution to avoid invalid objects in the store (if we can solve this with the dependency). Things I see unresolved:
I'm getting the impression, we're trying to reduce complexity so much but missing the targeting by doing so: If for the classes where the most repeated calls are expected, we do not have a cache, then for the others, we need to cache the objects often twice, since it's first accessed by one key, and then the other, then we create more overhead than we solve. Also, your draft is 60 lines long, and it will also require some other 10 lines for the missing Of course, we could gain some lines by removing some options, or the exception suffix, or some other small stuff. But while saving a few lines, we have to add back those checks in the originating code. In the end, that's making the change for the 181 lines we save with this PR. If you deduct those from my 260 lines above, I might still end up better with only about 80 net plus. :-) |
@martin-rueegg Thanks for the points, it definitely makes things clearer to me. Unfortunately, I have not enough time right now, come back to you in the next days... have a nice weekend! |
@martin-rueegg Thank you for your patience. In my example, it's not just about the sheer number of lines but also about complexity. Some thoughts:
However, I understand that it makes sense to create a solution that covers as many cases as possible, even if it makes it more extensive. To ensure I understand all the challenges, here's a list:
Here are some suggestions:
|
* Sort profile fields on People directory filters * Update CHANGELOG-DEV.md
@luke- Thank you for your time to review this and for your comments.
I understand.
You are apparently referring to the follow-up PR I'm happy to add some explanatory comments there. In the version of this PR (#6503), the PR #6528 on the other hand uses tag dependencies as suggested by your example. I find this also a bit too complicated and yii's TagDependency implementation seems to be too sophisticated for our purpose (which should also be super performant, given it's a runtime cache). I would opt for the middle way of the current PR and in case the
Yes, documentation has been left aside so far. I appreciate that documentation would be helpful for reviewing. On the other hand, writing a lot of documentation for something that is not yet in principle approved, is also very time consuming. I'll do my best to be more verbose in future PRs to make reviewing easier. Otherwise maybe a review on a video call might also safe some time?
Actually, this is not the PK, but a non-enforced unique key. (Side note: on DB level this is not enforced. There isn't even a index on this combination in neither direction.)
Yes, it is used. However, it is only used in follow-up PRs, where the implementation of The
In the current implementation there is a slight distinction between "record retrieval" (i.e. record retrieval (
cache management Now this mechanism is somewhat independent of the configuration that is used to retrieve the record. Maybe this could be streamlined by using a function like
In case the record is removed from the cache, some additional steps are performed:
yes.
correct.
This could be a good solution, yes. There is one implication:
Which ones would that be? I could see the following simplifications:
The deletion of the cache would also have to happen on every update, though. Since we have flags that would allow the return of the record (e.g. soft delete) or it's related records. Given that we currently store the record under different keys, the deletion of those keys is not really a big issue. Also, the deletion of the related records are just a few lines of code in I guess the biggest simplification is done by omitting the TagDependency that is implemented in #6528 (Part 1 b). Either we can identify the record, or we delete the entire cache. In most cases, |
ceab243
to
2dd3626
Compare
…ntActiveRecord class
2dd3626
to
5c2db8f
Compare
Before we can really continue with this,
|
ff59fb8
to
cbcc9cc
Compare
cbcc9cc
to
f0f9acb
Compare
f0f9acb
to
f011512
Compare
FindInstanceInterface
- Part 1.a Creating base classes
FindInstanceInterface
- Part 1.a Creating base classesFindInstanceInterface
- Part 1.a: Creating base classes
Introduce FindInstanceInterface
Part 1.a Creating base classes
FindInstanceInterface series:
FindInstanceInterface
- Part 1.a: Creating base classes #6503FindInstanceInterface
- Part 1.b: TagDependency #6528FindInstanceInterface
- Part 2: Implementing throughout the code #6463Functionality
$model::FindInstance($identifier, ?iterable $simpleCondition = null)
is the main new method in this PR. It provides a simplified way to get a single instance of a model that is implementingFindInstanceInterface
. It allows:$model::class
instance by['object_model' => some::class. 'object_id' => 123]
guid
, if it is defined in the model's implementation (and if the primary key is not a string itself)status
orcategory
columnsUniqueIdentifiersInterface::getUniqueIdVariants()
, if available, orRuntimeCacheHelper::buildUniqueIDs()
as a fallback with some sane defaults: using$model->getPrimaryKey()
(if aBaseActiveRecord
),$model->id
and$model->guid
(if they exist), and$model->getUniqueId()
(if exists).$model::getUniqueIdVariants()
(see previous point for details) once normalised.Implementation
A model has to either
CachedActiveRecord
, or mustFindInstanceInterface
directly.Deriving from
CachedActiveRecord
Just deriving from
CachedActiveRecord
should cover the basic functionality. However, some adjustments can be considered by overriding the following methods:findInstance()
: Useful if you want do define a default value (e.g. for the current user inUsers
) or to allow a special key combination (e.g.[$space_id, $user_id]
forMembership
) or something along those lines.validateInstanceIdentifier()
: Useful to specify a default key for string identifiers, e.g.guid
as used inSpace
getUniqueIdVariants()
: Useful to provide additional key variants used to store the record in the cache, seeContent
afterSave()
andafterDelete()
: Useful to delete the cache before performing additional steps, as done inContent
RuntimeCacheHelper::deleteVariants($this);
afterSave
, but skipping deleting the cache again:ActiveRecord::afterSave($insert, $changedAttributes);
Please note, that this works if you directly inherit from
CachedActiveRecord
or any intermediate class does not override theafterSave()
method. So please double check.Please also see notes on Implementing
FindInstanceInterface
below.Implementing
FindInstanceInterface
For this, one may use the
FindInstanceTrait
as a reference implementation or include it directly. In the latter case, the following functions may have to be "redirected" to the trait if additional functionality needs to be added (ContentContainer
may serve as an example here):find()
should return an instance ofCacheableActiveQuery
afterSave()
should delete the current record from cacheafterDelete()
should delete the current record from cacheupdateAll()
should delete all affected records from cache, or the entire cachedeleteAll()
should delete all affected records from cache, or the entire cachePlease also see notes on Deriving from
CachedActiveRecord
above.Background
Since #6457 was only a partial solution for #6375 as it
findMembership
anddeleteAll()
andsaveAll()
and linked content),there is an improved attempt in #6463 to address this in a broader way, and ultimately #6482, which addresses some more issues.
To break #6463 down, I've created this PR for review, and I've submitted towards the
statable
branch, as #6430 also bilds on top of #6463. Once, #6463 is merged instatable
too, both can be merged intdevelop
, which I recommend to happen before v1.15 is released.PR Admin
What kind of change does this PR introduce?
Does this PR introduce a breaking change?
findInstance
, rather thanfindOne
where the searched object is only identified by eitherid
orguid
(see additional examples in Enh: IntroduceFindInstanceInterface
- Part 2: Implementing throughout the code #6463).The PR fulfills these requirements:
develop
branch, not themaster
branch if no hotfixFix #xxx[,#xxx]
, where "xxx" is the Github issue number)