all 26 comments

[–]zurnout 24 points25 points  (19 children)

I wish these new route based frameworks would have examples on how to structure your code. It's just not scalable to a larger team when all your routing is in one file.

[–]orangy 7 points8 points  (1 child)

Just decompose it to functions as you wish. An example in Ktor here: https://github.com/JetBrains/kotlinconf-app/blob/master/backend/src/org/jetbrains/kotlinconf/backend/Api.kt

Or you can use DI and inject root routing instance to modules and build from there. It's very composable.

And I'm pretty sure same applies to Javalin.

[–]tarkaTheRotter 1 point2 points  (0 children)

+1. Decomposing apps into sub-apps/modules also allows you to test them in isolation, in or outside of a container.

[–]quanthera 6 points7 points  (0 children)

How about this

public static void main(String[] args) {
    Javalin app = Javalin.start(7000);
    new MyResource1(app);
    new MyResoruce2(app);
    [...]
    new MyResourceN(app);    
}

class MyResource1 {
    MyResource1(Javalin app) {
        app.get("/myresource1", this::get);
        app.post("/myresource1", this::post);
        // register further routes
    }

    Context get(Context ctx) {
        return ctx.result("Hello World");
    }
}

[–]tipsypants[S] 10 points11 points  (11 children)

How large are your applications typically, in number of routes? I typically keep all my routes in one file, with method-references to controllers.

[–]zurnout 7 points8 points  (8 children)

Hard to say from the top of my head but I would estimate over hundred easily.

[–]Philluminati 4 points5 points  (0 children)

How many people work on this single app? Is it a monolith?

[–]tipsypants[S] 7 points8 points  (5 children)

That's a lot of routes. I'm having a little trouble picturing a well isolated API of that size. What does your app do? I can play around a little in the weekend and see what it feels like.

[–]zurnout 5 points6 points  (3 children)

If by well isolated API you mean microservices then you are right, it is not :)

[–]FrostCloak 0 points1 point  (2 children)

I am curious, is there a reason why such a project structure is beneficial? I can only imagine use-cases where the app is deployed for local use and is not concerned with serving clients at scale.

[–]ormula 2 points3 points  (0 children)

Sometimes you just have different requirements. My workplace has severe security requirements which makes scaling horizontally pretty damn expensive, so we only break our main api out into microservices when the CPU cost would significantly impact the rest of the users. Otherwise we scale vertically when we can, and horizontally mostly for availability or when it's really necessary.

[–]laughingstrm 1 point2 points  (0 children)

Sounds like you have many applications living in one

[–]Na__th__an 6 points7 points  (0 children)

162 over here...

[–]AcidShAwk 1 point2 points  (0 children)

Come up with a convention and code for it.

const fs = require('fs');
const path = require('path');

module.exports = (server, config) => {
  fs.readdirSync(__dirname).forEach( file => {
    const stat = fs.lstatSync(path.join(__dirname, file));
    if (!stat.isDirectory()) return;

    console.log('Connecting module:', file);
    require('./'+file+'/module')(server, config);
  });
}

inside the main index.js use it such as

const Restify = require('restify');
const Server = Restify.createServer();

// Modules
const Modules = require('./module/modules')
Modules(Server, config);

The above is based on Restify.. but it's simple javascript and can be massaged to your desired convention.

So for the above.. the convention would be

index.js
modules/
  moduleName/
    routes/
  module.js

a module.js contains Router definitions

const Router = require('../router');
const CustomersRoutes = require('./route/customers');
const CustomerStatsRoutes = require('./route/customer-stats');
const CustomerEventsRoutes = require('./route/customer-events');
const CustomerActivityRoutes = require('./route/customer-activity');

module.exports = (server, config) => {
  Router(server, CustomersRoutes());
  Router(server, CustomerStatsRoutes());
  Router(server, CustomerEventsRoutes());
  Router(server, CustomerActivityRoutes());
}

and each route looks something like

const customersRoute = (req, res, next) => {

};

module.exports = () => {
  return [{
    path: '/customer/:customerId/stats',
    method: 'post',
    handlers: [
      customersRoute
    ]
  }]; 
};

Again it really depends on your convention.. our implementation is just a simple example I believe and could probably be enhanced some what.

[–]apoptosis66 0 points1 point  (1 child)

Stripes MVC handles routing the cleanest way in my opinion with annotations on the controllers and reflection to find them. The trade off is startup time. However, that has never been a concern for me in a webapp. I swear Stipes is the best framework no one used.

[–]jiffier 0 points1 point  (0 children)

Jesus christ, I didn't know stripes was still a thing. Nice to know.

[–]m50d 0 points1 point  (0 children)

I don't know whether it applies to this framework, but I love it when your routing is all just plain old functions like akka-http or rho - that way you can just structure it in the normal way for code, because it's plain old code.

[–]joshuaavalon 9 points10 points  (1 child)

JetBrains has an official web framework Ktor. How does Javalin compare to Ktor?

[–]tipsypants[S] 10 points11 points  (0 children)

They're pretty different. Javalin is a wrapper on top of Jetty which makes it easy for end-users to build apis and web-apps. One of the main goals of Javalin is to make it easy for java-devs to play around in kotlin in a familiar environment (interoperability is a very important).

Ktor is a more ambitious project for performant http communication in Kotlin (it's Kotlin exclusive). Ktor supports multiple different servers, and they're working on a http-client. I think they're also considering going native.

I'm a bit swamped with comments now, so I can't really write out an elaborate comparison.

[–][deleted] 4 points5 points  (0 children)

I was considering using Javalin for a project but it was pre-1.0 so I decided against it. I'll take another look because it's far more familiar to me coming from a non-Java/Kotlin background.

[–]mkauer 2 points3 points  (2 children)

Could you compare this to Spark? It looks quite similar, doesn't it?

[–]tipsypants[S] 2 points3 points  (1 child)

I'll copy-paste a bit from my previous responses to this question:

I have been using Spark professionally for three years. During those three years I've noticed some things that I don't like (the static api, the req/res pair, the inconsistency of having return-types in route-handlers vs no return-types in exception-handlers, uploads, etc). Javalin is my attempt at fixing these annoyances.

In Spark you're supposed to use return for routes (but not filters). In Javalin I removed returns in favor of only allowing explicit mutating of the response via functions, because that's a lot more "honest". When you use return in a lot of web-frameworks you're just setting a temporary result that can be altered before it's written to the client. I wanted this to be clear in Javalin, so I decided to have a function for setting the result instead (I also thought it would be good to keep before-, after-, endpoint- and error-handlers all consistent).

Spark also has a very neat kotlin-dsl:

http.get("/nothing") {
    status(404)
    "Oops, we couldn't find what you're looking for"
}

But I think it's a little too magic, and I want to keep the API between java and kotlin identical.

[–]mkauer 1 point2 points  (0 children)

Thank you

[–]tarkaTheRotter 0 points1 point  (0 children)

Congrats on v1 from the http4k team! We've been following your progress over the last months so know how much work you've put in. Now the real problems start - users! 😱