Hacker News new | ask | show | jobs
by EugeneOZ 3468 days ago
Syntax is amazing! Hope (so much) to see it possible on beta soon. Maybe for somebody it's not a new thing, but for me this:

  struct Message {
     contents: String,
  } 
      
  #[put("/<id>", data = "<message>")]
  fn update(id: ID, message: JSON<Message>)
where message is auto-decoded - it's awesome!
3 comments

Since you mentioned "for somebody it's not a new thing": this looks a lot like Flask, if you're into Python (except for the type safety); I'm sure there's a Ruby equivalent. It looks a lot like Jersey on the JVM (for Scala, Java, etc).

This pattern is really nice for web frameworks. I'm super excited to see it for Rust! To me, this signals that we're getting pretty high up there on the early-adoption curve.

Sorry but I cannot agree. I work on the JVM and this pattern is great for "hello world" webapps but breaks down pretty quickly. When large parts of your logic like metrics, security and routing is being done with metaprogramming you're really doing yourself a disservice.
If you don't like routing being done with metaprogramming, you can manually create routes yourself similar to this example[0]. I think the author is trying to show appealing APIs.

[0] - https://github.com/SergioBenitez/Rocket/blob/master/examples...

Thank you. Actually, that underlying API looks great. I would prefer it any day of the week.
I've worked a lot with this pattern in Python (Flask), Java and Scala. I have found it a little inconvenient beyond the simple hello world app when dealing with Flask, but I haven't encountered this problem with Java/Scala. To date, I've worked on probably 4-5 separate applications using this pattern and it's worked quite well. I have also used Spring 4 on the JVM, which ... feels more flexible, but also has a lot of cognitive overhead, and so I'm not crazy about it. I believe Spring Boot follows closely after Jersey, most likely as a result of the pattern's popularity.

In my experience, it is pretty straightforward to build your resources (as Jersey calls them) into various classes without any issue, even for applications with many API endpoints.

