Erlang中“可变”记录的最佳策略

时间:2011-11-03 06:32:57

标签: data-structures erlang

我开发了一个我认为会有很多用户的系统。每个用户都有一个在应用程序内表示的配置文件作为记录。为了存储用户的个人资料,我执行以下base64:encode_to_string(term_to_binary(Profile)),所以基本上是以序列化的方式存储的个人资料。

到目前为止一切都很好。现在问题是:

我不时会计划通过添加和删除其中的某些字段来扩展配置文件功能。我的问题是在代码中处理这些变化的最佳策略是什么?

我现在看到的方法是做这样的事情:

Profile = get_profile(UserName),
case is_record(Profile, #profile1) of
    true ->
        % do stuff with Profile#profile1
        ok;
    _ ->
        next
end,
case is_record(Profile, #profile2) of
    true ->
        % do stuff with Profile#profile2
        ok;
    _ ->
        next
end,

我想知道我的任务是否有更好的解决方案?

其他信息:我使用的是简单的KV存储。它无法存储Erlang类型,这就是我使用State#state.player#player.chips#chips.br

的原因

4 个答案:

答案 0 :(得分:1)

它在很大程度上取决于记录数量的比例,变化的频率和可接受的中断。由于可维护性,我希望首先将配置文件升级到最新版本。你也可以制作像mnesia一样即时升级的系统。最后有可能保留所有版本的代码,我绝对不会喜欢。这是维护的噩梦。

无论如何,当警卫允许is_record/2时,我更愿意

case Profile of
    X when is_record(X, profile1) ->
        % do stuff with Profile#profile1
        ok;
    X when is_record(X, profile2) -> 
        % do stuff with Profile#profile2
        ok
end

注意没有catch all子句,因为你对未知记录类型会做什么?这是错误所以快速失败!

您还有许多其他选择,例如像黑客一样:

case element(1,Profile) of
    profile1 ->
        % do stuff with Profile#profile1
        ok;
    profile2 -> 
        % do stuff with Profile#profile2
        ok
end

或类似

{_, F} = lists:keyfind({element(1,Profile), size(Profile)},
    [{{profile1, record_info(size, profile1)}, fun foo:bar/1},
     {{profile2, record_info(size, profile2)}, fun foo:baz/1}]),
F(Profile).

以及许多其他可能性。

答案 1 :(得分:1)

最好的方法是获得序列化(配置文件)的副本以及相同但以记录形式的副本。然后,每次对记录表单配置文件进行更改时,也会对同一用户的序列化配置文件进行更改(在同一事务中!)。 修改用户记录配置文件的代码应始终重新计算新的序列化表单,对于您来说,该表单是用户记录的外部表示

-record(record_prof,{name,age,sex}).
-record(myuser,{
            username,
            record_profile = #record_prof{},
            serialized_profile
        }).
change_profile(Username,age,NewValue)-> %% transaction starts here.... [MyUser] = mnesia:read({myuser,Username}), Rec = MyUser#myuser.record_profile, NewRec = Rec#record_prof{age = NewValue}, NewSerialised = serialise_profile(NewRec), NewUser = MyUser#myuser{ record_profile = NewRec, serialized_profile = NewSerialised }, write_back(NewUser), %% transaction ends here..... ok.
所以无论序列化函数在做什么,就是这样。但是这总是会留下额外的自由配置文件更改。因此,我们始终将序列化配置文件保持为记录配置文件的正确表示。当记录配置文件发生更改时,还必须重新计算序列化表单(事务性)以便具有完整性。

答案 2 :(得分:1)

也许,你可以使用支持者。

假设您已存储了一些用户个人资料。

User = [{name,"John"},{surname,"Dow"}].
store_profile(User).

然后,几年后,您决定使用用户年龄扩展用户个人资料。

User = [{name,"John"},{surname,"Dow"},{age,23}]. 
store_profile(User).

现在您需要从DB

获取用户个人资料
get_val(Key,Profile) ->
   V = lists:keyfind(Key,1,Profile),
   case V of
      {_,Val} -> Val;
      _ -> undefined
   end.

User = get_profile().
UserName = get_val(name,User).
UserAge = get_val(age,User).

如果您获得“版本2”的用户个人资料,您将获得实际年龄(在此特定情况下为23)。

如果您获得“版本1”(“旧版”)的用户个人资料,您将获得“未定义”作为年龄,然后您可以更新配置文件并将其与新值一起存储,这样它就会成为'新版'实体。

所以,没有版本冲突。

可能这不是最好的方法,但在某些情况下它可能是一种解决方案。

答案 3 :(得分:0)

您可以使用某些可扩展数据序列化格式,例如JSON或Google Protocol Buffers。

这两种格式都支持在不破坏向后兼容性的情况下添加新字段。通过使用它们,您无需为序列化数据结构引入显式版本控制。

在两种格式之间进行选择取决于您的使用案例。例如,使用Protocol Buffers更可靠,而JSON更容易上手。