jooby icon indicating copy to clipboard operation
jooby copied to clipboard

jooby 3: next major release

Open jknack opened this issue 4 years ago • 46 comments

This is an open discussion of what we want for Jooby 3.x. For now we will start a discussion here and then cleanup ideas/features to a wiki page (or similar).

Beside is going to be a major release... I'm not planning to break compatibility like we did in 1.x -> 2.x upgrade, so:

  • Web site will keep as it is today: https://jooby.io, same goes for Java package: io.jooby.
  • The 3.x source code will be just a refactor of existing 2.x source code (not an entire rewrite). Unless something really bad/ugly come up, you will be able to upgrade doing a search/replace session.

Some notes I have so far:

  • Review Context API add getter/setter for almost everything (simplify unit test)
  • Keep minimum request/response related method into Context (simply context API)
  • Add getRequest/getResponse method to Context and add all the necessary getter/setter to each of these new Request/Response API
  • Remove bytecode analysis for return types and get back reactive operators (similar to 1.x)
  • Remove asm from jooby core
  • Kotlin Normal route vs Coroutine route try to simplify usage and remove the HandlerContext object. Favor usage of fun some(ctx: Context) and then get("/route", ::some). This probably implies that lambda body route are bind to Context itself
  • Rename FileUpload.destroy to close and make it Closable
  • Value API make them getter friendly
  • Remove runApp. Replace. with new App()
  • Rename Jooby class to WebApp?
  • Use application.logback.xml so we don't have initialization issues

As always, your feedback is welcome

jknack avatar May 12 '20 19:05 jknack

These look good. Can you also include adding of setter methods for the ModelAndView class so we don't have to re-initialize the class in case we are changing the view name for the same route?

Having to do this right now, in stead just set the view name based on the request parameters. Other routes can have multiple such initialization which looks expensive?

get("/", ctx -> {
            ValueNode asyncPage = ctx.query("asyncPage");
            ModelAndView modelAndView;
            if (Boolean.parseBoolean(asyncPage.toString()))
                modelAndView = new ModelAndView("index_async.ftl");
            else
                modelAndView = new ModelAndView("index.ftl");
            
            return modelAndView;
        });

Sayyiditow avatar May 18 '20 01:05 Sayyiditow

Nice to hear that compatibility with 2.x is kept in mind. We just started to use Jooby 2.x and planning with it for the long run, also planning to migrate our older codebases.

Huge thumbs up for the API refactoring for Contect -> Request + Response!

We ran into some problems with the Guice extension. In Jooby 1.x it was very convenient that you could inject request scoped objects into controllers / services. In Jooby 2.x it's only possible with some custom extensions implementing @RequestScoped and co. similarly to Jooby 1.x.

Could you briefly explain what does return value analysis and ASM in Jooby do?

imeszaros avatar May 28 '20 08:05 imeszaros

Huge thumbs up for the API refactoring for Contect -> Request + Response!

Going to keep Context in 3.x, because feel it is better the single argument handler:

get("/", ctx -> {...})

and not the two arguments

