[HN Gopher] Show HN: Java REST without annotations, DI nor react...
       ___________________________________________________________________
        
       Show HN: Java REST without annotations, DI nor reactive streams
        
       grumpyrest is a Java REST server framework that does not use
       annotations, automatic dependency injection or reactive streams,
       and minimizes the use of reflection. I created this because I got
       fed up with annotation-mad frameworks that you cannot easily
       understand, step into or reason about. grumpyrest uses the type
       system to guide JSON mapping and validation, and (possibly virtual)
       threads for parallelism. It's for grumpy people who don't like what
       REST server programming in Java has become.  I made this because I
       intend to use it in one of my own projects, but at the same time I
       want to make it available to others to (hopefully) get some good
       ideas on how to extend it.
        
       Author : moring
       Score  : 76 points
       Date   : 2023-06-11 17:21 UTC (5 hours ago)
        
 (HTM) web link (github.com)
 (TXT) w3m dump (github.com)
        
       | social_ism wrote:
       | This does nothing that important. I do detest annotation soup,
       | but what would be more useful is to simply generate a competent
       | rest service and client from the openai spec. There is nothing
       | performant or magical about using Java here.
       | 
       | In fact the comments on performance are pretty dumb. The JIT
       | compiler is the only source of performance in the jvm and you
       | will never make a performance optimization that beats the JIT
        
         | moring wrote:
         | Thanks for your feedback. My experience with code generation
         | from an OpenAPI spec wasn't that great, to be honest.
         | 
         | The comments about performance are based on earlier experience
         | with a similar scenario in which code generation _did_ improve
         | performance. Mostly in that the generated code was "de-
         | convoluted" to such an extent that the JIT could actually do
         | its work. I'm expecting a similar thing here, especially with
         | JSON serialization/deserialization, because of the heavy amount
         | of reflection involved: The generated code would then no longer
         | use reflection, but access fields and methods directly, and can
         | therefore be optimized by the JIT.
        
           | exp-prohibited wrote:
           | [dead]
        
       | stickfigure wrote:
       | This misses out on the main benefit of using annotations: The
       | associated Java code is "just Java". Testing JAX-RS endpoints is
       | just a matter of instantiating Java objects and calling methods
       | on them. Testing grumpyrest endpoints would require mocking the
       | requestCycle other low-level http-oriented classes.
       | 
       | Here's the JAX-RS equivalent of the demo:
       | public class GreetingResource {             @POST
       | @Path("/make-greeting")             public MakeGreetingResponse
       | greet(final MakeGreetingRequest request) {                 if
       | (request.addendum.isPresent()) {                     return new
       | MakeGreetingResponse("Hello, " + request.name + "! " +
       | request.addendum.getValue());                 } else {
       | return new MakeGreetingResponse("Hello, " + request.name + "!");
       | }             }         }
       | 
       | This is less noisy (no explicit parseBody) and much easier to
       | test - you can just instantiate a GreetingResource and call the
       | method, no mocking required.
        
       | AugustoCAS wrote:
       | Something that people are missing about annotations: in order to
       | test the annotated class/method one needs to start running extra
       | code, sometimes a lot of extra code.
       | 
       | Using spring as a toxic example, many things have to be tested
       | using @SpringBootTest which is incredibly slow to start. On top
       | of that because of the use/abuse of @MockBean tests stop being
       | thread safe. So one ends up with slow test that need to be run
       | sequencially. I'm working in a 'start up' that went the spring
       | boot way and quite simple services take 15+ minutes to run all
       | their tests, which is insane.
       | 
       | On top of that, annotations make it impossible/very dificcult to
       | know what code is actually executed (and also it not possible to
       | navigate to the code in an IDE). As I rule of thumb, I'm always
       | happy to swap one annotation for one or two lines code.
        
         | ivan_gammel wrote:
         | > On top of that, annotations make it impossible/very dificcult
         | to know what code is actually executed (and also it not
         | possible to navigate to the code in an IDE)
         | 
         | I have never understood this argument. What is exactly the
         | problem with identifying the executed code?
        
       | revskill wrote:
       | Java is an OOP language, and OOP is enough to implement DI in a
       | consistent way. It's how enterprise NodeJS projects are
       | structured, too (without frameworks like NestJS).
       | 
       | We might not need annotation for DI stuffs, it's an overkill.
        
       | The_Colonel wrote:
       | > Unknown properties in records cause an error too.
       | 
       | This kills the forward compatibility.
        
         | ivan_gammel wrote:
         | It's fail fast vs fail randomly because unknown property was
         | expected by client to be handled in a certain way. Not every
         | case deserves forward compatibility.
        
       | dimgl wrote:
       | Hey, I'd be interested in seeing what this looks like but the
       | README.md is just a bunch of wordiness. I worked on a beanstalkd
       | client for Node.js and Golang, here's an example of going
       | straight to the point: https://github.com/getjackd/jackd
       | 
       | I'd say making a better README.md is your #1 priority.
        
         | moring wrote:
         | Thanks for your feedback. The README has been criticized in
         | another comment already, so I'll definitely take that serious.
        
       | faangsticle wrote:
       | > it calls constructors to create dependency objects, and passes
       | constructor parameters to inject them
       | 
       | So, basically DI. This is the recommended mode of operation of
       | Guice & Spring.
        
         | moring wrote:
         | Keep in mind that I had to cram as much useful information as
         | possible into a single headline. The GitHub page says "without
         | _automatic_ dependency injection". What grumpyrest does is
         | sometimes called "poor man's DI", although this term has been
         | discouraged because it is quite useful. The important thing is
         | that it does not rely on any DI framework / container, and
         | especially not on automatic construction of dependencies or
         | annotations.
        
           | faangsticle wrote:
           | To be clear, most (all?) DI frameworks are available with
           | plain classes without annotations. They do use reflection to
           | figure out which parameter goes where, but that's it.
           | public class Foo {           public Foo(Bar bar, Baz baz) {}
           | }
           | 
           | Is a perfectly valid class as far as most DI frameworks are
           | concerned.
        
             | jwfy2342___ wrote:
             | I don't find value in runtime DI frameworks, however I'm
             | assuming there is a valid use case for them since they
             | exist.
        
               | faangsticle wrote:
               | Interoperability with libraries that can be loaded at
               | runtime, and in general skipping a build step that would
               | make development annoying to do.
        
       | KronisLV wrote:
       | It's always nice to see projects attempting to explore something
       | a bit different than the status quo (which in Java's case is
       | Spring or hopefully at least Spring Boot). Personally, I've also
       | had good experiences with Dropwizard:
       | https://www.dropwizard.io/en/latest/ Any smaller project will
       | suffer at least somewhat from a lack of documentation/examples,
       | but Dropwizard takes many packages that might be considered
       | idiomatic in the Java ecosystem (Jetty, Jersey, Jackson, Metrics,
       | Logback, Hibernate/JDBI3 and others) and joins them without too
       | much hidden complexity or "magic".
       | 
       | In contrast, something like Spring might suffer from too many
       | examples, many of which are outdated, no longer relevant or
       | considered best practices, as well as there are far too many ways
       | to get something done, leading to lots of confusion. Though the
       | last time I used Dropwizard, I still ran into some issues with
       | adding dependency injection (which is optional), since I had to
       | write some of the code for that myself and couldn't find
       | something that worked nicely enough for my needs. Otherwise it
       | was fine, though probably could benefit from additional tooling
       | like Spring Initializr.
       | 
       | Either way, anything that lets you put a breakpoint on some
       | initialization code is worth a look in my book, vs having
       | multiple layers of indirection and logic dictated by annotations
       | that are not easy to debug, or even understand. That's also why
       | I'm not necessarily the biggest fun of Laravel, Rails (convention
       | over configuration/code) or a few other frameworks, though I
       | acknowledge their usefulness otherwise.
        
       | rco8786 wrote:
       | Kudos to you for trying this. Every time I open a Java project
       | after spending time in literally any other language I get
       | saddened by just how...cluttered? wordy? tedious? it is...hard to
       | find the right word, but it's not good.
        
         | nunobrito wrote:
         | Sure, other languages without semi-colons and using invisible
         | tabs for syntax are fantastic. What I love best are magic
         | object types where you only discover invalid type casting when
         | running the code.
         | 
         | And lest we forget the fabulous unit testing for those non-
         | tedious languages. Oh.. I forgot testing is tedious and
         | therefore unavailable for most of those languages.
         | 
         | But hey, just look at that really short code without a single
         | comment. The word you're looking for is: Fantasticc! :-)
        
           | rco8786 wrote:
           | This doesn't describe any language or framework I've worked
           | in so hard not to conclude that it's some sort of straw man
        
         | jayd16 wrote:
         | Wouldn't this add more cluttered boilerplate? Annotations are
         | pretty clean looking but the backlash is from the "magic" they
         | provide, not the clutter.
        
         | winrid wrote:
         | Vertx is pretty good. I have two vertx services in prod without
         | a single annotation.
        
           | exp-prohibited wrote:
           | [dead]
        
         | mhd wrote:
         | I've been doing some Angular-/Nest-ish TypeScript projects, and
         | it's basically Spring with more bugs and = signs.
        
           | Traubenfuchs wrote:
           | As a year long spring (boot) dev, I felt right at home with
           | angular 2+ -it's literally the spring framework of frontend
           | frameworks.
        
           | re-thc wrote:
           | > basically Spring with more bugs and = signs
           | 
           | The worst parts of Spring is Spring. Nest lacks the IDE, the
           | performance in the JVM, etc... there is nothing to gain.
        
           | webosdude wrote:
           | I haven't used Angular but I have used Nest and I agree with
           | this sentiment. Java Spring's annotations are similar to
           | Nest's custom decorators.
        
         | olavgg wrote:
         | Of all web frameworks, with perhaps the exception of Ruby on
         | Rails. I really struggle with lack of documentation or the
         | quality of documentation. And lesser popular frameworks has a
         | lot less volunteers sharing knowledge.
         | 
         | Spring Boot is one of the most popular web frameworks in the
         | world. It is by far easier to figure out how to do build
         | something that solves business problems in Spring / Hibernate
         | than alternatives(except maybe RoR).
         | 
         | For example, do you want to use Micronaut, the Spring Boot
         | killer? Good luck fetching a collection with string type. I
         | takes basically 2 minutes to google how do this with Spring
         | Boot / Hibernate, and it will take you a day or two to figure
         | out it isn't possible at all with Micronaut without writing
         | massive amount of code that binds the results from your SQL to
         | your objects. This is one example, and it shows the madness in
         | web frameworks. Why are we switching frameworks that only ends
         | up slowing us down?
         | 
         | Spring Framework is a big framework, that solves many problems.
         | It may be overwhelming the first year, but with a few years
         | experience you do not want to switch, because other frameworks
         | lacks or has ugly hacks for many Spring Framework features.
         | 
         | Other advantages, massive access to experts for hire. There are
         | literally thousands of Spring Framework developers in my area.
         | Also, since I am an expert myself, I can share my knowledge
         | between these people, and together we are improving our
         | knowledge at a rate other frameworks cannot offer and this
         | gives us a massive innovation pace that brings increased
         | business value.
         | 
         | The next time I will try another web framework, I need to see
         | that my productivity gets improved immediately. Otherwise, is
         | just noise.
        
       | winrid wrote:
       | Needs more code examples in the readme.
        
         | rattray wrote:
         | Agreed, it's hard to get a sense for what using this would
         | actually be like. Prose describing code just doesn't work for
         | most developers, myself including.
        
         | moring wrote:
         | I added a simple example to the README that shows for to build
         | a single-endpoint API and also how to do things in JSON with
         | the type system instead of annotations.
        
           | nunobrito wrote:
           | Moring, this is looking really simple to adopt, good work.
           | 
           | First question: is there maven already available for this
           | library? Mostly to ease upgrades in the future.
           | 
           | Second question: what strategy would you recommend for adding
           | https on the communication channel?
           | 
           | Thanks.
        
             | moring wrote:
             | Hi, thanks for your feedback.
             | 
             | Maven integration is not there yet. Gradle makes it easy to
             | generate a POM, but I have honestly never published
             | anything to central, and I don't even know what is
             | necessary for that. I have added this to my to-do list.
             | 
             | HTTPS is something I won't have to deal with myself,
             | because I'll be running it in a context (Cloud Run on
             | Google Cloud Platform) with external HTTPS termination. If
             | you have any chance to do the same, I'd highly recommend
             | it, because you can use standardized solutions. Other than
             | that, the current version runs on embedded Jetty, but is
             | actually just a servlet code-wise, so any servlet container
             | would do. Again, this is not possible out-of-the-box
             | (unless you don't mind cloning grumpyrest and modifying the
             | code), but likely will be in the future. Once you can use a
             | standard servlet container, such as Tomcat, you can just
             | follow the normal how-tos on how to add HTTPS.
        
           | winrid wrote:
           | Much better! By the way, how does it compare to Spark?
           | 
           | https://sparkjava.com/
        
         | moring wrote:
         | Thank you for the feedback. I'll keep that in mind for the next
         | version.
        
       | ivan_gammel wrote:
       | It is an interesting experiment that would require some work
       | before I would see it in my production environments. First of
       | all, in bigger projects dependency injection is going to happen
       | one or another way. Having all controllers implementing some
       | simple interface and thus avoiding overhead of runtime mapping is
       | fine. How can we map 50-100 endpoints and wire persistence etc
       | without a god class or a lot of user code? Looks like a case for
       | at least compile-time reflection and source level annotations
       | (micronaut).
       | 
       | Second, a small suggestion: the only difference between null and
       | empty string or any other special value is that null fails
       | faster. Client code that is not fully aware of all allowed
       | special cases is going to fail at some more obscure point, so
       | user of the data model must ensure that all cases are handled
       | adequately. Offering to null of all those cases a preferential
       | treatment with a special class is IMO just adding verbosity to
       | the code with longer declarations and unwrapping. I would keep
       | only OptionalField and rename it to Maybe<T> - it does have
       | semantics justifying a wrapper. Constructors can enforce non-null
       | constraint and user code can handle null or other cases in a
       | traditional way.
        
       | suchar wrote:
       | The main issue with Java-based projects (possibly using Spring)
       | is the amount of existing resources: there are thousands/millions
       | of example projects, code snippets, answers on StackOverflow etc.
       | and majority of them is very old (as far as software development
       | is considered). Even fresh resource are often using outdated
       | techniques.
       | 
       | Modern Java is pretty good (although Kotlin is a bit cleaner
       | IMO), but you should really use Spring documentation (if you are
       | using Spring) and avoid code snippets from SO/Github.
        
         | moring wrote:
         | The issues that I see with Spring aren't really code snippets
         | from SO, but the fact that annotations drop all the advantages
         | that Java had as a language. Just two examples:
         | 
         | When you create an Object of the wrong class and try to pass it
         | to a method, you get a compile-time error. When you use the
         | wrong annotation, nothing happens during startup of your
         | application... but you don't even know at which point in time
         | something should happen. Or if.
         | 
         | When a method you call throws an exception, you can run the
         | application in a debugger and single-step into the method, then
         | single-step until the exception gets thrown. This doesn't
         | always just solve the problem but more often than not it gives
         | you a good indication. If an exception gets thrown due to an
         | annotation, you get an enormous stacktrace from some code you
         | have never seen and didn't even know was run, or why it was
         | run, from a thread you have never seen, complaining about wrong
         | parameters that you have never seen, passed from another method
         | you have never seen.
        
           | nunobrito wrote:
           | These is just (another) reason for avoiding Spring. Whenever
           | I'm tempted because of XY module that looks good, I'm
           | reminded with feedback like yours that things are still the
           | same.
           | 
           | Oh well, will continue using java far away from that
           | framework.
        
           | atomicnumber3 wrote:
           | I love java, and especially modern java, but I also love Ruby
           | and python and the majority of my web dev has been in Rails.
           | 
           | I'm now working on a Java side project, and while typically
           | in the past I've just done the backend in java and then used
           | rails for the web UI and had them share a DB, I'm trying to
           | see if I can use Java for the web part without going insane.
           | 
           | Part of the problem is that historically the big players in
           | java web are HUGE enterprises that was to be able to have 50
           | teams all do a small part of a backend in parallel and then
           | just deploy them all together. Thus was born the servlet API
           | and application servers.
           | 
           | But there's so many assumptions and bizarre requirements that
           | come out of trying to do this perverse form of engineering
           | that the whole thing ends up nigh unusable for someone who
           | could otherwise just "rails g" 85% of their project.
           | 
           | Jetty has always struck me as a bit of a middle man where if
           | you want, you can do the servlet thing but it's also a
           | production grade application framework that doesn't force you
           | to do the java EE dance if you don't want to. Though there is
           | some leakage. But it's a lot less magical than Spring and is
           | also _just_ the http server bits, not the rest of the db and
           | view and etc.
           | 
           | But since virtual threads are pretty stable now, I _really_
           | want to use them, and jetty is the first reasonably complete
           | and robust option that seems to have included support for
           | them.
           | 
           | So - you know how these things go. I'm currently writing an
           | HTTP url path router that supports the rails syntax from
           | routes.rb. And then I'm going to write a Handler
           | implementation that wrangles all the database stuff and does
           | convenient/terse parsing of params (like how rails folds path
           | params, url params, and form params into a single params
           | object) and rendering of responses (so I can render a json
           | object without having to call Content.Sink.[...] and use gson
           | all over the place.
           | 
           | It's meant to all be very non magical and you can step
           | through the code in a debugger and not see a billion
           | reflective invocations of methods. And I'm hoping I can make
           | the API of my Handler convenient enough that you don't regret
           | that it's not annotation magic-based.
           | 
           | Also - I know there are various attempts at easier/less
           | annoying Java web things like vertx and such, but they a)
           | don't support virtual threads yet, and b) many of them are
           | small enough im worried they'll rot eventually. Jetty
           | meanwhile isn't going anywhere.
        
           | delusional wrote:
           | Another one of my favorites. When you make an API for a
           | library it's usually pretty clear what the user can do with
           | it. When you make an annotation API for a spring library it's
           | evidently impossible to expect how the user will use it.
           | Random feature of a library won't work because the context
           | you're using their annotation in aren't compatible with
           | whatever assumptions they made.
        
           | paulddraper wrote:
           | > you get an enormous stacktrace from some code you have
           | never seen and didn't even know was run, or why it was run,
           | from a thread you have never seen, complaining about wrong
           | parameters that you have never seen, passed from another
           | method you have never seen
           | 
           | Aka using a library
        
             | antonvs wrote:
             | That's more typical of a framework than a library. This is
             | one of the areas where the difference becomes clear: with a
             | library, you will see your own methods in the call stack
             | somewhere, and be able to tell what got called and why;
             | with a framework, it's often not so simple.
        
         | kmac_ wrote:
         | Spring WebClient pseudo streaming interface is atrocious. Java
         | has switch expression with pattern matching, but no, they had
         | to create something completely perpendicular to the language.
         | "Hey, let's reinvent a wheel, but it will be our _better_ wheel
         | " - and now we have a square egg.
        
           | horsestaple wrote:
           | WebClient predates pattern matching.
        
       | Phelinofist wrote:
       | Looks a bit like Spark - https://github.com/perwendel/spark
        
         | moring wrote:
         | I don't know why this was downvoted. Spark was actually one of
         | the alternatives I considered before I concluded that I
         | couldn't find any framework that suits me, and started writing
         | grumpyrest.
         | 
         | The main issue I had with spark was, from what I could see, the
         | lack of good JSON support. A major part of grumpyrest is its
         | JSON serialization/deserialization framework (it's roughly half
         | of the whole codebase!) which applies the same principles as
         | grumpyrest does for REST, to JSON. (I even called it grumpyjson
         | in anticipation that I might one day break this out as a
         | standalone project).
         | 
         | Now don't get me wrong, I consider Jackson a high-quality
         | framework, and I like very much how its author takes care of
         | even the smallest details. It's not at all like Spring.
         | However, in making it work as I wanted there were too many
         | things that I could not solve to my satisfaction -- I could not
         | abstract them in a way that was truly re-usable, and every API
         | method would have to deal with these things again. This was
         | even more true for Gson. So in the end, I used them (Gson, to
         | be precise) as a low-level JSON library that basically
         | translates between a JSON AST and serialized JSON, and did the
         | high-level mapping to application classes myself.
        
           | marginalia_nu wrote:
           | Hmm, a lot of my endpoints (in Spark) tend to look like
           | public SomeService() {
           | Spark.post("some/endpoint", this::someEndpoint,
           | gson::toJson);
           | Spark.get("another/endpoint/:id". this::anotherEndpoint,
           | gson::toJson);                //...              }
           | private ResponseType someEndpoint(Request request, Response
           | response) {                Something some =
           | gson.fromJson(request.body(), Something.class);
           | return new ResponseType(some.a, logic(some));             }
           | private ResponseType2 anotherEndpoint(Request request,
           | Response response) {                return new
           | ResponseType2(otherLogic(request.param("id"));             }
           | 
           | Like yeah I guess it could be have more well integrated json
           | support, but it's not like this is heavy in boilerplate, and
           | I do think it balances the need to occasionally access more
           | low-level aspects of the HTTP stack well.
           | 
           | Not to detract from your project, of course.
        
         | kasthack wrote:
         | More like ASP.NET minimal APIs[0]
         | 
         | [0] -- https://learn.microsoft.com/en-
         | us/aspnet/core/fundamentals/m...
        
       | krzyk wrote:
       | This is quite similar to Takes
       | (https://github.com/yegor256/takes).
       | 
       | I like both, look cleaner then all the annotation based ones.
        
         | moring wrote:
         | I did not know Takes, and I'll definitely have a closer look.
         | Thanks a lot!
        
       | rbanffy wrote:
       | Not being snarky at all, and saying this as a compliment, this
       | looks like a very pythonic way of building Java services, in
       | particular the "explicit is better than implicit" core principle.
       | 
       | I love this.
        
       | AlexITC wrote:
       | I think we need more simple alternatives like this,
       | https://javalin.io/ is another project I have been following
       | which looks simpler to me.
       | 
       | In any case, thanks for sharing.
        
       | bedobi wrote:
       | I'm a dev with 10+ years professional Java experience and
       | something more like this is what I wish Java had always had.
       | Ignore the other commenters who are being predictably and
       | characteristically harsh and uncharitable.
        
       | social_ism wrote:
       | I find this article weird. Aping Sinatra is not an innovation.
       | Java reflection is essentially zero cost after the JIt runs.
       | 
       | The idea of "this is what you type" to make a REST endpoint or
       | client call is a non-starter and non-issue.
       | 
       | This code in any shop worth its salt has been generate for years
       | off of metadata.
       | 
       | If you are typing in rest code into any language, you are holding
       | it wrong.
        
         | re-thc wrote:
         | > Java reflection is essentially zero cost after the JIt runs.
         | 
         | It is slower and it is cached so uses more memory.
        
         | hamandcheese wrote:
         | All of the harm from reflection happens well before runtime.
        
       ___________________________________________________________________
       (page generated 2023-06-11 23:02 UTC)