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!
need to start --> needs to start
need to process --> needs to process
the node should process more games than one each minute -->
the node should process more than one game each minute
await Task.Factory.StartNew(() => StartSessionAsync(messageHandler, options)); -->
Task.Run()?
. Letting another node take over. -->
, letting another node take over.
Skriv ut hela namn på typer, använd bara "var" när det uppenbart vilket typ det handlar om.
Flytta Orleans och andra referenser till ny rubrik, t.ex. More resources, då det handlar om helt nya frågor.
Skriv ut Azure WebJobs, vad Orleans är.