30’ : Create new ReactiveMongo AutoSource Controller in app/Person.scala
123456789101112131415161718192021
packagecontrollersimportplay.api._importplay.api.mvc._// BORING IMPORTS// Jsonimportplay.api.libs.json._importplay.api.libs.functional.syntax._// Reactive JSONCollectionimportplay.modules.reactivemongo.json.collection.JSONCollection// Autosourceimportplay.autosource.reactivemongo._// AutoSource is Async so imports Scala Future implicitsimportscala.concurrent.ExecutionContext.Implicits.globalimportplay.api.Play.current// >>> THE IMPORTANT PART <<<objectPersonsextendsReactiveMongoAutoSourceController[JsObject]{valcoll=db.collection[JSONCollection]("persons")}
50’ : Add AutoSource routes at beginning conf/routes
1
->/personcontrollers.Persons
60’ : Create conf/play.plugins to initialize ReactiveMongo Plugin
With Play-Autosource, in a few lines, you obtain :
A backed abstract datasource (here implemented for ReactiveMongo but it could be implemented for other DBs)
All CRUD operations are exposed as pure REST services
The datasource is typesafe (here JsObject but we’ll show later that we can use any type)
It can be useful to kickstart any application in which you’re going to work iteratively on our data models in direct interaction with front-end.
It could also be useful to Frontend developers who need to bootstrap frontend code with Play Framework application backend. With Autosource, they don’t have to care about modelizing strictly a datasource on server-side and can dig into their client-side code quite quickly.
Adding constraints & validation
Now you tell me: “Hey that’s stupid, you store directly JsObject but my data are structured and must be validated before inserting them”
Yes you’re right so let’s add some type constraints on our data:
12345678910
objectPersonsextendsReactiveMongoAutoSourceController[JsObject]{valcoll=db.collection[JSONCollection]("persons")// we validate the received Json as JsObject because the autosource type is JsObject// and we add classic validations on typesoverridevalreader=__.read[JsObject]keepAnd((__\"name").read[String]and(__\"age").read[Int](Reads.min(0)keepAndReads.max(117))).tupled}
You can add progressively constraints on your data in a few lines. With AutoSource, you don’t need to determine immediately the exact shape of your models and you can work with JsObject directly as long as you need. Sometimes, you’ll even discover that you don’t even need a structured model and JsObject will be enough. (but I also advise to design a bit things before implementing ;))
Keep in mind that our sample is based on an implementation for ReactiveMongo so using Json is natural. For other DB, other data structure might be more idiomatic…
Use typesafe models
Now you tell me: “Funny but but but JsObject is evil because it’s not strict enough. I’m a OO developer (maybe abused by ORM gurus when I was young) and my models are case-classes…”
Yes you’re right, sometimes, you need more business logic or you want to separate concerns very strictly and your model will be shaped as case-classes.
So let’s replace our nice little JsObject by a more serious case class.
1234567891011
// the modelcaseclassPerson(name:String,age:Int)objectPerson{// the famous Json Macro which generates at compile-time a Reads[Person] in a one-linerimplicitvalfmt=Json.format[Person]}// The autosource... shorter than beforeobjectPersonsextendsReactiveMongoAutoSourceController[Person]{valcoll=db.collection[JSONCollection]("persons")}
Please note that I removed the validations I had introduced before because there are not useful anymore: using Json macros, I created an implicit Format[Person] which is used implicitly by AutoSource.
So, now you can see why I consider AutoSource as a typesafe datasource.
Let’s be front-sexy with AngularJS
You all know that AngularJS is the new kid on the block and that you must use it if you want to be sexy nowadays.
I’m already sexy so I must be able to use it without understanding anything to it and that’s exactly what I’ve done: in 30mn without knowing anything about Angular (but a few concepts), I wrote a dumb CRUD front page plugged on my wonderful AutoSource.
Client DS in app/assets/javascripts/persons.js
This is the most important part of this sample: we need to call our CRUD autosource endpoints from angularJS.
We are going to use Angular resources for it even if it’s not really the best feature of AngularJS. Anyway, in a few lines, it works pretty well in my raw case.
(thanks to Paul Dijou for reviewing this code because I repeat I don’t know angularJS at all and I wrote this in 20mn without trying to understand anything :D)
varapp=// injects ngResourceangular.module("app",["ngResource"])// creates the Person factory backed by our autosource// Please remark the url person/:id which will use transparently our CRUD AutoSource endpoints.factory('Person',["$resource",function($resource){return$resource('person/:id',{"id":"@id"});}])// creates a controller.controller("PersonCtrl",["$scope","Person",function($scope,Person){$scope.createForm={};// retrieves all persons$scope.persons=Person.query();// creates a person using createForm and refreshes list$scope.create=function(){varperson=newPerson({name:$scope.createForm.name,age:$scope.createForm.age});person.$save(function(){$scope.createForm={};$scope.persons=Person.query();})}// removes a person and refreshes list$scope.remove=function(person){person.$remove(function(){$scope.persons=Person.query();})}// updates a person and refreshes list$scope.update=function(person){person.$save(function(){$scope.persons=Person.query();})}}]);
CRUD UI in index.scala.html
Now let’s create our CRUD UI page using angular directives. We need to be able to:
list persons
update/delete each person
create new persons
123456789101112131415161718192021222324
@(message: String)
@main("Welcome to Play 2.1") {
<divng-controller="PersonCtrl"><!-- create form --><labelfor="name">name:</label><inputng-model="createForm.name"/><labelfor="age">age:</label><inputng-model="createForm.age"type="number"/><buttonng-click="create()">Create new person</button><hr/><!-- List of persons with update/delete buttons --><table><thead><th>name</th><th>age</th><td>actions</td></thead><tbodyng-repeat="person in persons"><tr><td><inputng-model="person.name"/></td><td><inputtype="number"ng-model="person.age"/></td><td><buttonng-click="update(person)">Update</button><buttonng-click="remove(person)">Delete</button></td></tr></tbody></div></div>}
Import Angular in main.scala.html
We need to import angularjs in our application and create angular application using ng-app
I know what you think: “Uhuh, the poor guy who exposes his DB directly on the network and who is able to delete everything without any security”
Once again, you’re right. (yes I know I love flattery)
Autosource is by default not secured in any way and actually I don’t really care about security because this is your job to secure your exposed APIs and there are so many ways to secure services that I prefer to let you choose the one you want.
Anyway, I’m a nice boy and I’m going to show you how you could secure the DELETE endpoint using the authentication action composition sample given in Play Framework documentation.
// FAKE USER class to simulate a user extracted from DB.caseclassUser(name:String)objectUser{deffind(name:String)=Some(User(name))}objectPersonsextendsReactiveMongoAutoSourceController[Person]{// The action composite directly copied for PlayFramework docdefAuthenticated(action:User=>EssentialAction):EssentialAction={// Let's define a helper function to retrieve a UserdefgetUser(request:RequestHeader):Option[User]={request.session.get("user").flatMap(u=>User.find(u))}// Now let's define the new ActionEssentialAction{request=>getUser(request).map(u=>action(u)(request)).getOrElse{Done(Unauthorized)}}}valcoll=db.collection[JSONCollection]("persons")// >>> IMPORTANT PART <<<// We simply override the delete action// If authenticated, we call the original actionoverridedefdelete(id:BSONObjectID)=Authenticated{_=>super.delete(id)}defindex=Action{Ok(views.html.index("ok"))}// the login action which log any userdeflogin(name:String)=Action{Ok("logged in").withSession("user"->name)}// the logout action which log out any userdeflogout=Action{Ok("logged out").withNewSession}}
Nothing to complicated here.
If you need to add headers in your responses and params to querystring, it’s easy to wrap autosource actions. Please refer to Play Framework doc for more info…
I won’t try it here, the article is already too long but it should work…
Play-Autosource is DB agnostic
Play-Autosource Core is independent of the DB and provides Reactive (Async/Nonblocking) APIs to fulfill PlayFramework requirements.
Naturally this 1st implementation uses ReactiveMongo which is one of the best sample of DB reactive driver. MongoDB fits very well in this concept too because document records are really compliant to JSON datasources.
But other implementations for other DB can be done and I count on you people to contribute them.
DB implementation contributions are welcome (Play-Autosource is just Apache2 licensed) and AutoSource API are subject to evolutions if they appear to be erroneous.
Conclusion
Play-Autosource provides a very fast & lightweight way to create a REST CRUD typesafe datasource in your Play/Scala application. You can begin with blob data such as JsObject and then elaborate the model of your data progressively by adding constraints or types to it.
There would be many more things to say about Play/Autosource:
you can also override writers to change output format
you have some alpha streaming API also
etc…
There are also lots of features to improve/add because it’s still a very draft module.
If you like it and have ideas, don’t hesitate to discuss, to contribute, to improve etc…
curl -X POST -d "{ "coding" : "Have fun"} http://localhost:9000/developer