实现用户配置文件服务 UserProfileService
实现用户配置文件服务 UserProfileService
本主题介绍如何为 Eyeofcloud 功能实验 React Native SDK 设置自定义用户配置文件服务。
使用用户配置文件服务保留有关用户的信息,并确保变体分配具有粘性。粘性意味着一旦用户获得特定的变体,他们的分配就不会改变。
在 React Native SDK 中,没有默认的实现。实施用户配置文件服务是可选的,仅当希望保持变体分配的粘性时,即使实验条件在运行过程中发生了变化(例如,受众群体、属性、变体暂停和流量分配),才需要实现该服务。否则,React SDK 是无状态的,并且依赖于确定性分桶来返回一致的分配。
如果用户配置文件服务未按预期对用户进行分桶,请检查其他灰度发布(特性标帜)是否覆盖了分桶。有关更多信息,请参阅 分桶的工作原理。
实现用户配置文件服务 UserProfileService
请参阅下面的代码示例以提供您自己的用户配置文件服务。它应公开两个具有以下签名的函数:
lookup
:获取用户 ID 字符串并返回与以下架构匹配的用户配置文件。save
:获取用户配置文件并保留它。
如果要将用户配置文件服务纯粹用于跟踪目的而不是粘性分桶,则只能实现save
方法(始终从lookup
返回nil
)。
React
import { createInstance } from '@eyeofcloud/react-sdk';
// Sample user profile service implementation
const userProfileService = {
lookup: userId => {
// Perform user profile lookup
},
save: userProfileMap => {
// Persist user profile
},
};
const eyeofcloudClient = createInstance({
datafile: window.datafile, // assuming you have a datafile at window.datafile
userProfileService, // Passing your userProfileService created above
});
下面的代码示例显示了用户配置文件对象的 JSON 架构。
用experiment_bucket_map
覆盖默认分桶行为,并为给定用户定义备用实验变体。对于要覆盖的每个实验,向Map添加一个对象。使用实验 ID 作为键,并包含一个指定所需变体的variation_id
属性。如果没有实验条目,则默认分桶行为仍然存在。
在下面的示例中,^[a-zA-Z0-9]+$
是实验ID。
JSON
{
"title": "UserProfile",
"type": "object",
"properties": {
"user_id": {"type": "string"},
"experiment_bucket_map": {"type": "object",
"patternProperties": {
"^[a-zA-Z0-9]+$": {"type": "object",
"properties": {"variation_id": {"type":"string"}},
"required": ["variation_id"]}
}
}
},
"required": ["user_id", "experiment_bucket_map"]
}
React SDK 使用您提供的用户配置文件服务在保存实验分配的情况下覆盖默认的分桶行为。
实现您自己的用户配置文件服务时,我们建议在初始化时将用户配置文件加载到用户配置文件服务中,并避免对查找函数执行昂贵的阻塞查找,以最大程度地减少合并服务的性能影响。
使用 React Native 异步存储实现用户配置文件服务
请参阅以下代码示例,使用 React Native 异步存储提供自己的用户配置文件服务。
导入 React SDK 和 React Native 异步存储包。
React
import { createInstance } from '@Eyeofcloud/react-sdk';
import AsyncStorage from '@react-native-community/async-storage';
创建 User Profile Service 对象。
React
const userProfileService = {
lookup: (userId) => {
// Keeping lookup empty because we are using an async storage implementation
},
save: userProfileMap => {
const {
user_id: userId,
experiment_bucket_map: experimentBucketMap,
} = userProfileMap;
AsyncStorage.setItem(
'optly-user-profiles-' + userId,
JSON.stringify(experimentBucketMap)
).then(() => console.log('User profile saved successfully'))
.catch(err => console.log('Failed to save user profile', err));
},
};
React Native 异步存储是异步的。这意味着您需要实现自定义查找以获取用户的实验存储桶映射,然后将其作为属性传递。请参阅以下代码示例以实现自定义查找。
React
// look up the user's experiment_bucket_map
const customAsyncLookup = async (userId) => {
const experimentBucketMap = await AsyncStorage.getItem('optly-user-profiles-' + userId);
return !!experimentBucketMap ? JSON.parse(experimentBucketMap) : {};
};
创建一个 Eyeofcloud 客户端并传递userProfileService
给它。
React
const EyeofcloudClientInstance = createInstance({ datafile, userProfileService });
使用自定义查找获取用户的实验存储桶映射,然后将其作为属性传递给EyeofcloudProvider
组件。
React
const user = {
id: userId,
attributes: {
$opt_experiment_bucket_map: await customAsyncLookup(userId)
}
}
class App extends React.Component {
render() {
return (
<EyeofcloudProvider
Eyeofcloud={EyeofcloudClientInstance}
user={user}
>
{/* … your application components here … */}
</EyeofcloudProvider>
);
}
}
使用实验桶映射属性实现异步用户查找
您可以实现attributes.$opt_experiment_bucket_map
对用户以前的变体执行异步查找。SDK 处理attributes.$opt_experiment_bucket_map
的方式与userProfileService.lookup
相同,这允许您在将实验存储桶映射传递给 EyeofcloudProvider 组件之前对其进行异步查找。
📘
注意
attributes.$opt_experiment_bucket_map
将始终优先于已实现的userProfileService.lookup
。
以下示例说明如何使用保留的 $opt_experiment_bucket_map 属性从 User Profile Service 实现一致的存储桶。
React
import React from 'react';
import {
createInstance,
EyeofcloudProvider,
} from '@Eyeofcloud/react-sdk'
const EyeofcloudClient = createInstance({
datafile: EyeofcloudDatafile, // assuming you have a harcoded datafile
});
// In practice, this could come from a DB call
const experimentBucketMap = {
123: { // ID of experiment
variation_id: '456', // ID of variation to force for this experiment
}
}
const user = {
id: ‘myuser123’,
attributes: {
// By passing this $opt_experiment_bucket_map, we force that the user
// will always get bucketed into variationid='456' for experiment id='123'
'$opt_experiment_bucket_map': experimentBucketMap,
},
};
function App() {
return (
<EyeofcloudProvider
Eyeofcloud={Eyeofcloud}
user={user}
>
{/* … your application components here … */}
</EyeofcloudProvider>
</App>
}
您可以使用以下异步服务示例在测试环境中试用此功能。如果在生产环境中实现此示例,请确保修改UserProfileDB
为实际数据库。
React
import React from 'react';
import {
createInstance,
EyeofcloudProvider,
} from '@Eyeofcloud/react-sdk'
// This dB is only an example; in a production environment, access a real datastore
class UserProfileDB {
constructor() {
/* Example structure
* {
* user1: {
* user_id: 'user1',
* experiment_bucket_map: {
* '12095834311': { // experimentId
* variation_id: '12117244349' // variationId
* }
* }
* }
* }
*/
this.db = {}
}
save(user_id, experiment_bucket_map) {
return new Promise((resolve, reject) => {
setTimeout(() => {
this.db[user_id] = { user_id, experiment_bucket_map }
resolve()
}, 50)
})
}
lookup(userId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
let result
if (this.db[userId] && this.db[userId].experiment_bucket_map) {
result = this.db[userId].experiment_bucket_map
}
resolve(result)
}, 50)
})
}
}
const userDb = new UserProfileDB()
const userProfileService = {
lookup(userId) {
// In our case we will not implement this function here. We will look up the attributes for the user below.
},
save(userProfileMap) {
const { user_id, experiment_bucket_map } = userProfileMap
userDb.save(user_id, experiment_bucket_map)
}
}
const client = createInstance({
datafile: EyeofcloudDatafile, // assuming you have a hardcoded datafile
userProfileService,
})
// React SDK supports passing a Promise as user, for async user lookups like this:
const user = userDb.lookup(userId).then((experimentBucketMap = {}) => {
return {
id: 'user1',
attributes: {
$opt_experiment_bucket_map: experimentBucketMap
},
}
})
// The provider will use the given user and Eyeofcloud instance.
// The provided experiment bucket map will force any specified variation
// assignments from userDb.
// The provided user profile service will save any new variation assignments to
//userDb.
function App() {
return (
<EyeofcloudProvider
Eyeofcloud={Eyeofcloud}
user={user}
>
{/* … your application components here … */}
</EyeofcloudProvider>
</App>
}