Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Modeling database records as types

Tags:

f#

I'm rewriting a C# library in F# in which most of the classes map one-to-one with database tables (similar to ActiveRecord). I'm considering whether to use records or classes (maybe even DUs?). There's a fair amount of validation in the property setters to maintain invariants. What would be the best way to model this in F#? I don't want an object that violates business logic to be persisted to the database. Any ideas are welcome.

A few additional thoughts... Is it better to move the invariants to an external 'controller' class? Coming from C# it feels wrong to allow an object that corresponds to a database record to contain anything that can't be saved to the database. I suppose because failing earlier seems better than failing later.

like image 978
Daniel Avatar asked Jan 18 '10 22:01

Daniel


2 Answers

You can have your data in a record, and still keep the validation logic with the data type, by attaching methods to the record:

type Person =
    { First : string;
      Last  : string; } with
    member x.IsValid () =
        let hasValue = System.String.IsNullOrEmpty >> not    
        hasValue x.First && hasValue x.Last

let jeff = { First = "Jeff"; Last = "Goldblum" }
let jerry = { jeff with First = "Jerry" }
let broken = { jerry with Last = "" }

let valid = [jeff; jerry; broken] 
            |> List.filter (fun x -> x.IsValid())

The copy semantics for records are almost as convenient as setting a property. The validation doesn't happen on property set, but it's easy to filter a list of records down to only the valid ones.

like image 164
Joel Mueller Avatar answered Oct 09 '22 11:10

Joel Mueller


This should actually be a good way for you to handle it. Having your validation logic in the constructor will give you piece of mind later on in your code because the object is immutable. This also opens up multi-threading possibilities.

Immutable Version

type Customer (id, name) =

    do // Constructor
        if id <= 0 then 
            raise(new ArgumentException("Invalid ID.", "id"))
        elif String.IsNullOrEmpty(name) then 
            raise(new ArgumentException("Invalid Name.", "name"))    

    member this.ID
        with get() = id
    member this.Name
        with get() = name

    member this.ModifyName value =
        new Customer(id, value)    

Mutable Version

type Customer (id) =

    let mutable name = ""

    do // Constructor
        if id <= 0 then 
            raise(new ArgumentException("Invalid ID.", "id"))

    member this.ID
        with get() = id
    member this.Name
        with get() = name  
        and set value =
            if String.IsNullOrEmpty(name) then 
                raise(new ArgumentException("Invalid Name.", "value")) 
            name <- value
like image 30
ChaosPandion Avatar answered Oct 09 '22 13:10

ChaosPandion