-
Notifications
You must be signed in to change notification settings - Fork 13
feat(Local evaluation): Use the new context-based engine #109
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
base: feat/context-values
Are you sure you want to change the base?
Conversation
116f50c
to
59da131
Compare
59da131
to
d925f98
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good! Some minor questions/comments
$this->analyticsProcessor, | ||
$this->defaultFlagHandler, | ||
$identityModel->compositeKey(), | ||
if ($this->localEvaluationContext === null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see that the existing function didn’t have this check. What was happening earlier—a cryptic error?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Either that, or this function was never called without an ->environment
. This is to keep things closer to other implementations, e.g. Python.
foreach ($evaluationResult->flags as $flagResult) { | ||
$flag = new Flag(); | ||
$flag->feature_name = $flagResult->name; | ||
$flag->feature_id = (int) $flagResult->feature_key; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens if we can't convert feature+_key to INT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup good question.
AFAIK feature_key
is expected carry the numeric ID of a feature (see references for PHP and Python) so this is consistent with other implementations.
It doesn't mean I like it, though. I'd rather choose between:
- Change data type of both
FlagContext.feature_key
andFlagResult.feature_key
tointeger
, embracing the type from our current data model. - Don't assume it will always be an integer, and keep the string type, while deleting the old
Flag
model so this translation code isn't necessary.
Either way, I think any change here may fall out of scope and would prompt for team efforts towards standardization. IMO we should target deprecating the old models but let me know if you'd rather tackle this separately across SDKs now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am okay with 1 or adding a metadata field with feature id if we can't go with 1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm onboard with either, slightly leaning towards a metadata field only if it doesn't contribute to complexity. cc @khvn26
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't agree we should change the data type in the context schema. On the contrary, I think that, long-term, we should aim to store/pass ids as strings in SDKs.
- The perfectly correct solution would be to use metadata.
- We don't have to introduce feature metadata now; for now, the mapper layer could store a feature_key: flagsmith_id mapping to use when constructing
Flags
fromFlagResult
s. - Both of the above come from the assumption we absolutely want to keep
Flag.id
an integer, which makes little sense to me. I don't see anything wrong with major-versioning the SDK and making it a string, which it should be in the long run, anyway.
I'm happy to keep feature_key
a string, and proceed with either option 2 or 3 above.
src/Offline/IOfflineHandler.php
Outdated
interface IOfflineHandler | ||
{ | ||
public function getEnvironment(): ?EnvironmentModel; | ||
public function getEvaluationContext(): EvaluationContext; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed in 7a467ef.
src/Utils/Mappers.php
Outdated
|
||
$feature->variants = []; | ||
$multivariateFeatureStateValues = $featureState->multivariate_feature_state_values ?? []; | ||
uksort($multivariateFeatureStateValues, fn ($a, $b) => $a->id <=> $b->id); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are we doing this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Product-wise, I don't know. My best guess is that this is to keep a consistent order for testing. This matches the behavior of the Python SDK.
I still think we should make FeatureContext.variants
a mapping rather than a list, specially if that's the reason for sorting.
cc @khvn26
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Engine expects variants to be pre-sorted in an order determined by the Flagsmith implementation — in case with Core API, this is by MultivariateFeatureOption.id
.
I still think we should make FeatureContext.variants a mapping rather than a list, specially if that's the reason for sorting.
I don't necessarily agree; the only thing the engine needs is order, and that is provided by the order of elements in the array.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need to sort it again if it’s already provided by core in order? Or does core not return it in that order?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that it should, but, currently, there is no guarantee that it does. Counterintuitively, Django does not emit ORDER BY pk clauses by default.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason we can’t fix this on the Django side instead of implementing it in every SDK?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@gagantrivedi The reason might be related with migrating core models to have a default order by
clause, and mostly the side effects of that.
I agree input can be sorted, but it also sounds a bit fragile to make the engine rely on that solely IMO. Maybe we could have chosen to order multivariates by weight in the past, but I guess we need to keep evaluation consistent — despite major versioning the engine.
The id
field here works well, except that it assumes an integer from the data model, which isn't nice, but IMO it's acceptable given this logic only exists in this [temporary?] mapper layer. Perhaps we need a new index
field to help clarity, populated with the multivariate IDs from the environment document.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, this is a little confusing. So far, we’ve been using whatever order Django provides — we think it’s ordered by ID, but we can’t guarantee that, which is why we don’t want to make it explicit in Django. But by sorting it here based on ID, aren’t we doing the same thing? If Django is ordering by something else, and we sort by ID here, won’t the evaluation change once this is released?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason we can’t fix this on the Django side instead of implementing it in every SDK?
As discussed offline, this sounds like a correct solution and I'm onboard with doing it. However, we need to either keep the sorting in the SDK mapper layer to cover the Core API versions that do not yet guarantee the order, or document the change very explicitly to avoid unexpected changes in the evaluation.
Perhaps we need a new
index
field to help clarity, populated with the multivariate IDs from the environment document.
Not sure how making a json array more verbose by introducing a named field improves clarity here? I'd rather focus on documenting the schema more clearly.
$featuresToIdentifiers = []; | ||
foreach ($identityOverrides as $identityOverride) { | ||
$identityFeatures = $identityOverride->identity_features ?? []; | ||
uksort($identityFeatures, fn ($a, $b) => strcasecmp($a->feature->name, $b->feature->name)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are we sorting this? If it’s needed, it should happen after the empty check.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This matches the Python SDK behavior, but I don't see a reason either. Fixed in a52c914.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Python SDK sorts identity overrides by feature name to get consistent hashes for distinct sets of feature states.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
src/Utils/Mappers.php
Outdated
$segments = []; | ||
foreach ($featuresToIdentifiers as $serializedOverridesKey => $identifiers) { | ||
$segment = new SegmentContext(); | ||
$segment->key = hash('sha256', $serializedOverridesKey); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are we setting the key here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because I believe it's safer than an empty string, in case such segment keys are consumed later on. I understand this differs from other SDKs but I'd rather discuss improving the meaning here, or make this field nullable. cc @khvn26
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The consumption of segment keys is limited to the % Split operator. Generating a key here creates the false impression that identity override segments can have % Split conditions, but they can't, and I can't think of a reason to include them. An empty string is a legitimate key that clearly indicates, in my opinion, that these segments are useless for the hashing algorithm.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The consumption of segment keys is limited to the % Split operator.
Thanks for making this clearer.
An empty string is a legitimate key that clearly indicates, in my opinion, that these segments are useless for the hashing algorithm.
I would argue that an empty string can never a clear indication of anything. 😂 e.g. this wasn't the clearest to me until now, as it certainly takes context to come up with meaning.
Attempted improving with f5eaea7.
7d6f89c
to
f5eaea7
Compare
b0f060a
to
e7adeed
Compare
Contributes to #98
This is pass 2 of 3.
get_evaluation_result
+ engine tests passing. No deleted code. (PR)Warning
CANNOT MERGE before #108 (pass 1).
The client work is largely inspired by flagsmith-python-client.