Лучшая стратегия для "изменчивых" записей в Erlang - PullRequest
6 голосов
/ 03 ноября 2011

Я разрабатываю систему, где, как я полагаю, будет много пользователей. У каждого пользователя есть профиль, представленный внутри приложения в виде записи. Для сохранения профиля пользователя я делаю следующее 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 ]

1 голос
/ 05 июня 2013

Возможно, вы могли бы использовать проплисты.

Предположим, вы сохранили профиль пользователя.

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

Затем, через пару лет, вы решили расширить профиль пользователя с возрастом пользователя.

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

Теперь вам нужно получить профиль пользователя из БД

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» («старый»), вы получите «неопределенный» как возраст, а затем вы можете обновить профиль и сохранить его с новым значением, так что он будет быть сущностью «новой версии».

Таким образом, нет конфликта версий.

Возможно, это не лучший способ, но в некоторых случаях это может быть решением.

1 голос
/ 03 ноября 2011

Лучший подход - иметь копию сериализованного (профиля), а также копию того же самого, но в форме записи.Затем каждый раз, когда вносятся изменения в профиль формы записи, также вносятся изменения в сериализованный профиль того же пользователя ATOMICALLY (в рамках одной и той же транзакции!). Код, который изменяет профиль записи пользователя, должен всегда пересчитывать новую сериализованную форму, которая для вас является внешним представлением записи пользователя

-record(record_prof,{name,age,sex}).
-record(myuser,{
            username,
            record_profile = #record_prof{},
            serialized_profile
        }).<br>
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.
Так что, что бы ни делала функция сериализации, это так.Но это всегда оставляет свободное изменение профиля.Таким образом, мы сохраняем сериализованный профиль как всегда правильное представление профиля записи в любое время.Когда происходят изменения в профиле записи, сериализованная форма также должна быть пересчитана (транзакционная), чтобы иметь целостность.
1 голос
/ 03 ноября 2011

Это сильно зависит от пропорции количества записей, частоты изменений и допустимого простоя.Я бы предпочел обновить профили до новейшей версии в первую очередь из-за ремонтопригодности.Вы также можете создать систему, которая будет обновляться на лету, как это делает 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, потому что вы будете делать с неизвестным типом записи?Это ошибка, поэтому терпите неудачу быстро!

У вас есть много других опций, например, такие как hack:

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).

и многие другие возможности.

0 голосов
/ 03 ноября 2011

Вы можете использовать некоторые расширяемые форматы сериализации данных, такие как JSON или буфер протокола Google.

Оба эти формата поддерживают добавление новых полей без нарушения обратной совместимости.Используя их, вам не нужно вводить явное управление версиями в ваши сериализованные структуры данных.

Выбор между двумя форматами зависит от вашего варианта использования.Например, использование буферов протокола более надежно, тогда как с JSON легче начать.

...