与分析服务集成的架构/设计模式(flurry/google 分析)

Posted

技术标签:

【中文标题】与分析服务集成的架构/设计模式(flurry/google 分析)【英文标题】:Architeture/Design pattern for integration with analytics services (flurry/google analytics) 【发布时间】:2014-05-05 23:55:49 【问题描述】:

我正在开发一个 ios 应用程序,并计划在其中使用 分析 服务,例如 Flurry 或 Google Analytics。问题是:使用这些服务的良好软件设计(松散耦合、高度内聚、易于维护)是什么?

这是我的一个个人项目,我正在使用它来研究新技术和最佳实践。我偶然发现了这个“挑战”,并没有真正找到最佳解决方案。

我已经开发了一些使用这种服务的移动应用程序,并且通常我实现了适配器 + 工厂设计模式的组合:

创建代表通用分析服务的基本接口

public interface IAnalytics 
    void LogEvent(string name, object param);

每个服务(Flurry/Google Analytics/etc)API 都通过使用实现此接口的适配器进行封装​​

public class FlurryService : IAnalyticsService 
    public LogEvent(sring name, object param) 
        Flurry.Log(name, param.ToString());
    
 

实现了一个工厂,以便我们在该特定应用程序上拥有所需的任何分析服务

public static class AnalyticsServiceFactory 
    public IAnalytics CreateService(string servicename) 
        if(servicename == "google") 
            return new GoogleAnalyticsService();
         else 
            return new FlurryService();
        
    

最后,创建了一个“代理”对象(不是按规定)来记录特定于应用程序的事件

public class MyAnalytics 
    private static IAnalyticsService _Service = AnalyticsServiceFactory.CreateService("flurry");

    public static void LogUserLoggedIn(string user) 
        _Service.LogEvent("login", user);
    

    public static void LogLoginFailed(string user) 
        _Service.LogEvent("login", user);
    

这处理了每个服务 API 的封装并且效果很好,特别是在不同平台之间共享代码的应用中。

然而,还有一个问题是事件(或用户执行的操作)本身的日志记录。在我处理过的所有情况下,无论何时发生此类事件,事件的记录都是硬编码的。例如:

public void LogIn(string userName, string pass) 
    bool success = this.Controller.LogIn(userName, pass);

    if(success) 
        MyAnalytics.LogUserLoggedIn(username);

        // Change view

     else 
        MyAnalytics.LogLogInFailed(username);

        // Alert
    

这似乎比我想要的更耦合,所以我正在寻找更好的解决方案。

在使用 iOS 时,我考虑过使用 NSNotificationCenter:每当事件发生时,我不会立即记录它,而是在 NSNotificationCenter 中发布通知,另一个观察这些通知的对象负责调用 MyAnalytics 记录事件。然而,这种设计仅适用于 iOS(也就是说,不需要大量的工作)。

看待这个问题的另一种方式是:游戏如何跟踪你的行为以达到 Xbox 成就/Playstation 奖杯?

【问题讨论】:

你决定选择什么? @edelaney05 最后,我决定采用我在问题中提出的建议:使用通知中心发布有关事件发生的通知,然后让这些通知的侦听器处理日志记录和所有事情。每个通知都带有事件 ID 和带有相关信息(例如触发事件的用户的名称)的对象(通常是字符串)。在 iOS 之外,您将不得不实现通知中心,这很糟糕,但如果您注意可能存在的内存泄漏,它可能会正常工作。 我发现了这种方法 - aspect-oriented programming - 但感觉有点太聪明了。 【参考方案1】:

这是我通常用于可以是多提供者的日志记录类的设计。它允许在分析、崩溃报告和 beta OTA 提供商之间轻松交换,并且它使用预处理器指令,以便某些服务在某些环境中处于活动状态。此示例使用 CocoaLumberjack,但您可以使其与您选择的日志框架一起使用。

@class CLLocation;

@interface TGLogger : NSObject

+ (void)startLogging;
+ (void)stopLogging;

+ (NSString *)getUdidKey;

+ (void)setUserID:(NSString *)userID;
+ (void)setUsername:(NSString *)username;
+ (void)setUserEmail:(NSString *)email;
+ (void)setUserAge:(NSUInteger)age;
+ (void)setUserGender:(NSString *)gender;
+ (void)setUserLocation:(CLLocation *)location;
+ (void)setUserValue:(NSString *)value forKey:(NSString *)key;
+ (void)setIntValue:(int)value forKey:(NSString *)key;
+ (void)setFloatValue:(float)value forKey:(NSString *)key;
+ (void)setBoolValue:(BOOL)value forKey:(NSString *)key;

