I develop the system where I assume will be many users. Each user has a profile represented inside the application as a record. To store user's profile I do the following base64:encode_to_string(term_to_binary(Profile))
, so basically profiles stored in serialized maner.
So far everything is just fine. Now comes the question:
From time to time I do plan to extend profile functionality by adding and removing certain fields in it. My question is what is a best strategy to handle these changes in the code?
The approach I see at the moment is to do something like this:
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,
I want to know if there are any better solutions for my task?
Additional info: I use is simple KV storage. It cannot store Erlang types this is why I use State#state.player#player.chips#chips.br
Perhaps, you could use proplists.
Assume, you have stored some user profile.
User = [{name,"John"},{surname,"Dow"}].
store_profile(User).
Then, after a couple of years you decided to extend user profile with user's age.
User = [{name,"John"},{surname,"Dow"},{age,23}].
store_profile(User).
Now you need to get a user profile from 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).
If you get a user profile of 'version 2', you will get an actual age (23 in this particular case).
If you get a user profile of 'version 1' ('old' one), you will get 'undefined' as an age, - and then you can update the profile and store it with the new value, so it will be 'new version' entity.
So, no version conflict.
Probably, this is not the best way to do, but it might be a solution in some case.
It strongly depend of proportion of number of records, frequency of changes and acceptable outage. I would prefer upgrade of profiles to newest version first due maintainability. You also can make system which will upgrade on-fly as mnesia does. And finally there is possibility keep code for all versions which I would definitely not prefer. It is maintenance nightmare.
Anyway when is_record/2
is allowed in guards I would prefer
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
Notice there is not catch all clause because what you would do with unknown record type? It is error so fail fast!
You have many other options e.g. hack like:
case element(1,Profile) of
profile1 ->
% do stuff with Profile#profile1
ok;
profile2 ->
% do stuff with Profile#profile2
ok
end
or something like
{_, 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).
and many other possibilities.
The best approach is to have the copy of the serialized (profile) and also a copy of the same but in record form. Then , each time changes are made to the record-form profile, changes are also made to the serialized profile of the same user ATOMICALLY (within the same transaction!). The code that modifies the users record profile, should always recompute the new serialized form which, to you, is the external representation of the users record
-record(record_prof,{name,age,sex}). -record(myuser,{ username, record_profile = #record_prof{}, serialized_profile }).So whatever the serialize function is doing, that's that. But this always leaves an overhead free profile change. We thereby keep the serialized profile as always the correct representation of the record profile at all times. When changes occur to the record profile, the serialized form must also be recomputed (transactional) so as to have integrity.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With