Skip to content

feature branch: refactor experimentation to not use FluxC#244

Merged
wzieba merged 100 commits intotrunkfrom
experimentation_refactor
Sep 9, 2024
Merged

feature branch: refactor experimentation to not use FluxC#244
wzieba merged 100 commits intotrunkfrom
experimentation_refactor

Conversation

@wzieba
Copy link
Member

@wzieba wzieba commented Aug 30, 2024

wzieba added 30 commits August 26, 2024 10:56
We don't need milliseconds precision here, so it's better to have the same unit for `timeToLive` and `fetchedAt`
`experimentStore` is no longer used
This makes maintenance of the SDK easier, and it's not a big setback for clients, as the value for `platform` has to be only checked once.
Even though the ExPlat doesn't work correctly (we don't ever save data in cache) the `ExPlatTest` suite is passing, just because everything here is mocked.
Majority of experiments I've been testing require `anonId` parameter, hence this change.

Allowing consumers to specify `anonId` is also something that iOS counterpart does: https://github.com/Automattic/Automattic-Tracks-iOS/blob/ccf6b7d7424e32857819d35aa54813875557f5c8/Sources/Experiments/ExPlat.swift#L65

I've decided to introduce another state field in `ExPlat` for convenience of library consumers. They'll likely inject `ExPlat` class as Singleton, so clearing/reconfiguring its state on e.g. user change will be easier than injecting another ExPlat to DI graph.

iOS counterpart uses Singleton pattern.
…non_id`

There's no value in sending an empty parameter
So SDK consumers can build `ExPlat` instance
In order to remove `runBlocking` from `ExPlat`, we have to have a way to get value from cache in a way, that doesn't require sync file-operation. That's why I introduce `latest` field, which keeps the latest cache value in memory.

Providing dispatchers is considered a good practice and might come handy during tests
Now, that we have few new/changed tests, I extract some similarities to make tests more readable
This is a confusing API, because, even if user requests a refresh, we don't return fresh data immediately, but only queue this to fetch. This is not something I'd expect as the SDK consumer.

Also, we already have specialized refresh methods. I don't think we need more.

I plan to remove `shouldRefreshIfStale` in another commit.
This commit adds a new class: `CacheDto` that saves previously saved data with addition to `anonymousId`. This additional field will be used for cache validation.
The `initialize` method now takes a mandatory `anonymousId` parameter and is responsible for setting the anonymous identifier and triggering the initial assignment refresh and cache validation if `anonymousId` is different
I came to a conclusion, that `refresh` is not needed for the client: SDK should on its own manage the state of the cache and not delegate additional responsibility to SDK consumer.

The cache is refreshed whenever needed during initialization
As SDK doesn't offer refreshing anymore, we don't need to use internal-only `RefreshStrategy`. Refactor seems successful as unit tests are still passing without any changes
If ExPlat was not initialized, log or throw an exception for quicker debugging
It doesn't have to be exposed to SDK consumers
This can be used by WP/JP or WC apps
Maybe it can be useful to, e.g., be able to add network interceptors for logging or performance monitoring
…mandatory

Make anon id mandatory. Simplify API, remove `refresh`
@wzieba wzieba marked this pull request as ready for review September 5, 2024 09:58
@wzieba
Copy link
Member Author

wzieba commented Sep 5, 2024

Review

hi @necronet 👋 . This PR consist several smaller PRs and some of the code is Android platform-specific, so I thought about leaving some context to make the review easier.

  • The main API contract class is VariationsRepository. See the attached code comments.
  • To see how the SDK should be integrated, check out experimentation/README.md file and/or this basic integration POC [DNM] Experimentation integration POC pocket-casts-android#2800
  • I've build the sample app, so you don't have to install all Android tooling. I'll send it on Slack

@wzieba wzieba requested a review from necronet September 5, 2024 10:05
@wzieba wzieba added the ExPlat label Sep 5, 2024
Copy link

@necronet necronet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM I checked through the code and the test app. Making the right call and with the right parameters.

Screenshot 2024-09-06 at 16 27 13

As requested the fluxc and wellsql dependencies have been removed, and these changes now use a filebased system for caching the variations in the Repository class provided to ExPlat.

I'd enforce a non-empty anonymous id throught this sdk though instead of anonymousId?.let { addQueryParameter("anon_id", it) } but I am not sure if it will break something else, as this something that we discussed it should be fine, API can handle this scenario quite well.

@wzieba
Copy link
Member Author

wzieba commented Sep 9, 2024

Thank you for the review @necronet !

I'd enforce a non-empty anonymous id throught this sdk

That's a good point, but this is how the proposed change works now:

override fun getVariation(experiment: Experiment): Variation {
if (anonymousId == null) {
return guardAgainstNotInitializedSdk()
}

private fun guardAgainstNotInitializedSdk(): Control {
val message =
"ExPlat: anonymousId is null, cannot fetch assignments. Make sure ExPlat was initialized."
val exception = IllegalStateException(message)
logger.e(message, exception)
if (failFast) throw exception
return Control
}

When anonymousId is not provided and variation is requested, we return Control or crash the app (if it's failFast, usually in debug).

The anonymousId?.let { addQueryParameter("anon_id", it) } part you mention is solely for safety if something unexpected might've happened with anonymousId.

@wzieba
Copy link
Member Author

wzieba commented Sep 9, 2024

I'd enforce a non-empty anonymous id

After reading the comment again, I've enhanced this guard with a isNullOrBlank check (instead of only null check).

@wzieba wzieba merged commit 4656d85 into trunk Sep 9, 2024
@wzieba wzieba deleted the experimentation_refactor branch September 9, 2024 11:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants