Skip to content
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

RUM-3462 feat: Introduce DataStore #1730

Merged
merged 3 commits into from
Mar 22, 2024

Conversation

ncreated
Copy link
Member

@ncreated ncreated commented Mar 19, 2024

What and why?

📦 This PR introduces a DataStore interface designed to store key-value pairs for a given feature. The primary goal is to provide a mechanism for persisting data between app launches.

For the full list of use cases and requirements considered in this implementation, see RFC - Local Datastore (internal).

How?

New APIs

DataStore interface:

public protocol DataStore {
    func setValue(_ value: Data, forKey key: String, version: DataStoreKeyVersion = 0) {}
    func value(forKey key: String, callback: @escaping (DataStoreValueResult) -> Void) {}
    func removeValue(forKey key: String) {}
}

public enum DataStoreValueResult {
    case value(Data, DataStoreKeyVersion)
    case noValue
    case error(Error)
}

public typealias DataStoreKeyVersion = UInt16

DataStoreKeyVersion (an UInt16): Values are persisted in files and SDK upgrades may occur between app launches. To ensure proper data reading, the caller can specify the version of expected data on read. This way it can handle the situation when data was written in old format, so it must skip decoding it in the new format.

The DataStore is specific for a feature. It can be obtained through FeatureScope interface:

public extension FeatureScope {
   func dataStore() -> DataStore
   func dataStoreContext(_ block: @escaping (DatadogContext, DataStore) -> Void) {
}

This comes in-line with Event Write Context API that already exists on the FeatureScope:

public protocol FeatureScope {
   func context(_ block: @escaping (DatadogContext) -> Void)
   func eventWriteContext(_ block: @escaping (DatadogContext, Writer) -> Void) {
}

TLV format

Data is encoded in TLV format, leveraging prior work on batch files and recent refinements in #1725.

Review checklist

  • Feature or bugfix MUST have appropriate tests (unit, integration)
  • Make sure each commit and the PR mention the Issue number or JIRA reference
  • Add CHANGELOG entry for user facing changes

Custom CI job configuration (optional)

  • Run unit tests for Core, RUM, Trace, Logs, CR and WVT
  • Run unit tests for Session Replay
  • Run integration tests
  • Run smoke tests
  • Run tests for tools/

@ncreated ncreated self-assigned this Mar 19, 2024
@ncreated ncreated force-pushed the ncreated/RUM-3462/introduce-core-data-store branch 2 times, most recently from a0fefeb to b7948b2 Compare March 19, 2024 13:05
@datadog-datadog-prod-us1
Copy link

datadog-datadog-prod-us1 bot commented Mar 19, 2024

Datadog Report

Branch report: ncreated/RUM-3462/introduce-core-data-store
Commit report: b7948b2
Test service: dd-sdk-ios

✅ 0 Failed, 2903 Passed, 0 Skipped, 12m 29.43s Wall Time
🔻 Test Sessions change in coverage: 8 decreased, 5 increased, 1 no change

🔻 Code Coverage Decreases vs Default Branch (8)

This report shows up to 5 code coverage decreases.

  • test DatadogTraceTests tvOS 49.35% (-0.39%) - Details
  • test DatadogRUMTests iOS 79.43% (-0.38%) - Details
  • test DatadogTraceTests iOS 49.32% (-0.33%) - Details
  • test DatadogRUMTests tvOS 79.64% (-0.27%) - Details
  • test DatadogCrashReportingTests tvOS 28.69% (-0.22%) - Details

@ncreated ncreated marked this pull request as ready for review March 19, 2024 14:27
@ncreated ncreated requested review from a team as code owners March 19, 2024 14:27
maciejburda
maciejburda previously approved these changes Mar 19, 2024
Copy link
Member

@maciejburda maciejburda left a comment

Choose a reason for hiding this comment

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

Wow! Really nice piece 💪

Great tests, nice docs. I like how it's exposed to features 🎖️

One thing I would consider around naming is dropping Core to avoid having CoreData. It might confuse some developers.

DatadogCore/Sources/Core/DataStore/CoreDataStore.swift Outdated Show resolved Hide resolved
DatadogCore/Sources/Core/DataStore/CoreDataStore.swift Outdated Show resolved Hide resolved
}
}