extern void TGReportMilestone(NSString *milestone, NSDictionary *parameters);
extern void TGReportBeginTimedMilestone(NSString *milestone, NSDictionary *parameters);
extern void TGReportEndTimedMilestone(NSString *milestone, NSDictionary *parameters);

@end

现在是实现文件:

#import "TGLogger.h"
#import "TGAppDelegate.h"
#import <CocoaLumberjack/DDASLLogger.h>
#import <CocoaLumberjack/DDTTYLogger.h>
#import <AFNetworkActivityLogger/AFNetworkActivityLogger.h>
@import CoreLocation;

#ifdef USE_CRASHLYTICS
#import <Crashlytics/Crashlytics.h>
#import <CrashlyticsLumberjack/CrashlyticsLogger.h>
#endif

#ifdef USE_FLURRY
#import <FlurrySDK/Flurry.h>
#endif

#import <Flurry.h>

@implementation TGLogger

+ (void)startLogging

    [DDLog addLogger:[DDASLLogger sharedInstance]];
    [DDLog addLogger:[DDTTYLogger sharedInstance]];
    [[DDTTYLogger sharedInstance] setColorsEnabled:YES];
    [[DDTTYLogger sharedInstance] setForegroundColor:[UIColor blueColor] backgroundColor:nil forFlag:LOG_FLAG_INFO];
    [[DDTTYLogger sharedInstance] setForegroundColor:[UIColor orangeColor] backgroundColor:nil forFlag:LOG_FLAG_WARN];
    [[DDTTYLogger sharedInstance] setForegroundColor:[UIColor redColor] backgroundColor:nil forFlag:LOG_FLAG_ERROR];

    [[AFNetworkActivityLogger sharedLogger] startLogging];

#ifdef DEBUG
    [[AFNetworkActivityLogger sharedLogger] setLevel:AFLoggerLevelInfo];
#else
    [[AFNetworkActivityLogger sharedLogger] setLevel:AFLoggerLevelWarn];
#endif


#if defined(USE_CRASHLYTICS) || defined(USE_FLURRY)
    NSString *udid = [TGLogger getUdidKey];
    TGLogInfo(@"Current UDID is: %@", udid);
#endif

#ifdef USE_CRASHLYTICS
    // Start Crashlytics
    [Crashlytics startWithAPIKey:TGCrashlyticsKey];
    [Crashlytics setUserIdentifier:udid];
    [DDLog addLogger:[CrashlyticsLogger sharedInstance]];
    TGLogInfo(@"Crashlytics started with API Key: %@", TGCrashlyticsKey);
#endif

#ifdef USE_FLURRY
    [Flurry setAppVersion:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]];
    [Flurry setSecureTransportEnabled:YES];
    [Flurry setShowErrorInLogEnabled:YES];
    [Flurry setLogLevel:FlurryLogLevelCriticalOnly];
    [Flurry startSession:TGFlurryApiKey];
    TGLogInfo(@"Flurry started with API Key %@ and for version %@", TGFlurryApiKey, [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]);
    TGLogInfo(@"Flurry Agent Version %@", [Flurry getFlurryAgentVersion]);
#endif

    TGLogInfo(@"Logging services started");

+ (void)stopLogging

    TGLogInfo(@"Shutting down logging services");
    [DDLog removeAllLoggers];


+ (NSString *)getUdidKey

    return [[UIDevice currentDevice] identifierForVendor].UUIDString;


+ (void)setUserID:(NSString *)userID

#ifdef USE_CRASHLYTICS
    [Crashlytics setUserIdentifier:userID];
#endif


+ (void)setUsername:(NSString *)username

#ifdef USE_CRASHLYTICS
    [Crashlytics setUserName:username];
#endif

#ifdef USE_FLURRY
    [Flurry setUserID:username];
#endif



+ (void)setUserEmail:(NSString *)email

#ifdef USE_CRASHLYTICS
    [Crashlytics setUserEmail:email];
#endif



+ (void)setUserAge:(NSUInteger)age

#ifdef USE_FLURRY
    [Flurry setAge:(int)age];
