2010-11-29 13 views
11

Estoy trabajando en un prototipo para usar una base de datos documental (actualmente MongoDB, puede cambiar) y encontré los controladores .NET un poco molestos, así que pensé en abstraer el acceso a los datos con el Repositorio patrón. Esto debería facilitar el intercambio del controlador que estoy usando ahora (NoRM, mongodb-csharp, simple-mongob) con tu controlador f # mongodb asesino que no absorbe cuando está listo.Patrón de repositorio en F #

Mi pregunta es sobre el Operación Agregar. Esto tendrá un efecto secundario en la base de datos y, por lo tanto, las llamadas subsiguientes a Todas serán diferentes. ¿Debería importarme? En C# tradicionalmente no lo haría, pero siento que en F # debería.

Aquí es la interfaz de repositorio genérico:

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 

Y aquí es cómo una aplicación MongoDB se ve:

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 

A lo largo de la aplicación voy a utilizar IRepository, por lo que es fácil cambiar los conductores y potencialmente bases de datos.

Llamando Todo está bien, pero con Agregar lo que esperaba era en lugar de devolver la unidad, devuelva una nueva instancia de repositorio. Algo así como:

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

El problema es que si llamo Obtener, a continuación, Añadir, el repositorio original todavía devuelve todos los documentos. Ejemplo:

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

Idealmente quiero longitud de a1 y a2 a ser diferente, pero que son los mismos que los dos se golpean la base de datos. La aplicación funciona, los usuarios pueden hacer su pregunta, pero el programador se pregunta por qué devuelve un nuevo IRepository.

¿Debo tratar de manejar el efecto secundario de Añadir en la base de datos en el diseño de los tipos? ¿Cómo podrían otros hacer esto? ¿Usan un Repositorio o alguna clase de interfaz como esta o tienen un enfoque funcional mejor?

+0

De lo que estás hablando, MongoDB no es compatible, pero la idea sigue siendo interesante, y existe al menos una implementación. Eche un vistazo a http://www.datomic.com/. –

Respuesta

4

Parece que aplica la inmutabilidad a funciones que afectan el estado en el mundo exterior. Independientemente de la implementación de F #, ¿cómo verías esto funcionando en el nivel de MongoDB? ¿Cómo evitaría que repo1 viera los cambios que hace repo2? ¿Qué sucede si algún otro proceso afecta la base de datos? ¿Cambia ambos repo1 y repo2 en este caso?

Para decirlo de otra manera, imagina una implementación de System.Console que funcionó así. Si Console.Out.WriteLine siempre devolviera un nuevo objeto inmutable, ¿cómo interactuaría con las llamadas al Console.In.ReadLine?

Editar tl; dr: No haga esto. A veces los efectos secundarios están bien.

+0

+1 Estoy de acuerdo con Tim. En resumen: no, devolver un nuevo IRepository en Add no tiene sentido aquí. –

2

No creo que tenga sentido tener una interfaz inmutable para un tipo innatamente mutable (como una base de datos). Sin embargo, es posible que desee dividir la funcionalidad en un tipo de base de datos mutable (IRepository<'a> en su caso) y un conjunto inmutable de cambios (como ChangeSet<'a>, por ejemplo).El resultado podría ser algo así como:

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

Aquí un 'Repositorio' representa el registro de transacciones de la base de datos en un punto en el tiempo, y' ChangeSet' representa una transacción. Esto es genial, pero no tengo idea de cómo se correlacionaría con algún servidor de base de datos subyacente. –

+0

Interesante, ¿este patrón ChangeSet and ApplyChanges es el de Unidad de trabajo? – yanta

+0

@yanta - sí, esencialmente. El patrón generalmente se presenta con una unidad de trabajo mutable, pero no hay ninguna razón por la que deba serlo. – kvb

0

Podría envolverlo en una expresión computacional para que parezca puro. Incluso podría extenderlo aún más con el código para gestionar los tiempos de espera y los servidores caídos. Soy nuevo en el concepto, por lo que si los expertos me pueden enseñar si algo parece inapropiado.

Creo que este concepto sería mucho más útil si estuvieras leyendo algo más que un repositorio, pero quería hacerlo simple.

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

Seré honesto, no sigo esto, pero echaré un vistazo a las expresiones computacionales y espero poder volver a esto. – yanta

+0

@yanta - Si puede identificar qué es particularmente confuso, puedo intentarlo y elaborarlo. – ChaosPandion

+0

No entiendo RepositoryBuilder, ¿a qué se refieren todos los miembros como Bind for y dónde se usan? Entonces, deja que addPeople = rep {...} se ejecute realmente en el repositorio de inmediato? – yanta

Cuestiones relacionadas