Created
October 2, 2012 10:14
-
-
Save ToJans/3818011 to your computer and use it in GitHub Desktop.
Attempt for CQRS in erlang
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Erlang R13B04 (erts-5.7.5) [smp:8:8] [rq:8] [async-threads:0] | |
Eshell V5.7.5 (abort with ^G) | |
1> cd('/blah/erlang/CQRS'). | |
C:/blah/erlang/CQRS | |
ok | |
2> ls(). | |
item.erl item.hrl | |
ok | |
3> c(item). | |
./item.erl:65: Warning: function start_link/1 is unused | |
./item.erl:67: Warning: function init/1 is unused | |
./item.erl:70: Warning: function handle_call/3 is unused | |
{ok,item} | |
4> eunit:test(item,[verbose]). | |
======================== EUnit ======================== | |
module 'item' | |
item: item_activation_test_ (Activating for the first time should work)...ok | |
item: item_activation_test_ (Activating twice in a row should fail)...ok | |
item: item_deactivation_test_ (Deactivating before activating should fail)...ok | |
item: item_deactivation_test_ (Deactivating after activating should work)...ok | |
item: item_deactivation_test_ (Deactivating a deactivated item should fail)...ok | |
item: item_deactivation_test_ (Deactivating an active item with a balance should fail)...ok | |
item: deposit_amount_test_ (Depositing an amount to an unactivated item should fail)...ok | |
item: deposit_amount_test_ (Depositing an amount to an activated item should succeed)...ok | |
item: withdraw_amount_test_ (Withdrawing an amount from an unactivated item should fail)...ok | |
item: withdraw_amount_test_ (Withdrawing an amount from an activated item with a balance large enough should succeed)...ok | |
item: withdraw_amount_test_ (Withdrawing an amount for the second time where the total exceeds the balance should fail)...ok | |
[done in 0.172 s] | |
======================================================= | |
All 11 tests passed. | |
ok | |
5> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-module(item). | |
-author('Tom Janssens <[email protected]>'). | |
%%-behavior(gen_server). | |
-include("item.hrl"). | |
%% COMMAND HANDLERS | |
%% todo: figure out proper routing so I can avoid this boilerplate code | |
handle(#state{id=StateId},#activate_item{id=CommandId}) when StateId =/= CommandId -> | |
{unhandled,command}; | |
handle(#state{id=StateId},#deactivate_item{id=CommandId}) when StateId =/= CommandId -> | |
{unhandled,command}; | |
handle(#state{id=StateId},#deposit_amount{id=CommandId}) when StateId =/= CommandId -> | |
{unhandled,command}; | |
handle(#state{id=StateId},#withdraw_amount{id=CommandId}) when StateId =/= CommandId -> | |
{unhandled,command}; | |
handle(#state{active=false}, #activate_item{id=Id, name=Name}) -> | |
{ok, [#item_activated{id=Id, name=Name}]}; | |
handle(#state{active=true}, #activate_item{id=_Id}) -> | |
{error, "you can not activate an item if it is already active"}; | |
handle(#state{balance=Balance}, #deactivate_item{id=_Id}) when Balance > 0 -> | |
{error, "You can not deactivate this item as it is still in stock"}; | |
handle(#state{active=true}, #deactivate_item{id=Id}) -> | |
{ok, [#item_deactivated{id=Id}]}; | |
handle(#state{active=false,id=Id}, #deactivate_item{id=Id}) -> | |
{error, "you can not deactivate an item if it is not active"}; | |
handle(#state{active=false,id=Id}, #deactivate_item{id=Id}) -> | |
{error, "you can not perform any actions on an inactive item"}; | |
handle(#state{active=true}, #deposit_amount{id=Id, amount=Amount}) -> | |
{ok, [#amount_deposited{id=Id, amount=Amount}]}; | |
handle(#state{active=false}, #deposit_amount{id=_Id, amount=_Amount}) -> | |
{error, "you can not perform any actions on an inactive item"}; | |
handle(#state{balance=Balance},#withdraw_amount{id=_Id, amount=Amount}) when Balance < Amount -> | |
{error, "the amount you want to withdraw is larger then the balance"}; | |
handle(_State,#withdraw_amount{id=Id, amount=Amount}) -> | |
{ok, [#amount_withdrawn{id=Id, amount=Amount}]}. | |
%% EVENT HANDLERS | |
on(State,#item_activated{id=Id}) when State#state.id == Id -> | |
State#state{active=true}; | |
on(State,#item_deactivated{id=Id}) when State#state.id == Id -> | |
State#state{active=false}; | |
on(State,#amount_deposited{id=Id,amount=Amount}) when State#state.id == Id -> | |
State#state{balance=State#state.balance+Amount}; | |
on(State,#amount_withdrawn{id=Id,amount=Amount}) when State#state.id == Id -> | |
State#state{balance=State#state.balance-Amount}; | |
on(State,_) -> | |
State. | |
%% GEN_SERVER BEHAVIOR - does not work ATM | |
start_link(Id) -> gen_server:start_link({local,Id},?MODULE,[Id],[]). | |
init(Id) -> | |
{ok,load(Id,[])}. | |
handle_call(Command,_From,State) -> | |
{Result,Details} = handle(State,Command), | |
case Result of | |
ok -> | |
{reply,Result,load(State,Details)}; | |
error -> | |
{reply,Result,State}; | |
unhandled -> | |
{noreply,State} | |
end. | |
%% HELPER FUNCTIONS | |
load(State,Events) when is_record(State,state) -> | |
lists:foldl(fun(E,NewState) -> on(NewState,E) end,State,Events); | |
load(Id,Events) -> | |
load(#state{id=Id},Events). | |
%% SPECS | |
-define(TEST,1). | |
-ifdef(TEST). | |
-include_lib("eunit/include/eunit.hrl"). | |
item_activation_test_() -> | |
[ | |
{"Activating for the first time should work", fun()-> | |
State = load("item/1",[]), | |
Result = handle(State,#activate_item{id="item/1",name="Test item"}), | |
?assertEqual({ok,[#item_activated{id="item/1",name="Test item"}]},Result) | |
end }, | |
{"Activating twice in a row should fail", fun()-> | |
State = load("item/1",[#item_activated{id="item/1",name="blah"}]), | |
{Status,_Message} = handle(State,#activate_item{id="item/1",name="Test item"}), | |
?assertEqual(Status,error) | |
end } | |
]. | |
item_deactivation_test_() -> | |
[ | |
{"Deactivating before activating should fail", fun()-> | |
State = load("item/1",[]), | |
{Status,_Message} = handle(State,#deactivate_item{id="item/1"}), | |
?assertEqual(Status,error) | |
end }, | |
{"Deactivating after activating should work", fun()-> | |
State = load("item/1",[#item_activated{id="item/1"}]), | |
Result = handle(State,#deactivate_item{id="item/1"}), | |
?assertEqual(Result,{ok,[#item_deactivated{id="item/1"}]}) | |
end }, | |
{"Deactivating a deactivated item should fail", fun()-> | |
State = load("item/1",[#item_activated{id="item/1",name="blah"},#item_deactivated{id="item/1"}]), | |
{Status,_Message} = handle(State,#deactivate_item{id="item/1"}), | |
?assertEqual(Status,error) | |
end }, | |
{"Deactivating an active item with a balance should fail",fun() -> | |
State = load("item/1",[#item_activated{id="item/1",name="blah"},#amount_deposited{id="item/1",amount=100}]), | |
{Status,_Message} = handle(State,#deactivate_item{id="item/1"}), | |
?assertEqual(Status,error) | |
end } | |
]. | |
deposit_amount_test_() -> | |
[ | |
{"Depositing an amount to an unactivated item should fail",fun()-> | |
State = load("item/1",[]), | |
{Status,_Message} = handle(State,#deposit_amount{id="item/1"}), | |
?assertEqual(Status,error) | |
end}, | |
{"Depositing an amount to an activated item should succeed",fun()-> | |
State = load("item/1",[#item_activated{id="item/1"}]), | |
Result = handle(State,#deposit_amount{id="item/1",amount=100}), | |
?assertEqual(Result,{ok,[#amount_deposited{id="item/1",amount=100}]}) | |
end} | |
]. | |
withdraw_amount_test_() -> | |
[ | |
{"Withdrawing an amount from an unactivated item should fail",fun()-> | |
State = load("item/1",[]), | |
{Status,_Message} = handle(State,#withdraw_amount{id="item/1"}), | |
?assertEqual(Status,error) | |
end}, | |
{"Withdrawing an amount from an activated item with a balance large enough should succeed",fun()-> | |
State = load("item/1",[#item_activated{id="item/1"},#amount_deposited{id="item/1",amount=200}]), | |
Result = handle(State,#withdraw_amount{id="item/1",amount=100}), | |
?assertEqual(Result,{ok,[#amount_withdrawn{id="item/1",amount=100}]}) | |
end}, | |
{"Withdrawing an amount for the second time where the total exceeds the balance should fail",fun()-> | |
State = load("item/1",[#item_activated{id="item/1"},#amount_deposited{id="item/1",amount=120},#amount_withdrawn{id="item/1",amount=100}]), | |
{Status,_Message} = handle(State,#withdraw_amount{id="item/1",amount=100}), | |
?assertEqual(Status,error) | |
end } | |
]. | |
-endif. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
%% TYPE DECLARATIONS | |
-type item_id() :: nonempty_string(). | |
%% INTERNAL STATE RECORD | |
-record(state,{ id::item_id(), active=false :: boolean(),balance=0::non_neg_integer()}). | |
%% COMMANDS | |
-record(activate_item, {id::item_id(),name::nonempty_string()}). | |
-record(deactivate_item, {id::item_id()}). | |
-record(deposit_amount,{id::item_id(),amount=1::pos_integer()}). | |
-record(withdraw_amount,{id::item_id(),amount=1::pos_integer()}). | |
%% EVENTS | |
-record(item_activated, {id::item_id(),name::nonempty_string()}). | |
-record(item_deactivated, {id::item_id()}). | |
-record(amount_deposited,{id::item_id(),amount=1::pos_integer()}). | |
-record(amount_withdrawn,{id::item_id(),amount=1::pos_integer()}). |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment