valum-framework / valum

Web micro-framework written in Vala
https://valum-framework.readthedocs.io/en/latest/
GNU Lesser General Public License v3.0
225 stars 23 forks source link

Support for a more structured OO API? #160

Closed Bob131 closed 8 years ago

Bob131 commented 8 years ago

Hi again, I was just checking out all the new shiny stuff that's been added since I last had a peek, and I have to say I'm actually quite excited by the progress you've made. I've not had enough time with the docs to properly grok all the bits and bobs, but from my quick peruse it looks fantastic.

That said, there's a feature I'd be quite interested in. As far as I can see, currently if one so wished they could subclass the Router class, add their own functions and call this.get() etc in the construct block to try and move logic out of main() and into it's own discreet class. If this behaviour already works, could this become an official and documented way to use the framework? Perhaps further down the line some introspection magic could be added to automatically register routes based on declared functions (if that's even possible).

I don't think this would confer any technical benefit, but I feel like officially supporting this method of usage would enable applications leveraging the framework to be more Vala-esque (or at least according to my understanding of "the Vala style"). In any case, it would be an additional style in the API consumer's toolbox from which they can choose depending on taste ;)

arteymix commented 8 years ago

I've been working on a few things: I try to converge the features into a release for this summer. Right now, I'm working on a formal rule parser to reverse them!

I don't see anything that would prevent anyone from sub-classing Router. It would actually be a very good approach to solve structural complexity. It's worthy of an example and documentation recipe!

Valum supports the concept of subrouting, which can be used to assemble various routers under a single one serving requests.

var app = new Router ();
app.rule (Method.ALL, "user/*", new UserRouter ().handle);
new Server ("org.valum.example.Subrouting", app.handle);

In short, you would have something like:

public class UserRouter : Router {

    construct {
        rule ("user/<int:id>(/<method>)?", setup).then ((req, res, next, ctx) => {
            switch (ctx["method"]) {
                case null:
                    view (req, res, next, ctx);
                    break;
                case "delete"
                    delete (req, res, next, ctx);
                    break;
                 default:
                     throw new ClientError.NOT_FOUND ("method '%s' not defined", 
                                                      ctx["method"]);
            }
        }).then (teardown);
    }

    public void setup (Request req, Response res, NextCallback next, Context ctx) {
        // verify rights
        next (req, res);
    }

    public void view (Request req, Response res, NextCallback next, Context ctx) {
         // work here!
        next (req, res);   
    }

    public void new @delete (Request req, Response res, NextCallback next, Context ctx) {
         // work here!
        next (req, res);   
    }

    public void teardown (Request req, Response res, NextCallback next, Context ctx) {
        // (render or handle status codes)
    }
}

I like it!

What I think is that this is just what people need. It's flexible on all possible aspects. The rest of the answer is more hypothetical and would probably be a basis for a derived project.

GObject Introspection is surely powerful enough to automate the binding work (see template-glib, it's able to resolve method).

However, such a feature (namely a controller layer) would imply that we would enforce some conventions, which is not in line with Valum.

Also, this would be useful only for user interaction. APIs have other requirements: work is delegated given on possible manipulations of a resource.

You could even go one step further: resolve the controller using GType, so your application would just be a set of class definitions with a middleware that handle all the resolution automatically.

As said, this would be a quite complex piece of code, but ultimately very useful.

Bob131 commented 8 years ago

this would require a custom annotation so your application would just be a set of class definitions with a middleware that handle all the resolution automatically.

That's exactly what I had in mind. I'll be honest though, besides those bits I don't really follow what you're saying (forgive me, I'm a bit scatterbrained at the moment). Are you saying that code for building introspection-based routes don't belong in Valum proper but rather in a separate middleware? If so, I could give it a tentative attempt.

Could you elaborate further re GType controller resolution? That part in particular made me go whoosh

arteymix commented 8 years ago

In short: use GLib.Type.from_name to resolve the controller class and GObject Introspection to figure out the method to call.

Introspection capabilities are wide http://valadoc.org/#!wiki=gobject-introspection-1.0/index

Here's an incomplete sample: https://gist.github.com/arteymix/0f3acedc8d6e08d4cbf9

Effectively, such code should end up in a separate derived work, because it targets a specific use case. I would suggest that you first use it in a project and then bundle the code whenever it's ready.

Look into Kohana's routing, the approach used there is really close to your expectations.

If you would design an API endpoint and view URIs as resource identifiers, the approach would be much different (it would be method-oriented):

public class UserRouter {
    construct {
        get ("", view);
        rule (Method.POST | Method.PUT, register);
        delete ("", ban);
    }
}

Hope that answers your questions.

By the way, I'm working on generating typelibs #137 for Python and JavaScript (Gjs) bindings!

arteymix commented 8 years ago

Okay, let's say we:

By the way, docs are now hosted at docs.valum-framework.org :)

arteymix commented 8 years ago

Okay, hope you're satisfied @Bob131!