Introduction
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.
Scenario
We want to use our Domain with Commands and Events together with some C# infrastructure. The infrastructure might have interfaces that our F# solution need to implement to use the infrastructure. We could do some adapter clue, but in this post we’re going to implement the interfaces in F#.
Command interface
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 member 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 Correlation ID don’t have a corresponding property yet, as we create a new guid. The Correlation ID would return a new Guid each time – not ok. The correlation ID 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 Correlation ID ? If we want to stick to a record type we need to add a property to match Correlation ID.
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 correlation ID, 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, immutability 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
Event interface
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 Correlation ID what command resulted in this event. This introduces a new challenge. 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 infrastructure, 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 future posts.
Enjoy!