In three earlier posts we have explored modeling a Rock-paper-scissor game in F#. We have modeled the domain and infrastructure. In the last post we looked at refactoring and C# interop. In this post we'll go back to basics when we explore using C# infrastructure in our solution. We're going to look at record types, classes and interface implementation.
We want to use our Domain with Commans and Events together with some C# infrastructure. The infrastruct might have interfaces that our F# solution need to implement to use the infrastrcutre. We could do some adapter clue, but in this post we're going to implement the interfaces in F#.
The C# interface that the infrastructure uses looks like this;
public interface ICommand
{
Guid AggregateId { get; }
Guid CorrelationId { get; }
}
So let's take a look at one of our commmands - the CreateGameCommand.
type CreateGameCommand = {
playerName: string
firstMove: Move
name:string
id:string }
We have used interfaces before in our solution - the Event marker interface.
type Event =
interface
end
type MoveMadeEvent=
{
playerName:string
move:Move
}
interface Event
To implement the C# interface we use the same syntax, specifying the the interface then each memerber of the interface as follow;
type CreateGameCommand=
{playerName: string
firstMove: Move
name:string
id:System.Guid} interface Commanding.ICommand with
member x.AggregateId = x.id
member x.CorrelationId = Guid.NewGuid()
Note that all interface implementations in F# are Explicit
Here we changed the id to a Guid to align with the interface, pointing out the id property. The CorreclationId don't have a corresponding property yet, as we create a new guid. The CorrelationId would return a new Guid each time - not ok. The correlationId is just an identifier of each instance, nothing we need to set our self when creating a command. So what are our options regarding the CorrelationId ? If we want to stick to a record type we need to add a property to match CorrelationId.
type CreateGameCommand =
{playerName: string
firstMove: Move
name:string
id:System.Guid
correlationId:System.Guid}
interface Commanding.ICommand with
member x.AggregateId = x.id
member x.CorrelationId = x.correlationId
If we would like to skip passing a correlcationId, we could create an factory, or we need to create a class instead. A simple factory;
let createGameCommand playerName firstMove name gameId =
{ playerName = playerName; firstMove = firstMove; name = name; id = gameId; correlationId = Guid.NewGuid() }
In this case a record type fits better (Type inference, Immubility and pattern matching - Record types vs Classes).
Just to have a look - lets go for the class approach;
type CreateGameCommand(playerName:string, firstMove:Move, name:string, gameId:Guid, correlationId:Guid) =
member x.playerName = playerName
member x.firstMove = firstMove
member x.name = name
member x.id = gameId
member x.correlationId = correlationId
Adding the interface to the class;
type CreateGameCommand(playerName:string, firstMove:Move, name:string, gameId:Guid, correlationId:Guid) =
interface Commanding.ICommand with
member x.AggregateId = x.id
member x.CorrelationId = x.correlationId
member x.playerName = playerName
member x.firstMove = firstMove
member x.name = name
member x.id = gameId
member x.correlationId = correlationId
The C# interface that the infrastructure uses for Event looks like this;
public interface IEvent
{
Guid CorrelationId { get; set; }
Guid SourceId { get; }
}
The SourceId would be the aggregate related to the change. And CorrelationId what command resulted in this event. This introduces a new challange. The infrastructure needs to set something in our immutable world. Our events are record types - immutable. But to comply with this interface we could declare one of it's properties as mutable;
type MoveMadeEvent=
{
playerName:string
move:Move
id:Guid
mutable correlationId:Guid
}
interface Events.IEvent with
member x.SourceId = x.id
member x.CorrelationId with get() = x.correlationId
member x.CorrelationId with set(v) = x.correlationId <- v
We could skip the get();
member x.CorrelationId = x.correlationId
member x.CorrelationId with set(v) = x.correlationId <- v
###Conclusion
Hope the infrastructure is worth it. Our record types is now bulkier. There might have been other way to use the infrastrcutrue, wrapping it's contracts. But we got a good back to basics look on record types, classes and interface implementation in F#. Due to that the infrastructure where in C#, we haven't looked in creating interfaces in F#. Below is some resources on the topic we touched upon. Hope to get back to modeling in furute posts.
Enjoy!
"We could also give a timespan as timeout when the node would release the session if no messages are received. This allows the node or another node to pick up the session when new messages comes in." ska nog flyttas ovanför "var session = await _client.AcceptMessageSessionAsync(TimeSpan.FromMinutes(1));".
"When not using sessions, it is easy to handle messages using the message pump."
Behöver man beskriva mer vad en message pump är eller räcker det med länken? Jag vet inte vad det är för något.
"... we start listening for new a session on a new thread with a bit of delay ..."
Varför behöver man en delay!?
Allmänna frågor:
Vad händer om en nod går ner och inte lyckas hantera ett helt spel? Kan en annan nod ta över sessionen då?