Шаблон репозитория в F # - PullRequest
       2

Шаблон репозитория в F #

12 голосов
/ 29 ноября 2010

Я работаю над прототипом для использования базы данных документов (в настоящее время MongoDB, возможно, изменится) и нашел драйверы .NET немного болезненными, поэтому я решил абстрагировать доступ к данным с помощью шаблона Repository.Это должно облегчить замену того драйвера, который я сейчас использую (NoRM, mongodb-csharp, simple-mongob), на ваш драйвер killer f # mongodb, который не сосет , когда он будет готов.

Мой вопрос касается операции Добавить .Это будет иметь некоторые побочные эффекты для базы данных, и, следовательно, последующие вызовы All будут другими.Должен ли я заботиться?В C # традиционно я бы этого не делал, но я чувствую, что в F # я должен.

Вот общий интерфейс репозитория:

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

А вот как выглядит реализация 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

Во всем приложении я буду использовать IRepository, что позволит легко менять драйверы и, возможно, базы данных.

Вызов Все в порядке, но с добавлением, что я надеялся, было вместо возврата модуля, returnновый экземпляр репозитория.Что-то вроде:

        // 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>

Проблема в том, что если я вызываю Get, затем Add, исходный репозиторий по-прежнему возвращает все документы.Пример:

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

В идеале я хочу, чтобы длина a1 и a2 была разной, но они одинаковы, поскольку они оба попадают в базу данных.Приложение работает, пользователи могут задать свой вопрос, но программисту задается вопросом, почему он возвращает новый IRepository.

Так что я должен пытаться справиться с побочным эффектом от Add для базы данных при разработкетипы?Как другие поступили бы по этому поводу, используете ли вы репозиторий или какой-то подобный интерфейсный класс или у вас есть какой-то лучший функциональный подход?

Ответы [ 3 ]

4 голосов
/ 29 ноября 2010

Похоже, вы применяете неизменность к функциям, которые влияют на состояние во внешнем мире. Независимо от реализации F #, как бы вы это увидели на уровне MongoDB? Как бы вы помешали repo1 увидеть какие-либо изменения, которые вносит repo2? Что произойдет, если какой-либо другой процесс повлияет на базу данных - изменится ли repo1 и repo2 в этом случае?

Другими словами, представьте себе реализацию System.Console, которая работает следующим образом. Если Console.Out.WriteLine всегда возвращает новый неизменный объект, как он будет взаимодействовать с вызовами Console.In.ReadLine?

Edit tl; dr: Не делайте этого. Иногда побочные эффекты в порядке.

2 голосов
/ 29 ноября 2010

Я не думаю, что имеет смысл иметь неизменяемый интерфейс для изначально изменяемого типа (такого как база данных). Однако вы можете разделить функциональность на изменяемый тип базы данных (в вашем случае IRepository<'a>) и неизменный набор изменений (например, ChangeSet<'a>). Результат может выглядеть примерно так:

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()
0 голосов
/ 29 ноября 2010

Вы можете заключить его в вычислительное выражение, чтобы оно выглядело чистым. Вы можете даже расширить его с помощью кода для обработки таймаутов и отключенных серверов. Я новичок в этой концепции, поэтому, если бы эксперты могли обучить меня, если что-то кажется неуместным.

Я думаю, что эта концепция была бы гораздо более полезной, если бы вы работали не только с хранилищем, но я хотел, чтобы это было просто.

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 }) 
}  
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...