Skip to content
This repository has been archived by the owner on Feb 27, 2023. It is now read-only.

Fable and breeze.js #62

Open
bilkusg opened this issue May 8, 2018 · 5 comments
Open

Fable and breeze.js #62

bilkusg opened this issue May 8, 2018 · 5 comments

Comments

@bilkusg
Copy link

bilkusg commented May 8, 2018

Has anyone looked at breeze.js or anything similar with a view to producing a browser-hosted ORM in F#.
It's easy enough to just use something like breeze with a minimal fable import wrapper, but ideally one would want to integrate it fully into the F# world, leverage strong typing &c, and that looks like an interesting and potentially worthwhile challenge.

For anyone interested, breeze.js is an opensource javascript library which provides an entity-framework like ORM in javascript, and gets/puts data via ajax calls to a backend. The backend can be an existing web service if you produce your own adapter, but it's very easy to produce a one page web app which can serve an EF.core model in just the form expected by breeze, letting you write EF like code in javascript in the browser.

I'm currently playing with using breeze lightly bound to fable as the data source for an Elmish front-end - and will see how that goes first. But if I'm reinventing the wheel here, or going down a dead-end, it would be good to know!

@MangelMaxime
Copy link
Member

I think you are the first to speak about breeze.js in the Fable community

@bilkusg
Copy link
Author

bilkusg commented May 12, 2018

I've done some playing with breeze now and it seems quite promising as a way of leveraging entity framework code in the browser. You can very easily set up a server to talk to the javascript code, and then interface to fable to write code which looks like:

let serviceName = "breeze/Accounting"
let manager =  breeze.EntityManager.Create (serviceName)

let p = promise {
            let query = breeze.EntityQuery.from("Commodities").take(10).where("Fullname",FilterQueryOp.StartsWith,!!filter)
            let query2 = Breeze.breeze.EntityQuery.from("Accounts")
            let! data = manager.executeQuery(query)
            let res = data.results 
            let! data2 = manager.executeQuery(query2)
            let res2 = data2.results
            return (res,res2)
        }

and when you run it, the promise will give you a couple of arrays of items you can map to the entities.
In due course one could write a suitable breeze { } query expression and get some really nice syntax.

However, there are a few things troubling me, which I'd be interested in any views from the wider Fable community about:

  • The F# server-side classes are concrete, but the natural way to map them to Fable makes them abstract. That makes it hard to use the same source file to describe the entities on both server and client
  • Obviously the entities are not immutable, and indeed the expected way to update the database is to mutate the entities willy-nilly before committing changes. So can I still include entities in an Elmish model, or am I asking for trouble.

I've done a few simple tests which seem to work OK, but for all I know I'm causing lots of bad stuff to happen below the surface in terms of either unnecessary extra rendering, or cases where changes might not be reflected on screen.

Any advice/suggestions welcome

@alfonsogarciacaro
Copy link
Member

Nice work here @bilkusg! I'm sure this can very valuable to the community. I'll try to answer your questions:

The F# server-side classes are concrete, but the natural way to map them to Fable makes them abstract. That makes it hard to use the same source file to describe the entities on both server and client.

Can you please give a code example about this to make sure I'm understanding you correctly?

Obviously the entities are not immutable, and indeed the expected way to update the database is to mutate the entities willy-nilly before committing changes. So can I still include entities in an Elmish model, or am I asking for trouble.

The usual trick with Fable for this is to use records. Fable 1 compiles them as classes and Fable 2 will use plain JS objects instead. But in both cases the record fields will just become standard mutable JS fields, so even if the compiler doesn't let you change the value in F# code, this will be possible at runtime by external libraries.

@bilkusg
Copy link
Author

bilkusg commented May 13, 2018

Thanks @alfonsogarciacaro
So a grossly simplified example of the difference between the client and server types looks like this

Server:

type Accounts() =
  member val Guid = "" with get, set
  member val Name = "" with get, set
  member val Description = "" with get, set

Client:

type Accounts = 
  abstract Guid:string with get,set
  abstract Name:string with get,set
  abstract Description:string with get,set

So my first question is essentially, if I use the same definition as on the server on the client should it work anyway ( and be future proof for Fable 2 ) or will something subtle go wrong.

My second question might be better asked as an Elmish issue - I'm not worried about how Fable mutates the objects, but how Elmish and its renderer will deal with a model containing such mutable components several levels below the top.

@alfonsogarciacaro
Copy link
Member

Thanks for the example @bilkusg, now I understand much better your question :)

So we have two different things here: a concrete type and an interface. Because Fable tries to make interaction with JS as seamless as possible, interfaces can be used to handle objects coming from external code (I guess Breeze is returning a POJO). However, it's not safe to do the same with F# classes because while Fable keeps the semantics, the actual representation in JS is an implementation detail and can change (as it will in Fable 2).

This is a bit tricky, in Fable 1 you probably can use inflate to construct a concrete type from a POJO. However, I'm not sure yet if this will work in Fable 2, because the way how concrete types compile is going to be very different (members won't be attached to the instance).

But in the case of records, we do try to make the type and the actual JS representation as close as possible. In fact in Fable 2, records will just be compiled as POJOs. You could try to still share types between server and client using records with the CLIMutable attribute, which also allows you to keep the type immutable in your code.

[<CLIMutable>]
type Accounts =
    { Guid: string
      Name: string
      Description: string }

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants