首页 > 解决方案 > How to build F# type fulfilling business rules?

问题描述

I´m trying to build a type in F#, where when I get an object of that type I can be sure it´s in a valid state.
The type is called JobId and it just holds a Guid.
The business rule is: It must be a Guid - but no empty Guid.
I´ve already implemented the type in C# but now I would like to port it to a F# class library.

That´s the C# type:

public sealed class JobId
{
    public string Value { get; }

    private JobId(string value)
        => Value = value;

    public static JobId Create()
        => new JobId(Guid.NewGuid().ToString("N"));

    public static Option<JobId> Create(Guid id)
        => id == Guid.Empty
        ? None
        : Some(new JobId(id.ToString("N"));

    public static Option<JobId> Create(string id)
    {
        try
        {
            var guid = new Guid(id);
            return Create(guid);
        }
        catch (FormatException)
        {
            return None;
        }
    }
}

So how do I build that in F#? Thanks!

Update 1:
I tried to implement it as discriminated union type like this:

type JobId =
    | JobId of string

But the problem is, that I can´t define any business rules with that approach.
So the final question is: How to ensure that the string in JobId ist in a certain format?

标签: c#f#domain-driven-designoptionaloptions

解决方案


I've adapted Tomas' answer to use a DU instead of a class to preserve proper equality and comparison, allowing JobId to work as expected as a grouping key, for example.

[<AutoOpen>]
module JobId =
    open System
    type JobId = private JobId of string with
        static member Create() = JobId(Guid.NewGuid().ToString("N"))

        static member Create(id:Guid) =
            if id = Guid.Empty then None
            else Some(JobId(id.ToString("N")))

        static member Create(id:string) =
            try JobId.Create(Guid(id))
            with :? FormatException -> None

You have to put the type inside a module and then you can't access the DU constructor directly outside of that module:

JobId.Create (System.Guid.NewGuid()) // Some (JobId "1715d4ae776d441da357f0efb330be43")
JobId.Create System.Guid.Empty // None
JobId System.Guid.Empty // Compile error

推荐阅读