ceylon-cayla icon indicating copy to clipboard operation
ceylon-cayla copied to clipboard

Bind requests to handler methods instead of handler classes?

Open stefcl opened this issue 10 years ago • 11 comments

Salut Julien, This is Stéphane from the ceylon mailng list. I have had a quick look at cayla.I am not yet extremely familiar with the project so excuse me in advance if I have missed something or if my whole point is invalid.

The current approach requires us to use a separate Controller for every action, with the route annotation at class level.

route("/")
shared class Index() {
  shared actual default Response handle() => ok().body("<html><body>Hello World</body></html>");
}

Currently it's one action => one class, Will it be possible in the future to bind requests to methods returning Responses or Promises rather than classes (one action => one method)? The motivation behind this demand is that actions on a particular entity, for example the typical CRUD stuff :

/blog/index /blog/edit( id ) /blog/post /delete(id)

would certainly share some logic in common eg: security checks, data access services... so imho it would make sense to regroup them in the same class. I know one can probably achieve the same thing using controller inheritance but it doesn't sound that convenient. Btw if the controller instantiation could be delegated, it may make it trivial to implement DI.

stefcl avatar Nov 24 '13 08:11 stefcl

@stefcl IIRC, I think @vietj lets you write your actions as methods of a toplevel object.

gavinking avatar Nov 24 '13 17:11 gavinking

@stefcl

at the moment controllers are classes in order to be able to generate URL from them. (there is nothing wrong to have other kind of controllers in the future (like functions or methods) but it would not be possible to make URL generation work).

that being said you can till use inheritance and create intermediary abstract controllers:

abstract class BlogController() extends Controller() {
  // put some reusable logic here
}
route("/blog/index")
class class BlogIndex() extends BlogController() { ... }
....

(note: I haven't checked it works... but it should, if it should not => bug report)

there are also other kind of reuse via objects:

object blogControllers {
  // Put reusable logic here
  route("/blog/index")
  class BlogIndex() extends BlogController() { ... }
  ....
}

at the moment controllers are introspected at two levels:

  • top level controllers
  • in top level objects

I hope soon to extend controller discovery beyong that and even to be able to use route annotation on non controller elements, for example

route("/blog")
object blogControllers {
    route("/index")
    class BlogIndex() extends BlogControllers() { ... }
}

HTH

vietj avatar Nov 25 '13 00:11 vietj

So this issue remains open until it is implemented.

vietj avatar Nov 25 '13 00:11 vietj

@vietj ok nevermind, I was not aware of this limitation with method annotations, I still have some learning to do.

If you are open to supporting various kind of controllers/routers, I guess there may be other interesting options in the future. For example, depending on how you implement that "live mode" you mentioned on the list, I can easily imagine declaring routes in a file and having a build system generate strongly typed reverse routers usable on both server and client (via Ceylon for JS). Considering that manually building ajax URLs in pure javascript usually requires you to hardcode some parts of the path and prevent you from properly abstracting your URL scheme from the code, I see a nice benefit in generating ceylonJS routers. Well... just another idea thrown on the table, hope you don't mind but there is so much potential in a framework that takes advantage of both JVM and JS facets...

stefcl avatar Nov 25 '13 07:11 stefcl

I agree that the JS side is a fantastic opportunity to exploit. Concerning the route, it makes sense for me to be able to override the annotation routes with a configuration based (like in Play!) a bit like:

Application {
  container = `package myapp.server`;
  router = Route {
    // Route declarations
  };);

This way the Route declaration could also be a top level Ceylon object in a common module, reused then in server and client modules.

vietj avatar Nov 25 '13 17:11 vietj

I had the route file used by play 2.x! in mind. it looks like that :

# Home page
GET     /                       controllers.Application.index

# Tasks          
GET      /tasks                  controllers.Application.tasks()
POST    /tasks/add                  controllers.Application.newTask()
POST    /tasks/:id/delete       controllers.Application.deleteTask(id: Long)

A simple file where you declare your routes and the corresponding methods to call. Routers classes as well as reverse routers are generated by the the build system based on the content of this file. The generated code is placed in a managed source folder that gets included in the build and is directly available to be imported and used in your own classes, with full IDE completion and such. You can use the generated reverse router to find the url corresponding to the parameters :

String url = routes.Application.deleteTask(23);
//gives you url = "/tasks/23/delete"

Very safe and convenient to use... It also define priorities in case you'd have more than one matching route for a given URL. It can also generate javascript routers (jquery syntax) but it's not as convenient as it would be if these were in ceylon (and thus, type-checked by the ceylon->JS compiler, as well as user's client-side code relying on it) ;-).

However, unlike the system you are mentioning, it does not easily allow to redefine the URL scheme per-deployment using configuration (I personaly never had such requirement, but it may make sense nonetheless).

Again, it's just an idea, I am not telling you what it should be like (and wouldn't pretend to), I am just mentioning a system that I found working very well and that could open the road for strongly typed ceylonJVM and ceylonJS reverse routers.

stefcl avatar Nov 25 '13 18:11 stefcl

@stefcl I had actually that in mind more or less and I like this

The idea is that Application object is a reusable building block for an application.

An application can be written as a simple module(s) like it is today and declare routes in annotations

A runtime system build on top of the framework can load the config file Play like and generate the config object and then build the application with a daemon thread that scans of FS changes in config or in application and then generate / rebuild the application.

Cayla needs to be as simple to use as Play 1&2

vietj avatar Nov 25 '13 19:11 vietj

I really love the idea of using a background process to build the app and convert JS assets. In the case of play2, it works with SBT, ninja uses Maven... IIRC someone talked about contributing a build system in ceylon, I wonder if it could be interesting for cayla.

stefcl avatar Nov 25 '13 20:11 stefcl

IIRC someone talked about contributing a build system in ceylon

You mean ceylon.build?

gavinking avatar Nov 25 '13 20:11 gavinking

I looked a bit a few days ago and it seems appropriate!!!

you can reuse existing tasks or implement your own, that would buy extensibility for sure!

vietj avatar Nov 25 '13 23:11 vietj

@gavinking Yes that's probably ceylon.build, (btw It's a good surprise to see that many ceylon projects popping up).

I'll try to have a deeper look at ceylon.build, I can't really tell how advanced they are. (and to be honest, build systems were among the rare kind of software in which dynamic scripting languages such as groovy made sense to me... I may need to reconsider my opinions here :-) )

stefcl avatar Nov 26 '13 06:11 stefcl