Pytanie Wzór repozytorium w F #


Pracuję nad prototypem, aby korzystać z bazy danych dokumentów (obecnie MongoDB, może się zmieniać) i znalazłem trochę kłopotów z sterownikami .NET, więc pomyślałem, że mogę uzyskać dostęp do danych za pomocą wzorca repozytorium. Powinno to ułatwić zamianę dowolnego sterownika, którego teraz używam (NoRM, mongodb-csharp, simple-mongob) z twój zabójca f # mongodb, który nie jest do niczego kiedy będzie gotowe.

Moje pytanie dotyczy Dodaj operacja. Będzie to miało jakiś wpływ na bazę danych, a więc kolejne wywołania Wszystko będzie inny. Powinienem się martwić? W języku C # tradycyjnie nie, ale czuję, że w F # powinienem.

Oto ogólny interfejs repozytorium:

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

Oto jak wygląda implementacja MongoDB:

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

W całej aplikacji będę korzystać z IRepository, co ułatwi zmianę sterowników i potencjalnie baz danych.

Calling All jest w porządku, ale z Addem, którego oczekiwałem, zamiast zwracania jednostki, zwróć nową instancję repozytorium. Coś jak:

        // Add has a side-effect of modifying the database
        // but who cares as we now return a new repository
        abstract member Add : 'a -> IRepository<'a>

Problem polega na tym, że jeśli zadzwonię Get, a następnie Add, oryginalne repozytorium nadal zwraca wszystkie dokumenty. Przykład:

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()

Idealnie chcę, aby długość a1 i a2 była inna, ale są one takie same, jak obie trafiały do ​​bazy danych. Aplikacja działa, użytkownicy mogą zadawać pytania, ale programista zastanawia się, dlaczego zwraca nowy IRepository.

Więc powinienem próbować obsługiwać efekt uboczny z Add on the database w projektowaniu typów? Jak inni mogliby to robić, czy używasz repozytorium lub takiej klasy interfejsu, jak to lub masz lepsze podejście funkcjonalne?


12
2017-11-29 16:40


pochodzenie


O czym mówisz, MongoDB nie obsługuje, ale pomysł jest nadal interesujący i istnieje przynajmniej jedno wdrożenie. Spojrzeć na datomic.com. - Fyodor Soikin


Odpowiedzi:


Wygląda na to, że stosujesz niezmienność dla funkcji wpływających na stan w zewnętrznym świecie. Niezależnie od implementacji F #, jak widziałbyś to działając na poziomie MongoDB? Jak mógłbyś zapobiec? repo1 widząc wszelkie zmiany, które repo2 sprawia, że? Co się stanie, jeśli jakiś inny proces wpłynie na bazę danych - wykonaj oba repo1 i repo2 zmienić w tym przypadku?

Mówiąc inaczej, wyobraź sobie implementację System.Console to działało w ten sposób. Gdyby Console.Out.WriteLine zawsze zwracał nowy niezmienny obiekt, w jaki sposób oddziałuje on z wywołaniami Console.In.ReadLine?

Edytuj tl; dr: Nie rób tego. Czasami efekty uboczne są w porządku.


4
2017-11-29 16:47



+1 Zgadzam się z Timem. W skrócie: nie, zwrócenie nowego IRepository w Add nie ma sensu. - Mauricio Scheffer


Nie sądzę, że sensowne jest posiadanie niezmiennego interfejsu z wewnętrznie zmiennym typem (takim jak baza danych). Jednak możesz podzielić funkcje na zmienne typy baz danych (IRepository<'a> w twoim przypadku) i niezmienny zestaw zmian (takich jak ChangeSet<'a>, na przykład). Wynik może wyglądać mniej więcej tak:

type ChangeSet<'a> = ...                         //'
module ChangeSet = begin                         //'
  let empty = ...                                //'
  let add a c = ...                              //'
  ...
end

type IRepository<'a> =                           //'
  abstract GetAll : unit -> seq<'a>              //'
  abstract ApplyChanges : ChangeSet<'a> -> unit  //'

type Repository<'a> = ...                        //'

let repo = new Repository<Question>(...)
let changes =
  ChangeSet.empty
  |> ChangeSet.add (Question "Repository pattern in F#")
  |> ChangeSet.add (Question "...")
repo.ApplyChanges changes
let results = repo.GetAll()

2
2017-11-29 17:08



Tutaj Repository reprezentuje dziennik transakcji bazy danych w określonym momencie, oraz ChangeSet reprezentuje transakcję. To jest bardzo fajne, ale nie mam pojęcia, jak to mapuje do jakiegoś bazowego serwera bazy danych. - Tim Robinson
Interesujące, czy jest to schemat ChangeSet i ApplyChanges? - yanta
@yanta - tak, zasadniczo. Wzorzec zazwyczaj przedstawia zmienną jednostkę pracy, ale nie ma żadnego powodu, aby tak się stało. - kvb


Można go zawrzeć w obliczeniowej ekspresji, aby wyglądała na czystą. Można nawet rozszerzyć go za pomocą kodu obsługującego limity czasu i znoszone serwery. Jestem nowy w koncepcji, więc jeśli eksperci mogą mnie nauczyć, jeśli coś wydaje się niewłaściwe.

Myślę, że ta koncepcja byłaby o wiele bardziej użyteczna, gdybyś przeglądał coś więcej niż tylko repozytorium, ale chciałem zachować prostotę.

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
2017-11-29 18:17



Będę szczery, nie podążam za tym, ale przyjrzę się obliczeniom obliczeniowym i mam nadzieję, że wrócę do tego. - yanta
@yanta - Jeśli potrafisz wskazać, co jest szczególnie mylące, mogę spróbować je opracować. - ChaosPandion
Nie rozumiem RepositoryBuilder, do czego są wszyscy członkowie jak Bind i gdzie są one używane? W takim razie niech addPeople = rep {...} jest rzeczywiście wykonywane w repozytorium od razu? - yanta