get("/", (req, rsp) -> {...}

There are some method name collision will keeping everything in Context class. In 3.x going to only keep the most frequently used methods in Context class, while less frequently or more specific methods will be available in Request/Response classes. This helps to keep some common code in Context (jooby-core) and force server to implement more simple/basic Request/Response objects.

Could you briefly explain what does return value analysis and ASM in Jooby do?

ASM helps to identify handler return types in lambda/script routes:

get("/rx", ctx -> CompletableFuture.supplyAsync(() -> "..."));

So here ASM find the handler returns a completable future and:

  • Run route handler in EventLoop
  • Attach a completable listener and to send response when ready

Same for other reactive types from: rxjava and reactor In 3.x this will requires some manual intervention (like in 1.x):

use(new CompletableFutureAdaptor());
get("/rx", ctx -> CompletableFuture.supplyAsync(() -> "..."));

Removing ASM give us:

  • Even faster startup time
  • Reduce memory in class space

jknack avatar May 28 '20 12:05 jknack

Would removing ASM slow performance down?

Would be good to add a jackson alternative such as dsljson since that's been 1 of the bottlenecks in techempower benchmarks :)

Maybe other security options besides a heavy weight like pac4j?

GraalVM aka native mode would be useful in a lot of places but might be a bit of work.

re-thc avatar Jun 08 '20 12:06 re-thc

Would removing ASM slow performance down?

A bit on some specific use cases, but not for usage on real application.

Would be good to add a jackson alternative such as dsljson since that's been 1 of the bottlenecks in techempower benchmarks :)

Yea, nothing stop us from using dsljson today. Due it requires an annotation processor, don't think we need to support as Jooby module. I keep jackson in TEB bc is a real library that works for complex use cases. Still, we. could rewrite the benchmark to use. something more lightweight

Maybe other security options besides a heavy weight like pac4j?

Hahaha, feel something similar... but is there any security framework lightweight?

GraalVM aka native mode would be useful in a lot of places but might be a bit of work.

We did some work and got something working with 2.x, slf4j-simple logging and netty (think undertow didn't work). Removing ASM from core (actually from runtime) will help to make Graal image.

jknack avatar Jun 08 '20 12:06 jknack

Yea, nothing stop us from using dsljson today. Due it requires an annotation processor,

dsljson has a reflection option, which is still quite a bit faster.

but is there any security framework lightweight?

If it's just basic auth or jwt (via e.g. jjwt) then it doesn't really need a "framework".

netty (think undertow didn't work)

Undertow 3 will be a wrapper around netty.

re-thc avatar Jun 08 '20 12:06 re-thc

dsljson has a reflection option, which is still quite a bit faster.

cool, I didn't know it.

If it's just basic auth or jwt (via e.g. jjwt) then it doesn't really need a "framework".

Yea, I usually do header auth so never use pac4j. There is a jwt auth module too: https://jooby.io/modules/jwt-session-store/

Undertow 3 will be a wrapper around netty.

Awful/wrong decision is you ask me. Found undertow super fast (faster than netty) and by far lot easy to work than netty. Also, it is a truly option/alternative to netty today. Don't want to be a wrapper over netty. I believe Undertow API and execution model will be more or less the same (but around netty). Probably going to drop support for Netty when Undertow run on top of it.

jknack avatar Jun 08 '20 12:06 jknack

Found undertow super fast (faster than netty)

Depends. Netty has more features like OpenSSL support. There's a lot of options that can be turned on. Out of the box Undertow is faster. Netty is complex though I agree.

Awful/wrong decision is you ask me.

Main focus is on moving off Wildfly (the main use of Undertow) and into Quarkus (which is Vertx/Netty). Makes sense as a business - Netty has a lot more users in the community, e.g. Twitter, Google and Apple. I don't think it's wise for IBM to be re-implementing HTTP3 and many of the other missing features in Java land when it comes to it so consolidating was a good move.

Undertow hasn't had major features properly in a long time. It was almost a solo project and the ex-lead now moved to Quarkus. I really like the API of Undertow!

Probably going to drop support for Netty when Undertow run on top of it.

Would be good to compare once Undertow 3 or 4 comes out to see how much overhead they've added too.

There is a jwt auth module too

Is it a documentation issue? The session store doesn't feel like authentication so maybe I'm misunderstanding it...

Simple caching with annotations and some assistance with API versioning would be good too.

re-thc avatar Jun 08 '20 13:06 re-thc

I'm not going to say anything about Quarkus... 😄

Is it a documentation issue? The session store doesn't feel like authentication so maybe I'm misunderstanding it...

Integrates the HTTP Session with JWT token using a cookie or header.

Simple caching with annotations and some assistance with API versioning would be good too.

Yea, this is more or less: module (so we can probably add something today). This thread is more for core changes. Do you have an example of API versioning?

jknack avatar Jun 08 '20 13:06 jknack

Do you have an example of API versioning?

@Version(1) -- so via annotations on methods / classes and option to set it as either via header, request parameter, or part of the uri to request it. Can return via uri, mime type or header etc.

re-thc avatar Jun 09 '20 03:06 re-thc

kotlinx.serialization support!

Would be great to see some native support for https://github.com/Kotlin/kotlinx.serialization

Thanks!

inaiat avatar Jun 18 '20 19:06 inaiat

Please add a Server API.

This will allow for other server implementations.

Currently we have a choice of Jetty or Netty (Undertow is moving to Netty) with Jooby. However, Netty is very low level and we found their API evolution can be unsettling for production deployments especially when you have multiple Netty based apps with each dependent library asking for different versions of Netty. There are other good Java based servers see ActiveJ that can host Jooby if there were a Jooby server contract for us to validate our code.

Async Java programming may not be much a gain once project Loom becomes reality. We played with Jetty with Loom (early access) on Jooby - it worked, our code was a joy to read, but we could not certify against a server contract.

a1730 avatar Aug 25 '20 12:08 a1730

The API is: io.jooby.Context, a server implementation must provide a context. It is impossible to require more than io.jooby.Context there are all type of oddities between servers.

jknack avatar Aug 25 '20 12:08 jknack

Awesome. Thank you.

On Aug 25, 2020, at 8:15 AM, Edgar Espina [email protected] wrote:

 The API is: io.jooby.Context, a server implementation must provide a context. It is impossible to require more than io.jooby.Context there are all type of oddities between servers.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub, or unsubscribe.

a1730 avatar Aug 25 '20 12:08 a1730

@jknack assuming this isn't around the corner would it be possible to cut a 2.x release with support for kotlin 1.4?

jonnii avatar Sep 03 '20 14:09 jonnii

@jonnii master branch is running on Kotlin 1.4, so next release (2.9.0) comes with it and yea, no work has been done yet on 3.x 👍

jknack avatar Sep 03 '20 14:09 jknack

This is an open discussion of what we want for Jooby 3.x. For now we will start a discussion here and then cleanup ideas/features to a wiki page (or similar).

Beside is going to be a major release... I'm not planning to break compatibility like we did in 1.x -> 2.x upgrade, so:

* Web site will keep as it is today: https://jooby.io, same goes for Java package: io.jooby.

* The 3.x source code will be just a refactor of existing 2.x source code (not an entire rewrite). Unless something really bad/ugly come up, you will be able to upgrade doing a search/replace session.

Some notes I have so far:

* Review Context API add getter/setter for almost everything (simplify unit test)

* Keep minimum request/response related method into Context (simply context API)

* Add getRequest/getResponse method to Context and add all the necessary getter/setter to each of these new Request/Response API

* Remove bytecode analysis for return types and get back reactive operators (similar to 1.x)

* Remove asm from jooby core

* Kotlin Normal route vs Coroutine route try to simplify usage and remove the HandlerContext object. Favor usage of `fun some(ctx: Context)` and then `get("/route", ::some)`. This probably implies that lambda body route are bind to Context itself

* Rename FileUpload.destroy to close and make it Closable

* Value API make them getter friendly

* Remove runApp. Replace. with new App()

* Rename Jooby class to WebApp?

It could be named as JoobyWeb or just Jooby as it is now.

* Use application.logback.xml so we don't have initialization issues

An overridable logger configuration similar to what log4j2 provides, could be considered too. JSON logging could be helpful these days using logger specific appenders, then.

As always, your feedback is welcome

ravihara avatar Sep 12 '20 07:09 ravihara

@ravihara Log4j2 is supported in Jooby 2.x today https://jooby.io/#configuration-logging-log4j2 and you can use log4j.json to configure it.

jknack avatar Sep 12 '20 13:09 jknack

@ravihara Log4j2 is supported in Jooby 2.x today https://jooby.io/#configuration-logging-log4j2 and you can use log4j.json to configure it.

Well! What I was suggesting was, can we have log4j2 as an alternate to slf4j instead of using it via slf4j facade. Something similar to how Vert.x does it. If the plan is to stick to slf4j, I would rather continue to use logback mainly for its native implementation of slf4j api.

ravihara avatar Sep 12 '20 15:09 ravihara

Oh got it. will check what vertx does and yes! would like to remove logging from core

jknack avatar Sep 12 '20 16:09 jknack

Dependency on the Config library could be also dropped to:

  • be more lightweight
  • allow to use the library of choice

Configuration could be done by a DSL like:

{
  configure(cfg -> {
    cfg.serverOptions(so -> {
      so.setPort(90);
      ...
    });
    cfg.application(app -> {
      app.setName("MyApp");
      ...
    });
    // or
    cfg.applicaton(new AppConfig().setName("MyApp"));
  });

  // getting a property
  config().serverOptions().port();
}

And the integration with the Config library could be provided by a module to keep compatibility of course. Users could inject the instances provided by the module to get their own config entries:

{
  require(Config.class).getString("my.entry");
}

imeszaros avatar Sep 27 '20 16:09 imeszaros

Time for JPMS review? We tried to use modules in the past and I believe there were split modules between jooby-core and jooby-test.

a1730 avatar Sep 28 '20 01:09 a1730

I agree with @imeszaros on making Config library pluggable.

I have done extensive testing on startup time for our code base and our microservices and the number one thing that really slows startup time for an application before it starts connecting to databases or using massive reflection (e.g. Spring) is resource loading from the classpath.... and I don't mean classes.

For example Log4J2's startup time is 300ms and this is because of the excessive of resource looking up of property files.

Ditto for logback however logback can be loaded using just the service loader (I put that in a long time ago for ceki).

I haven't tested lightbends config time wise for awhile but I know by default it does some resource looking up and I think quite a bit if it can't find config files.

The convenience method ConfigFactory.load() loads the following (first-listed are higher priority):

From lightbend config project page:

system properties
application.conf (all resources on classpath with this name)
application.json (all resources on classpath with this name)
application.properties (all resources on classpath with this name)
reference.conf (all resources on classpath with this name)

Now I think I got around this problem by doing something like:

	public static Environment createEnvironment() {
		ClassLoader cl = Thread.currentThread().getContextClassLoader();
		if (cl == null) {
			cl = JoobyFactory.class.getClassLoader();
		}
		return new Environment(cl, Environment.defaults(), "ignore");
	}

Our microservices jars are like down to 9 Megs and I take great pleasure in every 200 KBs we can shed from them so no hard dependency on Config would be a nice feature in my book .

agentgt avatar Nov 20 '20 18:11 agentgt

@jknack

Oh got it. will check what vertx does and yes! would like to remove logging from core

On removing SL4J take a look at Min log: https://github.com/EsotericSoftware/minlog

I'm not saying you depend on the library but rather take the code and adapt it for internal logging of Jooby.

Then you make multiple jars for info/debug/warn etc or delagate to SL4J. You can make a production level jar that will true compile time NOOP for INFO and below but fail horrifically on ERROR by dynamically loading SL4J and then logging message and then falling back to System.err (this is how I do it for one of our libraries).

agentgt avatar Nov 20 '20 19:11 agentgt

will definitely look at minlog, but yea that is the issue with logging and config: we need to add a new contract/abstraction for each of them... which I don't like it.

jknack avatar Nov 20 '20 20:11 jknack

we need to add a new contract/abstraction for each of them... which I don't like it.

We kind of already have it: Value and Environment.getProperty.

Another alternative is Microprofile Config: https://github.com/eclipse/microprofile-config which is a config facade.

One thing I kind of don't like about Jooby is it tries to do too much initialization and thus is kind of getting into DI boot strapping and wiring space. This is indicative of all the modules that are not strictly related to HTTP.

That is initialization for our Jooby projects:

  1. Config framework
  2. Logging Framework
  3. Possibly Dependency Injection framework but lately we don't use DI
  4. Database layer
  5. Event system layer (kafka, rabbit, etc)
  6. And finally HTTP layer aka Jooby

Jooby seems to want to do:

  1. Logging framework (this because SLF4J static initialization is stupid and requires it to be practically the first thing that gets executed in every java application... thus logback with its XML dialect is the first thing that gets executed)
  2. Jooby
  3. Jooby modules

Because we do the initialization I don't find most of the Jooby modules at all useful especially the database ones.

The problem is the main requirement for sophisticated configuration is because Jooby has a modules system.

I'm not strongly against the module system but that is why the config library seems to need to be complicated. I guess my question is other than the HTTP message encoding modules are folks using the other modules?

See these days I find we don't need complicated configuration libraries nor dependency injection because we no longer make giant monolithic applications but instead do microservices. Thus if there is any config it needs to be flexible in terms of source and available over a network none of which lightbend config offers (our internal config framework does). What isn't really needed is the converting that lightbend config offers... String getProperty(String k) is mostly good enough (IMO).

agentgt avatar Nov 27 '20 15:11 agentgt

Good summary for your projects, but yea after 6 years of doing Jooby. I'm sure people find module useful. Installing modules (compared with 1.x) is just a call to module.install(this).. nothing more and if you don't want or like them, just don't install/add them. There is no overhead.

Slf4j was the default logging in Java in all these years, with logback native implementation. No big complains about until log4j2 got finally out. So if you see now it is basically three possible/real implementation (in practice or general): logback, log4j2, System.out.

Config from type safe is a simple, lightweight and easy to use configuration library.

Configuration over network is an over kill... You can't read a property and save it in a String (which is the minimal basic/use case). Instead you need a reference to the entire configuration or reference to that string... feel too much.

Beside this and due 2.x took a different approach than 1.x I believe we could:

  • Use java.util.Properties for configuration (no external dependencies, nothing)
  • Write a log wrap, that bootstrap logback or log4j2 as modules

jknack avatar Nov 27 '20 17:11 jknack

Configuration over network is an over kill... You can't read a property and save it in a String (which is the minimal basic/use case). Instead you need a reference to the entire configuration or reference to that string... feel too much.

Most cloud providers provide metadata services as well as there is also vaults for secrets (See hashicorp vault).

I’m not saying you provide a distributed config system. I’m saying use a library that is pluggable or provide an SPI. For example Use Micro profile config and let it default to lightbend config.

Javalin, spring, micronaut, quarkus, helicon and more all provide a way to plug in your own configuration sources. I mean it’s just key, values.

Also Config from lightbend is hardly simple. It has its own config parser (hucon), interpolation, and resource loading. It also can map config to objects via reflection. It takes longer for config lightbend to load up its default stuff than it takes for our internal config to use google clouds meta data service to fetch plain properties! I’m serious!

And yet with all that complexity it doesn’t have dynamic properties nor can you change the source. I mean I suppose you can try to implement configs interface and disregard its javadoc on not to do that.

But I can see how the modules are useful to other folks.

agentgt avatar Nov 27 '20 17:11 agentgt

I also don’t have that much problem with SLF4J provided it isn’t kicked off on unit testing and that internal network logging isnt done with it (eg tight loop trace statements).

I just meant if you are going to get rid of it in core you might as well have it go really fast and not auto initialize.

agentgt avatar Nov 27 '20 18:11 agentgt

Hmm I guess you can kind of alter how lightbend Config loads with ConfigLoadingStrategy. I remember looking to feed custom sources into lightbend config and had some issues a couple years ago.

Regardless it takes less than 10ms to call google clouds meta data service and to load it with Jackson or straight up java.util.Properties. The below loads all the key, values into Jackson but you can pull distinct keys as well. Loading up lightbend config on the other hand is at least 100ms last time I checked.... so no network is not slow or overkill in the cloud. Remember the filesystems are basically on a network.

static String GCLOUD_URL =
		"http://metadata.google.internal/computeMetadata/v1/project/attributes/" + "?recursive=true";

protected static Map<String, String> gcloudMap(
		String url)
		throws IOException {
	URL obj = new URL(url);
	HttpURLConnection con = (HttpURLConnection) obj.openConnection();
	try {
		con.setConnectTimeout(200); // don't worry google is extremely fast.
									// If
									// slow something is wrong.
		con.setReadTimeout(2000);
		con.setRequestMethod("GET");
		con.setRequestProperty("Metadata-Flavor", "Google");
		con.getResponseCode();
		int code = con.getResponseCode();
		if (code > 400) {
			throw new IOException("GCloud server had issue");
		}
		try (InputStream is = con.getInputStream()) {
			ObjectMapper om = new ObjectMapper();
			@SuppressWarnings("unchecked")
			Map<String, String> m = om.readValue(is, Map.class);
			m = new LinkedHashMap<>(m);
			return m;
		}
	}
	finally {
		if (con != null) {
			con.disconnect();
			con = null;
		}
	}
}

agentgt avatar Nov 27 '20 19:11 agentgt