RejectedSoftware Forums

Sign up

Pages: 1 2

How to organize a large router and corresponding functions

Hi,

I am porting a personal project to vibe.D which is rather large. I have many routes, each with individual route functions. I've had the darndest time trying to split the routes and their functions into separate files.

Right now I am using the basic layout of vibelog:
https://github.com/rejectedsoftware/vibelog/blob/master/source/vibelog/vibelog.d

I have the route functions in separate files, and I create a View class which is full of utility functions that take up a lot of space and don't change much. I like this setup.

The issue I'm running into is - referring to vibelog - how can I pass the database connection m_db to route functions located outside of class VibeLog?

My current solution is to wrap each set of route functions in classes, and inherit-daisy-chain them down to my View class into class VibeLog : View, with View inheriting from UserRoutes, which inherits from MapRoutes, etc.. This looks bad.

Another good option might be if I could pass this vibelog to router.get, such that I could access vibelog.m_db.

Perhaps I could initialize m_db outside of class VibeLog and import it elsewhere.. but I don't like this, and I feel like this might interfere with fiber/thread safety.

Thanks,

Re: How to organize a large router and corresponding functions

Oh - or attaching m_db to req would be nice.

Re: How to organize a large router and corresponding functions

On Sun, 12 Jul 2015 09:18:21 GMT, Taylor Gronka wrote:

Hi,

I am porting a personal project to vibe.D which is rather large. I have many routes, each with individual route functions. I've had the darndest time trying to split the routes and their functions into separate files.

Right now I am using the basic layout of vibelog:
https://github.com/rejectedsoftware/vibelog/blob/master/source/vibelog/vibelog.d

I have the route functions in separate files, and I create a View class which is full of utility functions that take up a lot of space and don't change much. I like this setup.

The issue I'm running into is - referring to vibelog - how can I pass the database connection m_db to route functions located outside of class VibeLog?

My current solution is to wrap each set of route functions in classes, and inherit-daisy-chain them down to my View class into class VibeLog : View, with View inheriting from UserRoutes, which inherits from MapRoutes, etc.. This looks bad.

Another good option might be if I could pass this vibelog to router.get, such that I could access vibelog.m_db.

Perhaps I could initialize m_db outside of class VibeLog and import it elsewhere.. but I don't like this, and I feel like this might interfere with fiber/thread safety.

Thanks,

One way to pass additional arguments is to construct a delegate (using a lambda in this case):

import vibe.vibe;

shared static this()
{
  auto r = new URLRouter;
  r.get("/", (req, res) => hello(req, res, "42"));

  auto settings = new HTTPServerSettings;
  settings.port = 8080;
  settings.bindAddresses = ["::1", "127.0.0.1"];
  listenHTTP(settings, r);
}

void hello(HTTPServerRequest req, HTTPServerResponse res, string answer)
{
  res.writeBody("Answer: "~answer);
}

There is also the partial function in Phobos that should work for this, too.

If you know that your instance doesn't change over time (or per request), you can just use a global variable to store and access it (global variables are thread-local by default, so there will be no race-conditions). If you have data that changes per request, you can also make use of a global TaskLocal variable, which will be local to the current request (because each request is processed in a separate task):

TaskLocal!string s_answer;

shared static this() {
  // ...

  // for any request, first set the answer
  r.any("*", &setupAnswer);
  // then continue with the normal routes
  r.get("/", &hello);

  // ...
}

void setupAnswer(HTTPServerRequest req, HTTPServerResponse res)
{
  s_answer = "42";
}

void hello(HTTPServerRequest req, HTTPServerResponse res)
{
  res.writeBody("Answer: "~answer);
}

Personally I'm nowadays creating all of my routes implicitly with the vibe.web.web or vibe.web.rest interface generators (see http://vibed.org/docs#web for a short introduction). They avoid the boilerplate code needed to process the various request parameters, including extraction, conversion and validation, and can make the code a lot more concise and readable (as well as less error prone). They are based on a class structure, though, so in that case you'd usually also just use class members:

shared static this() {
  // ...
  r.registerWebInterface(new WebInterface("42"));
  // ...
}

class WebInterface {
  string answer;
  this(string answer) { this.answer = answer; }
  // implicit "GET /" route
  void get(HTTPServerResponse res) { res.writeBody("Answer: "~answer); }
}

Re: How to organize a large router and corresponding functions

On Sun, 12 Jul 2015 09:32:17 GMT, Taylor Gronka wrote:

Oh - or attaching m_db to req would be nice.

