Friday, January 10, 2014

Creating a competing web service framework with D

One of D's current target audiences is web developers. For medium to large web sites. To provide for this a framework is generally expected to speed up development times.
Because of this I have started working on Cmsed. I describe it as a Web development component library. Not a web service framework. Why did I do that?

I choose to call it a web development component library because there are some common functionality sites are usually required to have:

  • User system (login/logout/admin)
  • Possibly private messaging
  • Comment system for pages
  • Forums
  • Page editing during runtime (aka no raw file changes)
  • Dynamic menus at runtime
I have yet to develop these items. However the plan is to build:
  • User system
  • Forums
  • Wiki
  • Admin
  • System settings
The user system will be the most generic and useful. Basically all will depend on it.
Forums will be utilized for commenting. With rights access through admin.
Wiki again, rights through admin however it'll also provide pages (admin disable editing to all but admins).
Admin is the core system that'll determine how to manage all the other components.
System settings is based upon per install configuration. Where as a node also has a configuration settings, but in file. In theory that shouldn't change often.

But how will I make these?

  • A damn good router.
    One of the core requirements is to have a good router.
    There is a URLRouter in Vibe but I choose not to expose it directly. Instead I built upon it using classes.
  • An ORM.
    When I started D did have one or two projects which aimed at being an ORM. With the the following requirements I built Dvorm.
    I had a few other requirements:
    • Not dependent on any database type
    • Simple
  • A registration system.
    This may seem off to those who have worked in PHP/Java where registering anything usually meant some method called it or in the like of PHP some file had it at the top and you hard coded to include said file some how.
    What this means is a flexible cmsed.*.registration system was required per application.
    This includes:
    • Update registration and execution on a regularly basis (either every five minutes or hourly)
    • Route registration, so we know what routes are available
    • Model registration, so they will get configured automatically
    • On load, pre starting of listening execution of these functions will occur
    • Widget and widget routes. There are two registrations for these. Because registering a widget is for external use. Your saying you have a widget type that you will handle. It actually displays a widget essentially.
      But a widget route says I'm a template and I have all these widget that I can use at position xyz. Why? Who doesn't like the idea of being able to grab all positions in a template / route that you can hook into and change in side the database!
  • I've just mentioned the widget system. So here goes what its all about.
    A widget is essentially a group of html/js with a load of css classess associated with it. When a widget  provider is told to be used in a given contexts. It essentially produces it upon the given input. Allowing the flexibility of what ever it does but without styling.
I've focused quite heavily on infrastructure and minimizing what I do upon templates itself. There are some functions inside them to simplify handling and using the more advance features e.g. saving all widget routes vs template usages of it.

So how does Cmsed and Dvorm help web developers exactly? How does the code _really_ look.

import cmsed.base;
import dvorm;

class BookModel {
    @dbId
    @dbName("")
    BookIdModel id = new BookIdModel;
 
    ulong releaseDate;
    string title;
 
    mixin ORMModel!BookModel;
}

class BookIdModel {
    @dbId {
        string isbn;
        ubyte release;
    }
}

shared static this() {
    registerModel!BookModel;
}

Notice how the BookIdModel is not registered? This is because Dvorm grabs its values in place of the property. It uses its id field to do so.
You don't have to separate out the id fields. However it'll make it easier upon:

  1. Developers. All id fields are well defined and easy to reuse.
  2. The ORM knows how to create the models.
There is no constructor required. In fact it would error if it was tried.
You can utilise the above functionality to store one model inside another but a more preferred method is to return a query on the many model.

To create a query its simple.

BookModel.query()

That easy. To actually use it is just as easy.
BookModel[] values = BookModel.query().isbn_eq("some isbn number here").find();

Of course you can also do a remove on a query too, not just a find.

If you know all the id field values you can do a findOne or a find for partial.
BookModel[] allVersions = BookModel.find("some isbn number here");

There is also save and remove methods generated with no args.

So that's the brilliance of Dvorm. But what about the router?
The router is very very flexible piece of software.

import cmsed.base;
import mybook;


BookModel currentBook;

class MyBooks : OORoute {
    @RouteFunction(RouteType.Get, "/", "index")
bool index() {return true;}
    
    @RouteGroup(null, "/books") {
        @RouteGroupIds(["isbn", "version"]) {
            @RouteFunction(RouteType.Get, "", "book_view")
            bool find() {
                currentBook = Book.findOne(http_request.params["isbn"], http_request.params["version"]);
                return true; // render even if null, handle null in template (book_view.dt)
            }
        
            @RouteFunction(RouteType.Delete)
            void remove() {
                auto value = Book.findOne(http_request.params["isbn"], http_request.params["version"]);
                if (value !is null)
                    value.remove();
            }
        }
    }
}

shared static this() {
    registerRoute!MyBooks;
}

The above code has the ability to find a book based upon an isbn and a version. Set it to a global variable (which then a template can grab) tells the router to render the given template.
It can also delete a book based upon an isbn and version.
At the end of a RouteFunction it is capable of having a filter e.g. is logged in.
Note RouteGroups are stack-able, so be free to have quite complex urls!

There is a lot more advanced functionality that is being commented on what it does and how to use it in code. Over in the repository.

Currently its a very slow process to rebuild (compared to PHP) however I foresee shared library support helping with this one day.

Note anything that can be currently logged is. This includes the router, ORM, widgets and access. It may seem weird about the router and ORM but I say, why create documentation for these things when your code can literally produce it, itself? Since code is always the number one most up to date version of it, it makes more sense. Not to mention you spend much less time.

Performance:
I have not done too much to test this. Mostly this is because I have no way currently to realistically test it. However my aim goal for it is this: $100usd on an amazon AWS account using EC2 and elastic load balancing. It should be capable of handling 1000 requests a minute. From minimal testing 600 requests in a 300ms period is doable for a very empty system. If that went down to even 6 in the same time span I would be happy. That'll hit a lot of existing use cases in of itself. With Amazons services with elastic load balancing and if configured right, bringing new VM's up than it should be a mute point performance on scaling. It'll hit pretty much any size web service with it being quite cheap!
However you need to have at least two nodes up at all times and one database (preferably two for larger sites). This will mean no matter what requests should be able to be hit and if something fails it'll recover gracefully.

No comments:

Post a Comment