Skip to content

Instantly share code, notes, and snippets.

@ToJans
Created October 1, 2012 11:55
Show Gist options
  • Save ToJans/3811189 to your computer and use it in GitHub Desktop.
Save ToJans/3811189 to your computer and use it in GitHub Desktop.
Attempt for CQRS in erlang
%% INFRASTRUCTURE CRAP
load(Events) ->
load(#state{},Events).
load(State,[]) ->
State;
load(State,[Event|RemainingEvents]) ->
NewState = change(State,Event),
load(NewState,RemainingEvents).
-module(item).
-exports([handle/2]).
-define(TEST,1).
-include("item.hrl").
%% COMMAND HANDLERS
handle(State,#activate_item{id=Id,name=Name}) ->
case State#state.active of
false ->
{ok, [#item_activated{id=Id,name=Name}]};
true ->
{error, "you can not activate an item if it is already active"}
end;
handle(State,#deactivate_item{id=_Id}) when State#state.balance>0 ->
{error,"You can not deactivate this item as it is still in stock"};
handle(State,#deactivate_item{id=Id}) ->
case State#state.active of
true ->
{ok, [#item_deactivated{id=Id}]};
false ->
{error, "you can not deactivate an item if it is not active"}
end;
handle(State,_Command) when State#state.active == false ->
{error, "you can not perform any actions on an inactive item"};
handle(_State,#deposit_amount{id=Id,amount=Amount}) ->
{ok, [#amount_deposited{id=Id,amount=Amount}]};
handle(State,#withdraw_amount{id=_Id,amount=Amount}) when State#state.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
change(State,#item_activated{id=_Id}) ->
State#state{active=true};
change(State,#item_deactivated{id=_Id}) ->
State#state{active=false};
change(State,#amount_deposited{id=_Id,amount=Amount}) ->
State#state{balance=State#state.balance+Amount};
change(State,#amount_withdrawn{id=_Id,amount=Amount}) ->
State#state{balance=State#state.balance-Amount}.
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
-include("infrastructure.erl").
item_activation_test_() ->
[
{"Activating for the first time should work", fun()->
State = load([]),
Result = handle(State,#activate_item{id="item/1",name="Test item"}),
?assertEqual(Result,{ok,[#item_activated{id="item/1",name="Test item"}]})
end },
{"Activating twice in a row should fail", fun()->
State = load([#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([]),
{Status,_Message} = handle(State,#deactivate_item{id="item/1"}),
?assertEqual(Status,error)
end },
{"Deactivating after activating should work", fun()->
State = load([#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_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_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([]),
{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_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([]),
{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_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_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.
%% 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