There is a params field in HTTPServerRequest that is meant for attaching data to the request, but it currently only supports string. There was a plan to support a Variant instead, just the transition path wasn't clear at the time. Maybe we should revive that.

Re: How to organize a large router and corresponding functions

Thank you for the variety of approaches. I really appreciate it.

I'll probably try to start with a global, and eventually move to use the lambda or partial function approach, passing in a struct of things. A lot of my routes break from REST

On Sun, 12 Jul 2015 09:43:20 GMT, Sönke Ludwig wrote:

On Sun, 12 Jul 2015 09:32:17 GMT, Taylor Gronka wrote:

Oh - or attaching m_db to req would be nice.

There is a params field in HTTPServerRequest that is meant for attaching data to the request, but it currently only supports string. There was a plan to support a Variant instead, just the transition path wasn't clear at the time. Maybe we should revive that.

IMO, that would be ideal, allowing flexibility without the need to work around the framework or using globals.

Thanks again

Re: How to organize a large router and corresponding functions

I didn't realize the chain of restrictions involving global/const/immutable, so I think that option is out.

I tried using the delegate function you recommended. Your example with passing a string worked. Then I tried to mass my custom struct RequestPass, stored in m_pass. These lines of code:

m_pass.bks = new CassaClient("127.0.0.1", 9042, "bolt");

router.get(m_subPath ~ "register", (req, res) => bolt.views.user.register(req, res, m_pass));

yielded this error:

source/bolt/bolt.d(77): Error: need 'this' for 'm_pass' of type 'RequestPass'

Replacing m_pass with this.m_pass yielded the exact same error. I'm not sure what to do differently. I thought it might be clever to try this

auto pass = &m_pass;
router.get(m_subPath ~ "register", (req, res) => bolt.views.user.register(req, res, *pass));

Which left me with this error.

source/bolt/bolt.d(80): Error: function bolt.bolt.Bolt.register.__lambda2 cannot access frame of function bolt.bolt.Bolt.register

I've tried many of times to write a delegate function out, but I keep failing. I feel like I need something like the following:

void delegate() dg = bolt.views.user.register(req, res, m_pass);

, or

auto dg = &bolt.views.user.register;

But my Bolt class has no access to req or res, so far as I can tell.. and my register function requires req and res.. so I've dropped that for the time being.

Now, I happened across a thing such as mixin(import("user.d")) which is less elegant, but would work great for me. But I'm getting this error for relative and absolute paths to files:

source/bolt/bolt.d(109): Error: file "user.d" cannot be found or not in a path specified with -J

Re: How to organize a large router and corresponding functions

Now, I happened across a thing such as mixin(import("user.d")) which is less elegant, but would work great for me. But I'm getting this error for relative and absolute paths to files:

source/bolt/bolt.d(109): Error: file "user.d" cannot be found or not in a path specified with -J

Solution was easy here. Had to add stringImportPaths to dub.json:

{
"name": "bolt",
"description": "A simple vibe.d server application.",
"copyright": "Copyright © 2015",
"authors": [""],
"dependencies": {
	"vibe-d": "~>0.7.19"
},
"versions": ["VibeDefaultMain"],
"stringImportPaths": ["views/", "source/bolt/views"]
}

Have to keep "views/" in the list, or else links to the diet templates will break.

Re: How to organize a large router and corresponding functions

"stringImportPaths": ["views/", "source/bolt/views"]

For the sake of clarity, that's referencing two separate folders. The second folder listed has the D files with functions to be included. I think of them as "view functions", which I think is OK since templating engine views never seem to conflict with my own source code.

Re: How to organize a large router and corresponding functions

I would still like to learn what my mistake was with the delegates. However, I've come back around to one of my original solutions. I removed it from the table early on, but after exploring other options, I like how this looks and how it works.

source/views/user.d:

module bolt.views.user;
class UserViews {
	protected void register(HTTPServerRequest req, HTTPServerResponse res)
	{
        bks.addUser(req.form);
        res.render!("user/register.dt", req);
}

bolt.d:

module bolt.bolt;
import bolt.views.user;
class Bolt {
    private {
        auto USER = new UserViews;
        bks = new CassaClient("127.0.0.1", 9042, "bolt");
    }
...
    void register(URLRouter router) {
        router.get("register", $USER.register);
    }
}

If anyone has a critique of this method of organisation, I'd like to hear it.

Re: How to organize a large router and corresponding functions

On Mon, 13 Jul 2015 07:32:17 GMT, Taylor Gronka wrote:

router.get("register", $USER.register);

sorry, should be

router.get("register", &USER.register);

Pages: 1 2