Coder Social home page Coder Social logo

billotei / scalligraph Goto Github PK

View Code? Open in Web Editor NEW

This project forked from thehive-project/scalligraph

0.0 2.0 0.0 742 KB

Scala Framework for web applications using graph database

License: GNU Affero General Public License v3.0

Scala 95.14% Shell 4.86%

scalligraph's Introduction

ScalliGraph is a framework for web applications using graph database.

Goals and features

  • Reduce boilerplate code as much as possible.
  • gremlin DSL is used to access the database. Application doesn't require code specific to the database engine.
  • type safe
  • Database schema generation
  • GraphQL

How to use scalligraph

Add Scalligraph in your build dependency

Currently, there is no official release of ScalliGraph. You can wait the first release and add the dependency in your build file:

libraryDependencies += "org.thehive-project" %% "scalligraph" % "0.1.0"

or use ScalliGraph sources in your project:

lazy val scalligraph = (project in file("path/to/scalligraph"))
  .settings(name := "scalligraph")
lazy val myApplication = (project in file("."))
  .dependsOn(scalligraph)

ScalliGraph uses macros to reduce boilerplate code. The macro paradise compiler plugins must be enabled:

addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.full)

Define your data model

Database schema is done by defining case classes and by annotate them with @VertexEntity for vertex or @EdgeEntity[From, To] for edge. ScalliGraph inspects these classes and generate database schema and CRUD methods.

import org.thp.scalligraph.models.{EdgeEntity, VertexEntity}

@VertexEntity
case class Person(name: String, age: Int)

@VertexEntity
case class Software(name: String, lang: String)

@EdgeEntity[Person, Person]
case class Knows(weight: Double)

@EdgeEntity[Person, Software]
case class Created(weight: Double)

The recognized types for model fields are String, Long, Int, Date, Boolean, Double, Float and JsObject. Field can be Option and Seq of theses types.

If it is not enough, you can create your own mapping by add implicit UniMapping value of annotate the field with @WithMapping

Define your service layer

For each entity (vertex and edge) you may have a service class that defines what you can do with: CRUD. It also has a traversal to query your entities using Gremlin DSL.

Default service class already defines these methods. You can of course override them.

class PersonSrv(implicit db: Database) extends VertexSrv[Person] {
  // Add business operations on Person 
  override def steps(implicit graph: Graph): PersonSteps = new PersonSteps(graph.V.hasLabel(model.label))
}

Create method accepts model class and returns the same class with Entity trait. This trait contains meta data, common to persisted vertex and edge: _id, _createdAt, _createdBy, _updatedAt, _updatedBy.

The steps method returns a Gremlin traversal which can be enriched. ScalliGrah uses gremlin-scala. You can have more details on how to write query on gremlin-scala home page.

@EntitySteps[Person]
class PersonSteps(raw: GremlinScala[Vertex])(implicit db: Database) extends BaseVertexSteps[Person, PersonSteps](raw) {
  def created = new SoftwareSteps(raw.out("Created"))

  def knownPerson: List[Person] = raw.out("Knows").toList
}

With the annotation @EntitySteps, ScalliGraph add a method for each field of your model which returns a traversal of that field value. personSteps.age.max.head returns the age of the oldest person.

Create your controllers

A controller method consists of extracting data from HTTP request, check user permissions, call service layer and marshall the result.

ScalliGraph offers DSL to build a controller:

  apiMethod("create a person")
    .extract('person, FieldsParser[Person]) // Extract person from HTTP request
    .extract('friends, FieldsParser[String].sequence.on("friends")) // Extract a string under the name "friends"
    .requires(Permissions.write) { implicit request  // Check user authentication and verify if (s)he has the write permission
      // request is the HTTP request (play.api.mvc.Request) with authentication information (AuthContext)
      
      db.transaction { implicit graph  // Start a new transaction
        val person  = request.body('person) // retrive the extracted data from the HTTP request
        // Note that the type of person is the case class Person
        val friends = request.body('friends) // Seq[String]
        val createdPerson = personSrv.create(person)
        friends
          .map(personSrv.get) // get person from id
          .foreach(person  knowsSrv.create(Knows(1), createdPerson, person)) // then create edges 
        Results.Created
      }
    }

More details will come ...

Query controller

Data is requested using a query chain. In your application, you can describe all possible links which must a subclass of ParamQuery. The object Query contains convenient method to create ParamQuery.

You should also declare all public properties of your data. These properties are used to build filter queries, sort queries and GraphQL schema.

Links and public properties are put in a QueryExecutor. The QueryExecutor is able to parse and execute a query from a HTTP request.

class ModernQueryExecutor extends QueryExecutor {
  val personSrv   = new PersonSrv
  val softwareSrv = new SoftwareSrv

  override val publicProperties =
    PublicPropertyListBuilder[PersonSteps, Vertex]
      .property[String]("createdBy").derived(_  _.value[String]("_createdBy"))
      .property[String]("label").derived(_  _.value[String]("name").map("Mister " + _))
      .property[String]("name").simple
      .property[Int]("age").simple
      .build :::
    PublicPropertyListBuilder[SoftwareSteps, Vertex]
      .property[String]("createdBy").derived(_  _.value[String]("_createdBy"))
      .property[String]("name").simple
      .property[String]("lang").simple
      .property[String]("any")
      .seq(_ 
        Seq(
          _.value[String]("_createdBy"),
          _.value[String]("name"),
          _.value[String]("lang")
      ))
      .build

  override val queries = Seq(
    Query.init[PersonSteps]("allPeople", (graph, _) => personSrv.initSteps(graph)),
    Query.init[SoftwareSteps]("allSoftware", (graph, _) => softwareSrv.initSteps(graph)),
    Query.initWithParam[SeniorAgeThreshold, PersonSteps]("seniorPeople", { (seniorAgeThreshold, graph, _) 
      personSrv.initSteps(graph).where(_.has(Key[Int]("age"), P.gte(seniorAgeThreshold.age)))
    }),
    Query[PersonSteps, SoftwareSteps]("created", (personSteps, _) => personSteps.created),
    Query.withParam[FriendLevel, PersonSteps, PersonSteps]("friends", (friendLevel, personSteps, _)  personSteps.friends(friendLevel.level)),
    Query[Person with Entity, Output[OutputPerson]]("output", (person, _)  person),
    Query[Software with Entity, Output[OutputSoftware]]("output", (software, _)  software)
  )

Once described, query can be parsed from HTTP request then it can be executed

db.transaction { graph 
  val query: Query Or Every[AttributeError] = modernQueryExecutor.parser(Field(request))
  val result: JsValue = modernQueryExecutor.execute(query, graph, authContext).toJson
}

HTTP request body is a list of query elements:

{
  "query": [
    { "_name": "allPeople" },
    { "_name": "filter", "_and": [
      { "_lt": { "age": 30 } },
      { "_contains": { "name": "a" } }
    ]},
    { "_name": "created" },
    { "_name": "_toList" }
  ]
}

GraphQL

From a QueryExecutor, Scalligraph can generate the related GraphQL schema and execute the query:

import sangria.schema.{Schema, }
import sangria.parser.QueryParser

val query: Document                 = QueryParser.parse(inputQueryString).get
val schema: Schema[AuthGraph, Unit] = SchemaGenerator(modernQueryExecutor)
val result: Future[JsValue]         = Executor.execute(schema, query, AuthGraph(Some(authContext), graph))

scalligraph's People

Contributors

billotei avatar to-om avatar

Watchers

 avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.