2010-11-29 7 views
11

Ich arbeite an einem Prototyp, um eine Dokumentendatenbank zu verwenden (derzeit MongoDB, kann sich ändern) und fand die .NET-Treiber ein wenig Schmerz, also dachte ich, ich würde den Datenzugriff mit dem Repository abstrahieren Muster. Das sollte es einfach machen, den Treiber, den ich gerade benutze (NoRM, mongodb-csharp, simple-mongob), mit auszutauschen, den Killer f # mongodb-Treiber, der nicht lutscht, wenn er fertig ist.Repository-Muster in F #

Meine Frage ist um die Hinzufügen Operation. Dies wird einige Seiteneffekte auf die Datenbank haben und somit werden nachfolgende Aufrufe an Alle unterschiedlich sein. Kümmert mich das? In C# würde ich das traditionell nicht machen, aber ich fühle, dass in F # ich sollte.

ist die generische Repository-Schnittstelle:

type IRepository<'a> = 
    interface 
     abstract member All : unit -> seq<'a> 

     // Add has a side-effect of modifying the database 
     abstract member Add : 'a -> unit 
    end 

Und hier ist wie eine MongoDB Implementierung aussieht:

type Repository<'b when 'b : not struct>(server:MongoDB.IMongo,database) = 
    interface IRepository<'b> with 

     member x.All() = 
      // connect and return all 

     member x.Add(document:'b) = 
      // add and return unit 

In der gesamten App I IRepository verwenden, so dass es leicht Treiber zu ändern und potenziell Datenbanken.

Calling All ist in Ordnung, aber mit Hinzufügen, was ich gehofft hatte, war anstatt eine Einheit zurückzugeben, eine neue Repository-Instanz zurückgeben. Etwas wie:

Das Problem ist, dass, wenn ich Get aufrufen, dann hinzufügen, das ursprüngliche Repository immer noch alle Dokumente zurückgibt. Beispiel:

let repo1 = new Repository<Question>(server,"killerapp") :> IRepository<Question> 
let a1 = repo1.All() 
let repo2 = repo1.Add(new Question("Repository pattern in F#")) 
let a2 = repo2.All() 

Idealerweise möchte ich Länge von a1 und a2, anders zu sein, aber sie sind die gleichen wie sie beide die Datenbank getroffen. Die Anwendung funktioniert, Benutzer können ihre Frage stellen, aber der Programmierer fragt sich, warum er ein neues IRepository zurückgibt.

Also sollte ich versuchen, den Nebeneffekt von Add auf der Datenbank im Design der Typen zu behandeln? Wie würden andere dies tun, verwenden Sie ein Repository oder eine solche Interface-Klasse oder haben Sie einen besseren funktionalen Ansatz?

+0

Worüber Sie MongoDB nicht unterstützt, aber die Idee ist immer noch interessant, und mindestens eine Implementierung existiert. Werfen Sie einen Blick auf http://www.datomic.com/. –

Antwort

4

Es sieht so aus, als ob Sie Unveränderlichkeit auf Funktionen anwenden, die den Zustand in der Außenwelt beeinflussen. Wie würden Sie unabhängig von der F # -Implementierung auf der MongoDB-Ebene arbeiten? Wie würden Sie verhindern, dass repo1 Änderungen sehen, die repo2 macht? Was passiert, wenn ein anderer Prozess die Datenbank beeinflusst - ändern sich in diesem Fall sowohl repo1 als auch repo2?

Um es anders auszudrücken, stellen Sie sich eine Implementierung von System.Console vor, die so funktioniert. Wenn Console.Out.WriteLine immer ein neues unveränderliches Objekt zurückgibt, wie würde es mit Aufrufen von Console.In.ReadLine interagieren?

Bearbeiten tl; dr: Tun Sie dies nicht. Manchmal sind Nebenwirkungen gut.

+0

+1 Ich stimme Tim zu. Kurz gesagt: Nein, ein neues IRepository in Add zurückzugeben macht hier keinen Sinn. –

2

Ich glaube nicht, dass es Sinn macht, eine unveränderliche Schnittstelle zu einem von Natur aus veränderbaren Typ (wie einer Datenbank) zu haben. Möglicherweise möchten Sie jedoch die Funktionalität in einen veränderbaren Datenbanktyp (IRepository<'a> in Ihrem Fall) und einen unveränderlichen Satz von Änderungen (z. B. ChangeSet<'a>) aufteilen.Das Ergebnis könnte etwa wie folgt aussehen:

+0

Hier repräsentiert ein 'Repository' das Transaktionsprotokoll der Datenbank zu einem Zeitpunkt, und' ChangeSet' repräsentiert eine Transaktion. Das ist sehr cool, aber ich habe keine Ahnung, wie es zu einem zugrunde liegenden Datenbankserver zuordnen würde. –

+0

Interessant, ist diese ChangeSet und ApplyChange das Unit of Work Muster? – yanta

+0

@yanta - ja, im Wesentlichen. Das Muster wird normalerweise mit einer veränderlichen Arbeitseinheit dargestellt, aber es gibt keinen Grund dafür. – kvb

0

Sie könnten es in einen Computerausdruck einfügen, damit es rein erscheint. Sie könnten es sogar noch weiter mit Code erweitern, um Timeouts und abgestürzte Server zu behandeln. Ich bin neu in dem Konzept, wenn die Experten mich dann schulen könnten, wenn etwas unpassend erscheint.

Ich denke, dieses Konzept wäre viel nützlicher, wenn Sie mehr als nur ein Repository durchforsten, aber ich wollte es einfach halten.

type IRepository<'a> = //'            
    abstract member All : unit -> seq<'a> //' 
    abstract member Add : 'a -> unit //' 
    abstract member Get : int -> 'a //' 

type Rep<'a, 'b> = IRepository<'a> -> 'b //' 

type RepositoryBuilder() = 
    member x.Bind (f:Rep<'a, 'b>, g:'b -> Rep<'a, 'c>) rep = g (f rep) rep //'    
    member x.Delay (f:unit -> Rep<'a, 'b>) = f() //' 
    member x.Return v r = v 
    member x.ReturnFrom f = f 
    member x.Zero() =() 

let rep = RepositoryBuilder() 

let action (action:_->unit) repository = 
    action repository  

let func (func:Rep<_, _>) repository = 
    func repository 

type Person = { 
    id:int 
    name:string 
    age:int 
    finalized:bool 
} 

let addPeople = rep { 
    do! action(fun r -> r.Add { id = 1; name = "Jim"; age = 45; finalized = false }) 
    do! action(fun r -> r.Add { id = 2; name = "Bob"; age = 32; finalized = false }) 
    do! action(fun r -> r.Add { id = 3; name = "Sue"; age = 58; finalized = false }) 
    do! action(fun r -> r.Add { id = 5; name = "Matt"; age = 11; finalized = false }) 
} 
+0

Ich werde ehrlich sein, ich folge diesem nicht, aber ich werde in Computerausdrücke schauen und hoffentlich kann ich darauf zurückkommen. – yanta

+0

@yanta - Wenn Sie herausfinden können, was besonders verwirrend ist, kann ich versuchen, es auszuarbeiten. – ChaosPandion

+0

Ich verstehe nicht den RepositoryBuilder, was sind all diese Mitglieder wie Bind für und wo werden sie verwendet? Dann wird das let addPeople = rep {...} direkt auf dem Repository ausgeführt? – yanta