trailsjs / trails

:evergreen_tree: Modern Web Application Framework for Node.js.
http://trailsjs.io
Other
1.66k stars 70 forks source link

standardized models API #94

Open jaumard opened 8 years ago

jaumard commented 8 years ago

For same as controller/policies (https://github.com/trailsjs/trails/issues/89) it would be nice to have a standardized API for trailpack who want include models

tjwebb commented 8 years ago

I think this would be much harder to get working well. For example, compare the waterline model definition to knex: https://github.com/trailsjs/trailpack-knex#models. A lot of translation work would be required there, with conditions for every kind of attribute and so forth.

jaumard commented 8 years ago

I understand that will not be easy but we will need this for trailpack-auth/permissions :/ or those will work only with waterline :/.

What I'm thinking right now, maybe we can just do something like (for a start) :

module.exports = MyModel extends Model{
 static waterlineSchema(){

 }

static knexSchema(){

}
}

And Trails take the good schema method, if schema is not present Trails can say to the user this Trailspack is not compatible with your ORM.

tjwebb commented 8 years ago

but we will need this for trailpack-auth/permissions :/ or those will work only with waterline :/.

Ok, I see what you're saying now. I wanted to avoid this, but I think there's no way around a single ORM definition as well that we can map to and from. Between knex/bookshelf, sequelize, and waterline, Waterline has the most "vanilla" schema definitions. By that, I mean that it's entirely plain js objects. This is actually why I chose hapi for the route definitions -- because hapi routes are simply js objects.

As long as waterline syntax is able to represent everything we need in a schema, I think we can use Waterline as the standard "trails" model definition, and extend it where needed. Luckily there's a pretty good start for mapping Waterline<-->Knex here: https://github.com/waterlinejs/postgresql-adapter/blob/master/lib/util.js

Similar to our other discussion about the webserver layer, each datastore trailpack that wants to support this interface is in charge of implementing it. And it's up to us (the framework designers) to devise a spec and a set of tests that trailpack developers can use to verify their implementations.

Feedback?

jaumard commented 8 years ago

Sounds very good to me :) Waterline<-->Knex look promising but yeah not easy to translate one to the other ^^ 100% agree for doing same as webserver layer ! That's the best options (in my opinion). And yeah tests for verify trailpack implementations are a good thing for both datastore and webserver.

jaumard commented 8 years ago

@tjwebb I just think about another way to make this available more quickly than the above solution. Can we give the possibility to trailpacks to have models definition for each ORM they want to support ?

But it seems a long way for doing the same for ORM because models definitions are not the same at all... So maybe we can think of a way to have multiple definition for the same models and the right one it used depends on witch orm is installed. Can be quite simple to implement and then when knex and bookshelf can map waterline definition switch to only one (or keep same usage...).

It can be done like this for trailpack controllers too cause now trailpack-router map directly handlers under routes so we can't know if they are for hapi API or Express API...

Any feedback on this ?

jaumard commented 8 years ago

Few solution to implement what I'm saying :

Solution 1) : Duplicate model definitions We can allow multiple schema methods to support multiple ORM.

/**
 * @module User
 * @description User model for basic auth
 */
module.exports = class User extends Model {

  static config() {

  }

  static schema() {
    return {
      username: {
        type: 'string',
        unique: true
      },
      email: {
        type: 'email',
        unique: true
      }
    }
  }
static schemaBookshelf(table) {
    //table definition for migrate='drop'
    if (table) {
      table.increments('id').primary();
      table.string('name').notNullable();
      return       
    } else {
      // booskelf model prototypeProperties   
      return {
        profile() {
          return this.hasOne('profile');
        }
      }
    } 
  }
}

Solution 2) : Duplicate model classes Each ORM will have to include a model classes ModelBookshelf ModelWaterline... : For waterline :

const Model = require('trails-model')
module.exports = class User extends Model {

  static config() {

  }

  static schema() {
    return {
      username: {
        type: 'string',
        unique: true
      },
      email: {
        type: 'email',
        unique: true
      }
    }
  }
}

For bookshelf :

const Model = require('node_modules/trailpack-bookshelf/models/ModelBookshelf')
// api/models/User.js
class User extends Model {
  static schema(table) {
    //table definition for migrate='drop'
    if (table) {
      table.increments('id').primary();
      table.string('name').notNullable();
      return       
    } else {
      // booskelf model prototypeProperties   
      return {
        profile() {
          return this.hasOne('profile');
        }
      }
    } 
  }
}

For the index.js :

exports.User = [require('./User'), require('./UserBookshelf')]

Now Trails will 'only' have to pick the right models definition based on ORM installed and models super classe.

I prefer the solution 2 that look more clean to me. Once again this will be useful only for Trailpacks, for Trails project user will simply have to use the trails-model only.

jaumard commented 8 years ago

Little up here :) because I think it's important to prevent trailpacks fragmentation, some will work only with one ORM some others will work one WebServer only... not really good

andersea commented 7 years ago

I think if a trailpack wants to support multiple orms and multiple web servers, it should be the trailpacks responsibility to abstract away api differences.

For example in the python flask framework, the security plugin flask security supports three different orms. The user just have to follow certain minimum rules for which attributes the security models must contain and how relationships are set up.

jaumard commented 7 years ago

@andersea that doesn't prevent trails to offer an interface that trailpack will implement in order to make things easy :) I implement multi ORM support in one of my trailpack already and it can be painful actually depending of the complexity of the trailpack. Also with a GrahQL model representation we can be uniform with all ORM as the orm trailpack will do the transformation to native model

scott-wyatt commented 7 years ago

I agree @jaumard, I think this is what the new resolvers are meant to do.

scott-wyatt commented 6 years ago

So I have some thoughts on this after spending some time writing a bunch of trailpacks and particularly one that focuses on generic apis for 3rd party services.

By that, I mean that it's entirely plain js objects. This is actually why I chose hapi for the route definitions -- because hapi routes are simply js objects. -- @tjwebb

There are a few basics that all ORMs follow, and we've identified at least 2 of them: Schema, Config. I think we are missing Datatypes and Associations. I would think it would be possible for each ORM trailpack to define it's datatypes. For example, this is how sequelize, bookshelf, mongoose, etc. treats a string, boolen, json, etc, and how these ORMs write associations etc.

The "Brass Tax" is that there is no way for us to completely cover all use cases or cool specific tools for each ORM. So I agree with @jaumard's concept:

I prefer the solution 2 that look more clean to me. Once again this will be useful only for Trailpacks, for Trails project user will simply have to use the trails-model only.

This got me thinking about Sequelize which has some awesome capabilities with it's Class Methods and Instance Methods and returning DAO instances.

So here's what I think. Models could become DAO instances with generic class methods for: find, findOne, findAll, and generic instance methods for: save, update, destroy etc. (we kind of do this with footprints, but it's really slow, and this could still work with the Resolver's concept coming in v3). The DAO could inherit a generic schema and generic config, and ORM trailpacks could specify how they handle those generics. Community Trailpacks could write with the DAO in mind, and user specific trailpacks could extend/modify the DAO.

This would for the most part eliminate the need for each trailpack creator to write out individual schemas for each ORM and instead allow for more community involvement in maintaining ORM trailpacks.

TLDR;

Make Models generic, extendable, DAO instances that have Class and Instance methods defined by ORM trailpacks.

Pros:

Cons: