C++ SDK
Supported Platforms
The C++ SDK is compatible with the following platforms:
- Linux
- MacOS
- Windows
- Android
- iOS
Code Building Requirements
- C++ version should be C++17 or higher
- CMake version should be 3.24 or higher (this can be adjusted)
- Protobuf version should be 3.21.12 (this can be adjusted to match the project by modifying CMakeLists.txt, but it must be pb3)
Getting Started
This guide will walk you through the process of getting started with ABetterChoice for C++. To utilize the SDK, an ABetterChoice project is required. If you haven't already, please follow the doc to create a new project and some experiments. While you can choose to bypass this step for now, please note that you may require the project ID and the names of the experiment layers in subsequent sections.
Installation
There are two methods to use the SDK: you can either directly import the source code or link it after precompilation. Choose the approach that best suits your needs.
1.1 Source Code Import
add_subdirectory
There's no need for precompilation. You can directly copy the source code into your project directory. This is only applicable for the target language CPP. For operating systems like Android, iOS, etc., precompilation into libraries is necessary.
cd ${YOUR_PROJECT_PATH}
mkdir third_party
cd third_party
git clone https://git.woa.com/tencent_abtest/open-source/abetterchoice-cpp-sdk.git
shell
If you're using CMake, include the following in your CMakeLists.txt
file:
# CMakeLists.txt
add_subdirectory(${YOUR_PROJECT_PATH}/third_party/abetterchoice-cpp-sdk)
target_include_directories(your_library_name PRIVATE ${YOUR_PROJECT_PATH}/third_party/abetterchoice-cpp-sdk/include/)
target_link_libraries(your_library_name PRIVATE abetterchoice_cpp_sdk)
shell
You can check out the latest versions at https://git.woa.com/tencent_abtest/open-source/abetterchoice-cpp-sdk.git .
1.2 Precompile
find_library
For OS like Android, iOS, etc., precompiling into libraries is required.
First, precompile and generate the corresponding platform-specific dependency packages. Then, link the dependency libraries using "find_library".
1.2.1 How to Precompile
Linux、Macos、Windows
git clone https://git.woa.com/tencent_abtest/open-source/abetterchoice-cpp-sdk.git
cd abetterchoice_cpp_sdk
mkdir build
cd build
cmake ..
make
IOS
Requirements:
- Xcode.
Xcode is required to be installed and the environment needs to be configured.
On iOS, only static libraries can be used.
Make sure to configure CMakeLists.txt to generate a static library.
git clone https://git.woa.com/tencent_abtest/open-source/abetterchoice-cpp-sdk.git
cd abetterchoice_cpp_sdk
mkdir build
cd build
# You can modify the iOS environment.
cmake .. -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_SYSTEM_VERSION=9.0 -DCMAKE_OSX_ARCHITECTURES=arm64
make
Android
Requirements:
- NDK
This depends on NDK, so you need to install the corresponding version of NDK first.
git clone https://git.woa.com/tencent_abtest/open-source/abetterchoice-cpp-sdk.git
cd abetterchoice_cpp_sdk
mkdir build
cd build
# You can modify the Android environment and specify the NDK address.
cmake .. -DCMAKE_TOOLCHAIN_FILE=/Users/${USERNAME}/Library/Android/sdk/ndk/23.0.7599858/build/cmake/android.toolchain.cmake -DANDROID_NATIVE_API_LEVEL=21 -DANDROID_ABI=arm64-v8a -DCMAKE_PREFIX_PATH=/Users/${USERNAME}/vcpkg/installed/arm64-android -DCMAKE_SYSTEM_NAME=Android -DCMAKE_ANDROID_ARCH_ABI=arm64-v8a -DCMAKE_ANDROID_NDK=${ANDROID_NDK_HOME} -DCMAKE_ANDROID_STL_TYPE=c++_shared
make
1.2.2 How to Link a Static Library
Linux、Macos、Windows
If you are using CMake, include the following in your CMakeLists.txt
file
# CMakeLists.txt
find_library(abetterchoice_cpp_sdk
NAMES abetterchoice_cpp_sdk
HINTS ${YOUR_PROJECT_PATH}/lib)
find_library(protobuf
NAMES protobuf-lite
HINTS ${YOUR_PROJECT_PATH}/lib)
find_library(roaring
NAMES roaring
HINTS ${YOUR_PROJECT_PATH}/lib)
find_library(curl
NAMES curl
HINTS ${YOUR_PROJECT_PATH}/lib)
find_library(ssl
NAMES ssl
HINTS ${YOUR_PROJECT_PATH}/lib)
find_library(crypto
NAMES crypto
HINTS ${YOUR_PROJECT_PATH}/lib)
find_library(z
NAMES z
HINTS ${YOUR_PROJECT_PATH}/lib)
target_include_directories(your_library_name PRIVATE ${YOUR_PROJECT_PATH}/third_party/abetterchoice-cpp-sdk/include/)
target_link_libraries(your_library_name PRIVATE
${abetterchoice_cpp_sdk} ${protobuf} ${roaring}
${curl} ${ssl} ${crypto} ${z})
IOS
Here's a simple example, but the specific implementation will depend on the design of your business logic:
Add library files:
- Add the libabetterchoice_cpp_sdk.a, libroaring.a, libprotobuf-lite.a, libcurl.a, libssl.a, libcrypto.a, and libz.a files to your Xcode project.
- Drag the library files into the "Navigator" pane in Xcode, and make sure they are added to the target application.
Add header files:
- Add the header files in the abetterchoice_cpp_sdk/include folder to your Xcode project. You can drag the header files directly into the "Navigator" pane in Xcode, or add the directory where the header files are located to the "Header Search Paths" setting of your project. To set the "Header Search Paths", go to the "Build Settings" tab of your project, find the "Header Search Paths" setting, and add the directory where the header files are located.
Link library files:
- In your Xcode project, go to the "Build Phases" tab, and expand the "Link Binary With Libraries" section.
- Click the + button to add a new library. In the pop-up window, select the library files you just added and click "Add".
Create an Objective-C wrapper :
- Since abetterchoice_cpp_sdk is a C++ library, you need to create an Objective-C++ wrapper to use it in your iOS project. Refer to the previous answer for how to create a wrapper. Here's a simple example of how to create the AbetterchoiceCppSdkWrapper:
Create a header file
//
// AbetterchoiceCppSdkWrapper.h
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface AbetterchoiceCppSdkWrapper : NSObject
(NSString *)testSDKWithProjectID:(NSString *)projectID Layer:(NSString *)layer Parameter:(NSString *)parameter Guid:(NSString *)guid;
@end NS_ASSUME_NONNULL_END
Create an implementation file
You can name the file AbetterchoiceCppSdkWrapper.mm. Note that we use the .mm extension instead of .m to use Objective-C++.。
//
// AbetterchoiceCppSdkWrapper.mm
//
#import "AbetterchoiceCppSdkWrapper.h"
#include <string>
#include <random>
// Replace path/to/abc_cpp_sdk.h with the actual path of your C++ library header file
#include "path/to/abc_cpp_sdk.h"
@implementation AbetterchoiceCppSdkWrapper
- (NSString *)testSDKWithProjectID:(NSString *)projectID Layer:(NSString *)layer Parameter:(NSString *)parameter Guid:(NSString *)guid {
// Call functions in the C++ library
std::vector<std::string> project_id;
std::string project_id = [projectID UTF8String];
project_ids.push_back(project_id);
std::string token = "xxx";
int ret = abc_cpp_sdk::AbcService::GetInstance()->Init(project_ids,
abc_cpp_sdk::WithSetSecretKey(token),
abc_cpp_sdk::WithSetInitOvertimeTime(30),
abc_cpp_sdk::WithSetEnv(abc_cpp_sdk::PROD_ENV));
// Call functions in the C++ library
std::string result = std::to_string(ret);
// Please refer to the later section for more recommended APIs
auto experiment = abc_cpp_sdk::AbcService::GetInstance()->GetExperiment(project_id, [layer UTF8String],
abc_cpp_sdk::WithSetUnitID([guid UTF8String]));
// Convert the result to an NSString and return it
return [NSString stringWithUTF8String:experiment.GetString([parameter UTF8String]).c_str()];
}
@end
After completing the above steps, your AbetterchoiceCppSdkWrapper class should be able to call functions in the abetterchoice_cpp_sdk library and use them in Objective-C.
Now you can use this wrapper class in your iOS project to access the functionality of the C++ library.
1.2.3 How to Link a Dynamic Library
Linux、Macos、Windows
If you are using CMake, include the following in your CMakeLists.txt
file
add_library(abetterchoice_cpp_sdk SHARED IMPORTED)
set_target_properties(abetterchoice_cpp_sdk PROPERTIES
IMPORTED_LOCATION "${YOUR_PROJECT_PATH}/libabetterchoice_cpp_sdk.so")
target_include_directories(your_library_name PRIVATE ${YOUR_PROJECT_PATH}/third_party/abetterchoice-cpp-sdk/include/)
target_link_libraries(your_library_name PRIVATE abetterchoice_cpp_sdk)
Android
Here's a simple example in Java, but the specific implementation will depend on the design of your business:
Add library files: Add the libabetterchoice_cpp_sdk.so (dynamic library) file to your Android project. Put the library file in the src/main/jniLibs directory of your project and create ABI subdirectories as needed (e.g., armeabi-v7a, arm64-v8a, x86, etc.). For example, put the library file for the arm64-v8a version in the src/main/jniLibs/arm64-v8a directory.
Add header files: Add the header files of the abetterchoice_cpp_sdk library to your Android project. You can put the header files in the src/main/cpp/include directory of your project (create a new directory if it doesn't exist yet). Copy the header files to the include directory.
Modify CMakeLists.txt: Create or modify the existing CMakeLists.txt file in the src/main/cpp directory of your project. In this file, do the following:
shelladd_library(abetterchoice_cpp_sdk SHARED IMPORTED) set_target_properties(abetterchoice_cpp_sdk PROPERTIES IMPORTED_LOCATION "${CMAKE_SOURCE_DIR}/../jniLibs/${ANDROID_ABI}/libabetterchoice_cpp_sdk.so" ) include_directories("${CMAKE_SOURCE_DIR}/include") target_link_libraries(your_executable_target abetterchoice_cpp_sdk)
Modify build.gradle: In the build.gradle file (Module level) of your project, add a reference to the CMakeLists.txt file in the externalNativeBuild section of the android section:
shellexternalNativeBuild { cmake { path "src/main/cpp/CMakeLists.txt" } }
Create a Java wrapper: For an Android project, you need to create a Java or Kotlin wrapper class to call functions in the abetterchoice_cpp_sdk library in Java or Kotlin code. Here's a simple example:
- Create a Java wrapper class: In the src/main/java directory of your Android project, create a Java class named AbetterchoiceCppSdkWrapper. In this class, declare a native method that corresponds to the function you declared in the JNI interface.
public class AbetterchoiceCppSdkWrapper {
static {
System.loadLibrary("abetterchoice_cpp_sdk");
}
public native String init(); // Declare a native method
}
- Create a JNI interface: In the src/main/cpp directory, create a C++ file, such as abetterchoice_cpp_sdk_jni.cpp. In this file, include your library header file and declare a JNI interface function that will call your C++ library function.
#include <jni.h>
#include <string>
#include <random>
#include "include/abetterchoice_cpp_sdk/abc_cpp_sdk.h"
std::string jstringToString(JNIEnv *env, jstring jstr) {
if (env == nullptr) return "";
const char *cstr = env->GetStringUTFChars(jstr, nullptr);
std::string str(cstr);
env->ReleaseStringUTFChars(jstr, cstr);
return str;
}
extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_test_1cpp_1sdk_AbetterchoiceCppSdkWrapper_testAbcSDK(JNIEnv *env, jobject thiz, jstring project_id, jstring layer_key, jstring parameter, jstring guid) {
std::vector<std::string> project_ids;
std::string project_id = jstringToString(env, project_id);
project_ids.push_back(project_id);
std::string token = "xxxx";
int ret = abc_cpp_sdk::AbcService::GetInstance()->Init(project_ids,
abc_cpp_sdk::WithSetSecretKey(token),
abc_cpp_sdk::WithSetInitOvertimeTime(30),
abc_cpp_sdk::WithSetEnv(abc_cpp_sdk::PROD_ENV));
// Call functions in the C++ library
std::string result = std::to_string(ret);
std::string unit_id = jstringToString(env, guid);
auto experiment = abc_cpp_sdk::AbcService::GetInstance()->GetExperiment(project_id,
jstringToString(env, layer_key),
abc_cpp_sdk::WithSetUnitID(unit_id));
// Convert the result to a Java string and return it
return env->NewStringUTF(experiment.GetString(parameter).c_str());
}
After completing the above steps, your Android project will contain a Java wrapper class that can call functions in the abetterchoice_cpp_sdk library.
Now you can use the AbetterchoiceCppSdkWrapper class in your Android project to access the functionality of the C++ library. For example, you can create an instance of the AbetterchoiceCppSdkWrapper class in an Activity or other class and call the testAbcSDK method.
Working with the SDK
After installation, you will need to initialize the SDK . The Init function requires two parameters:
- The first parameter is the ID list of projects under which you need to fetch experiment/feature flag, usually this list only contain one element.
- The second parameter is the secret key. You can locate this under the API key section within the settings tab. Please contact us if you need to fetch more than one projects.
int ret = abc_cpp_sdk::AbcService::GetInstance()->Init(project_ids, abc_cpp_sdk::WithSetSecretKey({{.sdk_token}}));
if (ret != 0) {
// init fail
}
In the following section, we will guide you to create a new experiment and fetch the experiment assignments via our experiment fetching APIs provided by our SDK.
Terms | Meaning |
---|---|
UnitID | A unit ID can be a user ID, a session ID or a machine ID. The ABC SDK assigns a unit ID to the same group consistantly. |
Layer | A layer usually represent 100% of the units that you can run experiment in your product, it can be further splited into one or more experiments, the traffic of the experiments under same layer are mutually exclusive. |
Experiment | An experiment can take up to 100% of the layer traffic, it will further contain two or more experiment groups. |
Group | Units within one experiment will be split into two or more groups, e.g. control and treatment, they will be compared against each other. |
Parameter | These are the values binded to a particular layer. Experiments under the same layer can share the same parameters with each other. |
1. Creating a New Experiment
If you haven't done so already, please navigate to the console and create a new experiment by following our documentation on how to create an experiment.
2. Retrieving Experiment Assignments
Assume that in step 1, under the project project_id
, we have already created an experiment with the layer name abc_layer_name
. Within this layer, there is a parameter named should_show_banner
of Boolean type. Please note that in our basic version, the layer name defaults to the experiment name. However, if you create new experiments under the same layer, you will need to find the layer name on the experiment page. Currently, we offer three methods to fetch the traffic assignment for a specific unit id.
a. Retrieve Experiment by Layer Key
The first method involves obtaining the experiment assignment by the layer key and fetching the parameters within the layer using the strong type APIs. This is useful when you have multiple parameters within the layer/experiment. This API will automatically provide the correct result when there are multiple experiments under the same layer, as the unit will and can only fall into one of them. An additional advantage of this method is that it allows you to iterate through the experiments quickly by deallocating the old experiment and creating new ones under the same layer without modifying the code.
auto experiment = abc_cpp_sdk::AbcService::GetInstance()->GetExperiment({{.project_id}}, {{.abc_layer_name}},
abc_cpp_sdk::WithSetUnitID({{.unit_id}}));
string shouldShowBanner = experiment.GetString("should_show_banner");
b. Retrieve All Experiment Assignments Under the Project
In certain scenarios, users might require to retrieve all experiment assignment results within a single project. To cater to this need, we offer a batch API. This API returns a map linking the experiment layer name to the experiment assignment of the unit_id specific to that layer. However, it's important to note that using this API could potentially cause dilution problems, thus it is recommended to proceed with caution. Depending on your specific needs, you may want to consider disabling exposure logging through the 'opts' parameter, as demonstrated below. The exposure logging API can be used to manually log the exposure at a later time. Additionally, it's worth noting that you have the option to apply an extra filter to fetch results for specific layers only.
auto experiments = abc_cpp_sdk::AbcService::GetInstance()->GetExperiments({{.project_id}},
abc_cpp_sdk::WithSetUnitID({{.unit_id}}),
abc_cpp_sdk::WithSetDisableReport(true),
abc_cpp_sdk::WithSetLayerKeys({{{.abc_layer_name_1}}, {{.abc_layer_name_n}}}));
c. Retrieve Experiment by Parameter Key
This method is similar to the first one, with the only difference being that instead of fetching by layer name, we retrieve the result by parameter name, which is unique within the same project. We are planning to introduce features that may allow users to move the parameter from one layer to another (e.g., launcher layer), and this API will be useful in handling such cases.
auto value = abc_cpp_sdk::AbcService::GetInstance()->GetValueByVariantKey({{.project_id}}, "should_show_banner",
abc_cpp_sdk::WithSetUnitID({{.unit_id}}));
string shouldShowBanner = value.GetString();
3. Logging Exposure Data
When retrieving the experiment assignments as described above, you have the option to disable exposure logging to avoid exposure dilution. You can manually log the exposure later at an appropriate time:
auto experiment = abc_cpp_sdk::AbcService::GetInstance()->GetExperiment({{.project_id}}, {{.abc_layer_name}},
abc_cpp_sdk::WithSetUnitID({{.unit_id}}));
int ret = experiment.LogExposure();
4. Checking feature flags
In this section, we will guide you through the process of retrieving the value of a feature flag. If you haven't already done so, please first follow our documentation to create one. Assuming we have already created a new feature flag named new_feature_flag
under the project project_id
, and its value type is Boolean, we can fetch the value in the following manner:
auto feature_flag = abc_cpp_sdk::AbcService::GetInstance()->GetFeatureFlag({{.project_id}}, {{.new_feature_flag}},
abc_cpp_sdk::WithSetUnitID({{.unit_id}}));
string value = feature_flag.value.GetString();
5. Example
Here is a simple example:
#include "include/abc_cpp_sdk.h"
int main() {
std::vector<std::string> project_ids;
// Project ID requested on ABC
std::string project_id = "${PROJECT_ID}";
project_ids.push_back(project_id);
// TODO Replace SDK_TOKEN with the SDK token for the business
int ret = abc_cpp_sdk::AbcService::GetInstance()->Init(project_ids, abc_cpp_sdk::WithSetSecretKey("${SDK_TOKEN}"));
std::cout << ret << std::endl;
if (ret != 0) {
std::cout << "init err" << std::endl;
return 0;
}
// TODO Replace LAYER_KEY with the key of the layer to be queried
std::string layer_key = "${LAYER_KEY}";
// TODO Replace PARAM_KEY with the key of the param to be queried
std::string param_key = "${PARAM_KEY}";
// TODO Replace UNIT_ID with the unit_id
std::string unit_id = "${UNIT_ID}"
auto experiment = abc_cpp_sdk::AbcService::GetInstance()->GetExperiment(project_id, layer_key,
abc_cpp_sdk::WithSetUnitID(unit_id));
string shouldShowBanner = experiment.GetString(param_key);
}
NOTE
You need to install the curl library (supporting HTTPS) by yourself.
During the compilation process, if the curl library cannot be found, you need to specify the path of the curl library in CMakeLists.txt.
set(CURL_LIBRARY "path/to/your/curl/lib/libcurl.a")
set(CURL_INCLUDE_DIR "/path/to/your/curl/include")
# Replace path/to/your with the actual path of your library file, when compiling packages for different platforms, specify the corresponding library file path for each platform.
If there is a protobuf conflict error, you can adjust CMakeLists.txt to specify the dependent protobuf package.
# is use protobuf-lib
# You can turn on the switch and can specify the address of the dependent Protobuf, using the same version of protobuf as the project.
set(ABC_SDK_USE_PROTOBUF_LIB ON)
You can choose to generate a static library or a dynamic library by yourself.
# dynamic library
set(LIBRARY_TYPE SHARED)
# static library
set(LIBRARY_TYPE STATIC)
If the ABC_SDK_USE_PUBSUB_SDK switch is turned on, you need to specify the path of the pubsub library by yourself.