extension CoreDataStore: Flushable {
Copy link
Member

Choose a reason for hiding this comment

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

nice!

@ncreated ncreated marked this pull request as draft March 21, 2024 08:33
Comment on lines +28 to +37
/// Retrieves the data value associated with the result, if it matches the expected version.
///
/// - Parameter expectedVersion: The version expected for the retrieved data.
/// - Returns: The data value if the version matches the expected version; otherwise, nil.
public func data(expectedVersion: DataStoreKeyVersion = dataStoreDefaultKeyVersion) -> Data? {
guard case .value(let data, let storedVersion) = self, storedVersion == expectedVersion else {
return nil
}
return data
}
Copy link
Member Author

Choose a reason for hiding this comment

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

Alternatively we can change the semantic to make it always return Data (if found) with performing an optional check on expectedVersion (if provided) as in following code. I have no strong opinion.

public func data(expectedVersion: DataStoreKeyVersion? = nil) -> Data? {
    guard case .value(let data, let storedVersion) = self else {
        return nil
    }
    if let expectedVersion == expectedVersion, storedVersion != expectedVersion {
        return nil
    }

    return data
}

Copy link
Member

Choose a reason for hiding this comment

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

not sure if version expectation should be built in.

I would assume that the consumer of the api will handle different model version migration

Copy link
Member Author

Choose a reason for hiding this comment

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

The version is now made a pure convenience. It is fully hidden in the basic usage:

store.setValue(data, forKey: "key")
store.value(forKey: "key") { result in
   _ = result.data()
}

If only versioning problem occurs, we can leverage it:

store.setValue(data_in_new_version, forKey: "key", version: 2)
store.value(forKey: "key") { result in
   _ = result.data(expectedVersion: 2)
}

I think this is the best balance between having this support and keeping a clean API. Requiring the caller to handle versioning will cause unnecessary repetitive code infection across different places.

Copy link
Member Author

@ncreated ncreated Mar 21, 2024

Choose a reason for hiding this comment

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

Requiring the caller to handle versioning will cause unnecessary repetitive code infection across different places.

Also, if version is not encoded for every data upfront, we won't be able to ever support migration from "no version" to "version: 2". The decoding failure will become ambiguous and (if not filtered somehow) will flood telemetry with false-positives.

Comment on lines 226 to 229

/// Retrieve data store.
/// - Returns: Data store configured for storing data for this feature.
func dataStore() -> DataStore
Copy link
Member Author

Choose a reason for hiding this comment

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

The presence of DataStore in FeatureScope interface limits its usage to only DatadogRemoteFeatures (the ones that can write and read batch files). In consequence, it won't be available in following features:

  • CrashReportingFeature
  • NetworkInstrumentationFeature
  • BacktraceReportingFeature

While last two are rather fine, there might be use cases for DataStore in Crash Reporting. I don't foresee any at this point, hence I don't consider it blocking.

Even if DataStore is required for CrashReportingFeature at some point, I rather thing we should work on the FeatureScope interface rather than re-design the DataStore management. I'm open for discussion 🙂.

Copy link
Member

Choose a reason for hiding this comment

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

I rather thing we should work on the FeatureScope interface rather than re-design the DataStore management.

Totally 👍

/suggestion can we use a var instead?

@ncreated ncreated marked this pull request as ready for review March 21, 2024 11:34
maxep
maxep previously approved these changes Mar 21, 2024
Copy link
Member

@maxep maxep left a comment

Choose a reason for hiding this comment

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

It looks great, well done!!

I have left minor suggestions but it's good to go 👍

DatadogCore/Sources/Core/TLV/TLVBlock.swift Outdated Show resolved Hide resolved
TestUtilities/Helpers/DDAssert.swift Outdated Show resolved Hide resolved
Comment on lines 226 to 229

/// Retrieve data store.
/// - Returns: Data store configured for storing data for this feature.
func dataStore() -> DataStore
Copy link
Member

Choose a reason for hiding this comment

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

I rather thing we should work on the FeatureScope interface rather than re-design the DataStore management.

Totally 👍

/suggestion can we use a var instead?

Copy link
Member

@maciejburda maciejburda left a comment

Choose a reason for hiding this comment

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

Nice! Thanks for taking the extra effort and improving the API 💪

Copy link
Member

@maxep maxep left a comment

Choose a reason for hiding this comment

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

Nice!

@ncreated ncreated merged commit 6d7d3aa into develop Mar 22, 2024
8 checks passed
@ncreated ncreated deleted the ncreated/RUM-3462/introduce-core-data-store branch March 22, 2024 12:21
@maxep maxep mentioned this pull request Apr 10, 2024
8 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants