-
Notifications
You must be signed in to change notification settings - Fork 3
feat: implement JSON destination #308
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
Merged
Merged
Changes from all commits
Commits
Show all changes
28 commits
Select commit
Hold shift + click to select a range
c416964
feat: implement JsonDestination
cwaldren-ld 5711b7e
update SerializedItemDescriptor
cwaldren-ld c3fb9be
implementing JSON destination
cwaldren-ld 623e174
update comments on ISerializedDestination
cwaldren-ld 533d3a6
update comments
cwaldren-ld 094905c
Merge branch 'feat/data-system' into cw/json-destination-impl
cwaldren-ld 4ca20a9
add WriteMinimal for std::vector, use in flag/segment serialization
cwaldren-ld d49e37b
add bool perator for ISerializedItemKind and descriptor
cwaldren-ld d54ae62
add kinds.cpp to CMakeLists.txt
cwaldren-ld e532133
add tests for JsonDestination
cwaldren-ld e2e4d40
more tests
cwaldren-ld fb6971c
more tests
cwaldren-ld d904a77
add test fixture
cwaldren-ld 055f40a
typo
cwaldren-ld 4b88673
update flag serialization test in internal
cwaldren-ld f00bf07
update unbounded segment serialization test
cwaldren-ld b347f33
Merge branch 'feat/data-system' into cw/json-destination-impl
cwaldren-ld 0ca8b5d
try to fix gmock segfault
cwaldren-ld d49690d
another attempt
cwaldren-ld ff5fd5a
try to not have a sigsegv
cwaldren-ld 2d48311
one more attempt
cwaldren-ld 753eb8e
think I was using gmock wrong, use testing::ReturnRef
cwaldren-ld af56baa
remove identity test
cwaldren-ld 54e6953
try only instantiating the test fixture
cwaldren-ld 8f9135b
29th try
cwaldren-ld 58122a2
fix some includes
cwaldren-ld c4bd414
add back missing tests
cwaldren-ld ea054f9
fix broken formatting on json_destination_test.cpp
cwaldren-ld File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
123 changes: 113 additions & 10 deletions
123
libs/server-sdk/src/data_components/serialization_adapters/json_destination.cpp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,26 +1,129 @@ | ||
| #include "json_destination.hpp" | ||
|
|
||
| #include <launchdarkly/serialization/json_flag.hpp> | ||
| #include <launchdarkly/serialization/json_segment.hpp> | ||
|
|
||
| namespace launchdarkly::server_side::data_components { | ||
|
|
||
| JsonDestination::JsonDestination( | ||
| data_interfaces::ISerializedDestination& destination) | ||
| : dest_(destination) {} | ||
| using data_interfaces::ISerializedDestination; | ||
| using integrations::SerializedItemDescriptor; | ||
|
|
||
| FlagKind const JsonDestination::Kinds::Flag = FlagKind(); | ||
| SegmentKind const JsonDestination::Kinds::Segment = SegmentKind(); | ||
|
|
||
| /** | ||
| * @brief Creates a boost::json::value representing a tombstone for a given | ||
| * ItemDescriptor. The tombstone includes 'deleted', 'key', and 'version' | ||
| * fields. | ||
| * | ||
| * @param T Type of descriptor. | ||
| * @param key Key of item. | ||
| * @param desc The descriptor. | ||
| * @return Tombstone suitable for serialization. | ||
| */ | ||
| template <typename T> | ||
| boost::json::value Tombstone(std::string const& key, | ||
| data_model::ItemDescriptor<T> const& desc) { | ||
| boost::json::object tombstone; | ||
| tombstone.emplace("key", key); | ||
| tombstone.emplace("version", desc.version); | ||
| tombstone.emplace("deleted", true); | ||
| return tombstone; | ||
| } | ||
|
|
||
| /** | ||
| * @brief Creates a SerializedItemDescriptor for a given ItemDescriptor. The | ||
| * SerializedItemDescriptor either represents an item that is present, or one | ||
| * that is absent. | ||
| * | ||
| * @param T Type of descriptor. | ||
| * @param key Key of item. | ||
| * @param desc The descriptor. | ||
| * @return SerializedItemDescriptor suitable for forwarding to an | ||
| * ISerializedDestination. | ||
| */ | ||
| template <typename T> | ||
| SerializedItemDescriptor Serialize(std::string const& key, | ||
| data_model::ItemDescriptor<T> const& desc) { | ||
| return desc.item | ||
| ? SerializedItemDescriptor::Present( | ||
| desc.version, boost::json::serialize( | ||
| boost::json::value_from(*desc.item))) | ||
| : SerializedItemDescriptor::Absent( | ||
| desc.version, | ||
| boost::json::serialize(Tombstone(key, desc))); | ||
| } | ||
|
|
||
| JsonDestination::JsonDestination(Logger const& logger, | ||
| ISerializedDestination& destination) | ||
| : logger_(logger), | ||
| dest_(destination), | ||
| ident_(dest_.Identity() + " (JSON)") {} | ||
|
|
||
| std::string const& JsonDestination::Identity() const { | ||
| return ident_; | ||
| } | ||
|
|
||
| void JsonDestination::Init(data_model::SDKDataSet data_set) { | ||
| // TODO: serialize and forward to dest_.Init | ||
| // TODO(sc-225327): Topographical sort of flag dependencies | ||
|
|
||
| std::vector<ISerializedDestination::ItemCollection> items; | ||
|
|
||
| ISerializedDestination::OrderedNamepace flags; | ||
| for (auto const& [key, descriptor] : data_set.flags) { | ||
| flags.emplace_back(key, Serialize(key, descriptor)); | ||
| } | ||
| std::sort(flags.begin(), flags.end(), [](auto const& lhs, auto const& rhs) { | ||
| return lhs.first < rhs.first; | ||
| }); | ||
| items.emplace_back(Kinds::Flag, std::move(flags)); | ||
|
|
||
| ISerializedDestination::OrderedNamepace segments; | ||
| for (auto const& [key, descriptor] : data_set.segments) { | ||
| segments.emplace_back(key, Serialize(key, descriptor)); | ||
| } | ||
| std::sort( | ||
| segments.begin(), segments.end(), | ||
| [](auto const& lhs, auto const& rhs) { return lhs.first < rhs.first; }); | ||
| items.emplace_back(Kinds::Segment, std::move(segments)); | ||
|
|
||
| if (auto const result = dest_.Init(std::move(items)); | ||
| result != ISerializedDestination::InitResult::kSuccess) { | ||
| LD_LOG(logger_, LogLevel::kError) | ||
| << dest_.Identity() << ": failed to store initial SDK data"; | ||
| } | ||
| } | ||
|
|
||
| void JsonDestination::Upsert(std::string const& key, | ||
| data_model::FlagDescriptor flag) { | ||
| // TODO: serialize and forward to dest_.Upsert | ||
| data_model::FlagDescriptor const flag) { | ||
| LogUpsertResult(key, "flag", | ||
| dest_.Upsert(Kinds::Flag, key, Serialize(key, flag))); | ||
| } | ||
|
|
||
| void JsonDestination::Upsert(std::string const& key, | ||
| data_model::SegmentDescriptor segment) { | ||
| // TODO: serialize and forward to dest_.Upsert | ||
| data_model::SegmentDescriptor const segment) { | ||
| LogUpsertResult(key, "segment", | ||
| dest_.Upsert(Kinds::Segment, key, Serialize(key, segment))); | ||
| } | ||
|
|
||
| std::string const& JsonDestination::Identity() const { | ||
| return dest_.Identity(); | ||
| void JsonDestination::LogUpsertResult( | ||
| std::string const& key, | ||
| std::string const& data_type, | ||
| ISerializedDestination::UpsertResult const& result) const { | ||
| switch (result) { | ||
| case ISerializedDestination::UpsertResult::kSuccess: | ||
| break; | ||
| case ISerializedDestination::UpsertResult::kError: | ||
| LD_LOG(logger_, LogLevel::kError) | ||
| << dest_.Identity() << ": failed to update " << data_type << " " | ||
| << key; | ||
| break; | ||
| case ISerializedDestination::UpsertResult::kNotUpdated: | ||
| LD_LOG(logger_, LogLevel::kDebug) | ||
| << dest_.Identity() << ": " << data_type << " " << key | ||
| << " not updated; data was stale"; | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| } // namespace launchdarkly::server_side::data_components |
80 changes: 77 additions & 3 deletions
80
libs/server-sdk/src/data_components/serialization_adapters/json_destination.hpp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,24 +1,98 @@ | ||
| #pragma once | ||
|
|
||
| #include "../../data_components/kinds/kinds.hpp" | ||
| #include "../../data_interfaces/destination/idestination.hpp" | ||
| #include "../../data_interfaces/destination/iserialized_destination.hpp" | ||
|
|
||
| #include <launchdarkly/logging/logger.hpp> | ||
|
|
||
| #include <string> | ||
|
|
||
| namespace launchdarkly::server_side::data_components { | ||
|
|
||
| class JsonDestination : public data_interfaces::IDestination { | ||
| /** | ||
| * @brief JsonDestination is responsible for converting flag and segment | ||
| * models into serialized data suitable for storage in an | ||
| * ISerializedDestination. | ||
| * | ||
| * By encapsulating the serialization logic here, different adapters can be | ||
| * swapped in if our serialization format ever changes. | ||
| * | ||
| * JsonDestination does not currently initialize ISerializedDestination with a | ||
| * flag-dependency-order payload, which is required to minimize bugs in | ||
| * stores without atomic transactions (e.g. DynamoDB). | ||
| * | ||
| * Instead, it sorts items within a collection using 'operator<' on their keys, | ||
| * giving which is enough determinism for testing purposes. | ||
| * | ||
| * TODO(sc-225327): Implement topographic sort as prerequisite for DynamoDB. | ||
| * | ||
| */ | ||
| class JsonDestination final : public data_interfaces::IDestination { | ||
| public: | ||
| JsonDestination(data_interfaces::ISerializedDestination& destination); | ||
| /** | ||
| * @brief Construct the JsonDestination with the given | ||
| * ISerializedDestination. Calls to Upsert will trigger serialization and | ||
| * store to the destination. | ||
| * @param logger Used for logging storage errors. | ||
| * @param destination Where data should be forwarded. | ||
| */ | ||
| JsonDestination(Logger const& logger, | ||
| data_interfaces::ISerializedDestination& destination); | ||
|
|
||
| virtual void Init(data_model::SDKDataSet data_set) override; | ||
| /** | ||
| * @brief Initialize the destination with an SDK data set. | ||
| * @param data_set The initial data. | ||
| */ | ||
| void Init(data_model::SDKDataSet data_set) override; | ||
|
|
||
| /** | ||
| * @brief Upsert data for the flag named by key. | ||
| * | ||
| * If the descriptor represents a deleted item, a tombstone will | ||
| * be forwarded to the ISerializedDestination. | ||
| * | ||
| * @param key Key of flag. | ||
| * @param flag Descriptor of flag. | ||
| */ | ||
| void Upsert(std::string const& key, | ||
| data_model::FlagDescriptor flag) override; | ||
|
|
||
| /** | ||
| * @brief Upsert data for the segment named by key. | ||
| * | ||
| * If the descriptor represents a deleted item, a tombstone will | ||
| * be forwarded to the ISerializedDestination. | ||
| * | ||
| * @param key Key of segment. | ||
| * @param segment Descriptor of segment. | ||
| */ | ||
| void Upsert(std::string const& key, | ||
| data_model::SegmentDescriptor segment) override; | ||
|
|
||
| /** | ||
| * @return Identity of this destination. Used in logs. | ||
| */ | ||
| [[nodiscard]] std::string const& Identity() const override; | ||
|
|
||
| /** | ||
| * @brief These are public so they can be referenced in tests. | ||
| */ | ||
| struct Kinds { | ||
| static FlagKind const Flag; | ||
| static SegmentKind const Segment; | ||
| }; | ||
|
|
||
| private: | ||
| void LogUpsertResult( | ||
| std::string const& key, | ||
| std::string const& data_type, | ||
| data_interfaces::ISerializedDestination::UpsertResult const& result) | ||
| const; | ||
|
|
||
| Logger const& logger_; | ||
| data_interfaces::ISerializedDestination& dest_; | ||
| std::string const ident_; | ||
| }; | ||
|
|
||
| } // namespace launchdarkly::server_side::data_components |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Used for pretty-er failures in google test.