As for metrics, security and routing, most of these are canonically managed using annotations in Java. In Spring, application context XML configurations are used, but they are very big and not close to their actual implementations, which makes them get out of control quickly (and inconvenient when they aren't out of control). It feels like losing either way.

I'm a big fan of Coda Hale's work on Dropwizard, which uses metrics, security and routing through annotations, and it works very well for large scale production applications. I use these techniques at scale at Bazaarvoice with great success.

To be fair, I won't go so far as to claim the pattern is imperfect. It's just the best pattern I've used for web services to date, even at scale.

I would love for you to expound more on your own experiences in more detail!

edit: clarification on where I've used the pattern

I've worked on everything you mention and more, and I find the simplicity of say node.js or SparkJava very refreshing when coming from metaprogramming frameworks. Once you go metaprogramming everything is suddenly metaprogramming. Data serialization is metaprogramming. Middleware is metaprogramming. Adding your own plugins to the framework becomes metaprogramming.

Also, because the framework wants to create your classes you cannot simply send in the objects dependencies into the constructor anymore. You find yourself in need of a DI framework instantiating your classes. And suddenly the data dependencies of your app become very vague. Adapters get registered based on the presence of JARs and/or classes in directories which are scanned in runtime.

You have reinvented the work of the compiler and the language, and what do you gain? A big mess where you no longer have programmatic control. I cannot easily start parts of my application for testing, I have to rely on Spring or Jersey having some sort of metaprogramming magic triggered by some magic annotation supplied by a JUnit<->Spring integration module. Suddenly modules like JUnit and Spring aren't combinable without metaprogramming. You see this trend spreading like wildfire: "Is your lib Spring compatible?". We should instead be asking ourself why the heck unrelated libs have to be compatible with one another. They are supposed to be orthogonal, that's the point! To see an example of how ridicilous this can get, have a look here: http://stackoverflow.com/questions/35957287/cucumber-testng-...

I wouldn't go as far as saying that frameworks like Spring or Jersey are unworkable. But I believe you could acheive the same level of service a LOT easier if you wouldn't go metaprogramming crazy. Just consider the amount of time wasted trying to figure out why something isn't wired correctly, why some plugin is loaded, why your component isn't picked up, why some property isn't resolved etc. With normal programming, you fire up the debugger and step through the code. Solved in 5 min. With metaprogramming, it's a struggle on a whole different level.

Thanks for clarifying. I agree with you, mostly.

This is exactly the reason why I refuse to use Lombok. It literally mutates your bytecode! That's a disaster waiting to happen, and a potential debugging nightmare.

I think that there is a fair balance where metaprogramming has great utility. For example, I'm a huge proponent of Guice. And yes, sometimes it makes debugging issues a little more complicated. OTOH, without it, there's a lot of boilerplate. Admittedly, DI in Java is a workaround for an inherent Java problem (boilerplate). Maybe it wouldn't be so useful in other languages/ecosystems. I have written services both with and without DI, and prefer it, and haven't had significant issues debugging or managing it. But it must remain scoped.

As for library compatibility issues, that is more of a byproduct of dynamically linked libraries than metaprogramming. If you're writing in a JVM language, you're kind of stuck with this (OK, you could shade your library and all its dependencies to get away from it, if you want to deal with the bloat / impact to the JIT'er).

Having said all that, your statements are arguing against metaprogramming, and I'm not really arguing for or against metaprogramming in my original comment. Simply, I think the pattern of defining HTTP resources in the aforementioned way is very intuitive from a software development perspective. If it can be done without metaprogramming, then great. But the presence of metaprogramming doesn't, in my mind, preclude the effectiveness of the pattern.

Ok, I hear you. I agree somewhat, but not totally :) To me, the most important aspect of programming is to retain full control. I for instance want to be able to register my HTTP endpoints based on some data read from a file. I want to be able to start my app in server mode and run requests against it in any way I want during testing. What many of these frameworks do is to remove this power.

Bytecode modification is not too bad IMO, as long as it's within reason. I use a notnull annotation weaver to add notnull assertions. Not Lombok, that's too much for me as well. Should be another language really, like Kotlin.

Metaprogramming does have utility. But it has a tendency to go overboard. I actually find Jersey to be a good example of a nice and clean API for defining resources. I recently tried to integrate it into a small app where we do all the wiring manually for clarity. The problem is that it's hard to stay in programmatic control. You define a resource. You realize you need a Service in your resource. You realize that the only way of getting it into your object is to inject it with the JAX-RS annotations. And so suddenly your service has to be a JAX-RS @Singleton service. You have to make your whole app JAX-RS compatible. The framework takes control. This is really bad IMO. A library which requires control over your program.

This is not an intrinsic problem of metaprogramming, but IMO it's a big problem of Java as it stands today. We need to give up on frameworks, make sure that all libraries are combinable and that they don't take control of your app. Doing so would make everything a lot better.

I wish I could just do:

new Jersey(config).addRoutesIn(new HelloWorldResource(new MyBackendService(dbDriver))).addSerializer(new JsonSerializer()).start();

This would then utilize some reflection when routing requests, but not take over my entire app.

Regarding the libraries, my point was not that you get conflicting classes (DLL hell sort of thing), but that we're building multiple products which want to take over the world. JUnit wants to be in control, and so does Spring. So when you try to combine them, you end up with a problem. Can you start Spring programmatically from a JUnit test? Well, not really. You can, but it's very involved. So they want to provide you with a Spring-JUnit integration. This is a really bad idea, since every linear combination of libs has to get an integration, like the link I included in my post. If all these libraries just tried to stay out of the way of the programmer and just provide a programmatic API, there would be no need for such integrations.

Your post totally resonated with me, particularly how once you go metaprogramming everything is suddenly metaprogramming. I work at a Spring shop and we joke about how we should just be called Senior Java Annotators. I find it ironic that Spring does all this machinery in order to instantiate your application, yet often you end up needed to use the Order http://docs.spring.io/spring/docs/current/javadoc-api/org/sp... annotation anyway to get things to wire up in the right way...at which point, it is kind of like, what even is the point of using Spring then? We waste tons of time figuring out what is getting configured, injected, instantiated, etc. to the point where I'd rather just define everything myself in the order I need it, so I know exactly what I'm getting and why. I miss normal programming, normal debugging, and normal application initialization.

And then there is the problem of CGLib wrapping e.g. for @Transactional and other annotations. Of course, it only happens for other services calling into your wrapped service. If you call your own methods from within the service, you're hitting just the service instance, not the wrapped instance, which causes all sorts of headaches. Yet people still take this disaster further with Lombok/bytecode manipulation and aspects.

http://sparkjava.com/ looks very appealing; it looks very much like http://koajs.com/ which I really respect.

Hey! A colleague who also sees the light! I've been pushing for more programmatic control in our apps due to the same problems you mention. I would actually consider myself one of the most versed in Spring at our shop. But while I see so many problems, many devs only see "the beauty of the minimal code". I wish I could understand what is blindsiding them from all the problems we have.

The latest incarnation of this problem is Spring Boot. It does some really far out stuff to your app, like making m separate metrics for each unique url of your webapp. That goes south pretty quickly. Not to mention how much longer the app takes to start, how much slower it is compared to coding the same functionality manually (x4), how much trouble people have getting the right mental model of how the app works (with instrumented transaction boundaries and stuff that you mention), how hard it is to debug (why did this endpoint return 404?) etc.

We did some POCs with vert.x and spark, and all of a sudden you see what type of overcomplicated mess we have been creating for ourselves.

I disagree. It's great syntax for controllers - place where you should read request data. Services is another story , but you will always have place where you read initial request data.
Edit: should preface this by saying the framework looks really impressive. I've been looking for a Rust web framework to settle on and this is the most appealing.

It's funny to note how much something small like this matters. It's syntax sugar and presentation, but the difference a comment like this has at the top of an HN post vs an ambiguous but detailed discussion about feature x...massive in effect.

It doesn't seem to be the case, but the entire framework under that could be bad yet it's caught mindshare on the basis of routing syntax (one of the smallest worries in a production web application). Wonder how many frameworks have failed just on the basis of not making a similar decisive first impression?

Syntax matters?

We're in the process of addressing discoverability of the crates.io ecosystem, which led to this RFC: https://github.com/rust-lang/rfcs/pull/1824

A quote from it:

> By far, the most common attribute people said they considered in the survey was whether a crate had good documentation. Frequently mentioned when discussing documentation was the desire to quickly find an example of how to use the crate.

Open source projects, in a sense, are like startups: if you want to acquire users, your users have to be able to understand the product! A deeply impressive technical project is, well, impressive, but if nobody can figure out how to use it, it's not going to see nearly as much uptake as a technology that's got a clear and easy way to use it.

There's secondary effects too: a project with a slick website like this makes people take it more seriously. It's less likely that someone who put this much effort in is just going to drop it immediately, though of course, that's not an absolute rule.

TL;DR: developers are users too. Developer marketing matters!

Can I just say, I cannot applaud you enough (nor other contributors to rust) for how much you care about developer opinions and ease of their introduction to the language and subsequent use! I freaking love you guys.
API (and modern web servers are API only) should:

1) handle route (read data, transform, prepare other resources, check access rights)

2) generate response with reading/writing to db.

First step is not so primitive. Reading request data is often boilerplate-rich code, so syntax which can remove tons of boilerplate is very welcomed.

Personally I don't like domain-specific sugared syntax. I prefer clarity above all, especially since I hope to spend the least possible amount of time in the networking domain, and this means I'd otherwise have to relearn the syntax whenever I have to dig into the code again.
When digging into new projects I always find that there's going to be cognitive overhead because of certain abstractions.

    let always_forward = Route::ranked(1, Get, "/", forward);
this is a normal function call, but what does it mean? Is that really better than special syntax that desugars to this code?
Yes, because function calls are much easier to trace down than special syntax.
Syntax is why sinatra and express got bit.

Just add a callback for your route and off you go.

What does "got bit" mean?
he means got big ;)
lol, yes sorry. got bit sounds like I meant the opposite.
Thanks. That does make more sense. :)
Indeed way cleaner than Iron in terms of parsing query (int) arguments and JSON post data (BodyParser).