实现用户配置文件服务 UserProfileService

云眼About 5 min

实现用户配置文件服务 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> 
}
Last update:
Contributors: zhangweixue,“zhangweixue”