#endif



+ (void)setUserGender:(NSString *)gender

#ifdef USE_FLURRY
    [Flurry setGender:gender];
#endif


+ (void)setUserLocation:(CLLocation *)location

#ifdef USE_FLURRY
    [Flurry setLatitude:location.coordinate.latitude longitude:location.coordinate.longitude horizontalAccuracy:location.horizontalAccuracy verticalAccuracy:location.verticalAccuracy];
#endif
#ifdef USE_CRASHLYTICS
    [Crashlytics setObjectValue:location forKey:@"location"];
#endif


+ (void)setUserValue:(NSString *)value forKey:(NSString *)key

#ifdef USE_CRASHLYTICS
    [Crashlytics setObjectValue:value forKey:key];
#endif


#pragma mark - Report key/values with crash logs

+ (void)setIntValue:(int)value forKey:(NSString *)key

#ifdef USE_CRASHLYTICS
    [Crashlytics setIntValue:value forKey:key];
#endif


+ (void)setBoolValue:(BOOL)value forKey:(NSString *)key

#ifdef USE_CRASHLYTICS
    [Crashlytics setBoolValue:value forKey:key];
#endif


+ (void)setFloatValue:(float)value forKey:(NSString *)key

#ifdef USE_CRASHLYTICS
    [Crashlytics setFloatValue:value forKey:key];
#endif


void TGReportMilestone(NSString *milestone, NSDictionary *parameters)

    NSCParameterAssert(milestone);
    TGLogCInfo(@"Reporting %@", milestone);

#ifdef USE_FLURRY
    [Flurry logEvent:milestone withParameters:parameters];
#endif


void TGReportBeginTimedMilestone(NSString *milestone, NSDictionary *parameters)

    NSCParameterAssert(milestone);
    TGLogCInfo(@"Starting timed event %@", milestone);

#ifdef USE_FLURRY
    [Flurry logEvent:milestone withParameters:parameters timed:YES];
#endif


void TGReportEndTimedMilestone(NSString *milestone, NSDictionary *parameters)

    NSCParameterAssert(milestone);
    TGLogCInfo(@"Ending timed event %@", milestone);

#ifdef USE_FLURRY
    [Flurry endTimedEvent:milestone withParameters:parameters];
#endif



@end

【讨论】:

这有帮助吗?您会注意到,因为它与您的日志记录相结合,您根本不参与分析,如果您不想使用分析,它实际上只是充当日志语句。因此,无需使用 NSNotificationCenter 进行外来连接,您只需拥有一个记录事件并可选择将其报告给您选择的服务的功能。我在许多生产应用程序中都有这种设计,并根据需要在几分钟内切换了提供程序,甚至将其与 NewRelic 或多个分析提供程序等相关联。 感谢您的回复。当必须记录事件时,您只需在代码中调用 TGReportMilestone() 函数,还是在事件和记录之间存在抽象层?这是我在问题的最后一个代码示例中引用的问题。编辑:对不起,我发表了不完整的评论,似乎没有你的回复。我会读的。对不起。 抽象是 TGReportMilestone 本身只是将事件记录到您的日志系统(在这种情况下,TGLogLogCInfo 只是我使用的一个宏,它在调试模式下映射到 NSLog)。 USE_FLURRY ifdef 是一个编译器指令,表示如果在预处理器指令中设置了 USE_FLURRY=1,它也会将事件报告给 Flurry。但如果不是,那么它根本不使用该代码,实际上它甚至不导入标头。这使您的分析集成完全隔离且易于更改,同时默认充当简单的记录器。 我明白你的意思,不过,老实说,我会找到一种方法来封装 Flurry API 和其他日志提供程序。建议的方式太容易修改而不是扩展,但我理解这可能只是一个例子。无论如何,我认为对于我的这个项目,我会使用各种想法,感谢您的贡献!

以上是关于与分析服务集成的架构/设计模式(flurry/google 分析)的主要内容,如果未能解决你的问题,请参考以下文章

系统集成项目管理之信息系统集成

解析微服务架构:融入微服务的企业集成架构

微服务架构案例(04):中间件集成,公共服务封装

Kafka架构设计简介

《基于微服务架构的在线学习系统设计与实现》第三章 文献随笔

IIS 7 托管管道模式 经典模式(Classic) 集成模式(Integrated) 分析与理解