#Introduction#
In the quest of getting into F# we have explored modeling a rock-paper-scissor game. In the two earlier posts we made a Game aggregate and the GameHandler. In this post we're going to do some refactoring and see what we could learn from that. The earlier posts could be found here;
- http://www.jayway.com/2014/01/13/exploring-f-through-modeling/
- http://www.jayway.com/2014/02/24/exploring-f-through-modeling-2/
In this post we're going to do some refactoring and see what we could learn from that.
#Restoring state#
In the game aggregate we have the restore state function. This is our first target for refactoring. We want the GameHandler (part of the application) to hold the part of the step function, and letting the game just restore the state for each event.
####Before
let restoreState state (events:list<Event>) :State=
let step (evt:Event) (state:State) =
match evt with
| :? GameCreatedEvent as e ->
{ gameState = GameState.Started; creatorName = e.playerName; creatorMove = state.creatorMove }
| :? MoveMadeEvent as e when e.playerName = state.creatorName ->
{ gameState = state.gameState; creatorName = state.creatorName; creatorMove = e.move }
| :? GameEndedEvent as e -> { state with gameState = GameState.Ended }
|_ -> state
List.foldBack step events state
We added applyEvent to Game that now does the type matching. This way the GameHandler could restore state from a future snap shot (as pointed out in the comments from post #2).
####After
let applyEvent (state:State) (evt:Event) =
match evt with
| :? GameCreatedEvent as e ->
{ gameState = GameState.Started; creatorName = e.playerName; creatorMove = state.creatorMove }
| :? MoveMadeEvent as e when e.playerName = state.creatorName ->
{ gameState = state.gameState; creatorName = state.creatorName; creatorMove = e.move }
| :? GameEndedEvent as e -> { state with gameState = GameState.Ended }
|_ -> state
In the handler we now fold the events, applying each event with our new applyEvent. Think of fold as the LINQ equvilant of Aggregate. The handler gives the initial default state to aggregate upon.
####After
let rehydrate events =
List.fold applyEvent {State.creatorName="";State.creatorMove=Move.Rock;State.gameState=NotStarted } events
#Storing events#
The second target for refactoring is the way we store the events. In the GameHandler we have a map of aggregateId and a list of events. The load function simply returns the events for the given aggregate and the save does nothing. To be able to play the game and try this out, we need to change this.
####Before
let eventStore = [("a",[{GameCreatedEvent.playerName="per";name="stanley cup"} :> Event;{MoveMadeEvent.playerName="per";MoveMadeEvent.move=Move.Rock} :> Event])] |> Map.ofList
let load store id =
eventStore.[id]
let save store commandId events =
()
We are going to continue to store our events in memory in a map, but without any dummy events and also implement save. To do this we need to change/update the list of events (or the eventstore it self). Replacing the eventstore with and empty map. The save method now needs to check if there are any events for the aggregate and then append to the list of events. Due to this save changes name to append.
####After
let mutable eventStore = Map.empty
let load store aggregateId =
if eventStore.ContainsKey aggregateId
then eventStore.[aggregateId]
else List.empty
let append store aggregateId events =
let oldList = load eventStore aggregateId
let newList = List.append oldList events
eventStore <- Map.add aggregateId newList eventStore
We now need to mutate the EventStore or each list of events in the EventStore map. F# is not big on mutating :) so we need to declare the EventStore as mutable to get away with it. This way we now have our in-memory persistance of events, enabling us to try this out.
#C# interop - Trying it out With the changes made to restoring state and persiting state, we're now going to see how it feels like to use our Game from C#, to test and try out our solution.
We're not going to look at testing of the Game aggregate in this post but simply playing a game end-to-end as two simplified integration tests.
private const string GameId = "Game001";
public IntegrationTests()
{
var player1 = new Game.CreateGameCommand("per", Game.Move.Paper, "TestGame", GameId);
var player2 = new Game.MakeMoveCommand(Game.Move.Scissors, "Christoffer", GameId);
GameHandler.handle(player1);
GameHandler.handle(player2);
}
First we create a CreateGameCommand with the name of the game and the move for playerOne and send it to the GameHandler. Now its time for player two to make a move. So we create a MoveCommand and passes that to the GameHandler. The two commands should result of the four events in total (GameCreated, MoveMade, MoveMade, GameEnded). Lazyly we here just check the count :p
[Fact]
public void AllEventsAreRecorded()
{
Assert.Equal(4, GameHandler.eventStore[GameId].Count());
}
Lacking projections to check the result, we simply check the GameEndedEvent for the result of the game (we could check the game(state) it self).
[Fact]
public void BestPersonWins()
{
var end = GameHandler.eventStore[GameId].OfType<Game.GameEndedEvent>().Single();
Assert.True(end.result.IsPlayerTwoWin);
}
Note the IsPlayerTwoWin property the is exposed to C# to check the result.
#Lessons learned#
- We looked how to use mutable
- We used fold for restoring state much like the C# starting point from post #1 Func<state, event, state>
- We used the Map.add function in the append function. returning a new map in it's imutable fashion.
- We looked at using our Game from C#, pretty straight forward. The only thing notable here was the IsPlayerTwoWin. But what if we would have passed move as a string ? How would we get from string -> Move ? - Next time.
#Conclusion# We've made the first refactoings to our game. The is still more to do. Maybe try commands and events as discriminated union. Testing in F#, using the a real eventstore (geteventstore.)
Enjoy!