Skip to content

Instantly share code, notes, and snippets.

@adamw
Created October 12, 2017 12:36
Show Gist options
  • Save adamw/6248904a39eb3fedb89d3c9a1752ba10 to your computer and use it in GitHub Desktop.
Save adamw/6248904a39eb3fedb89d3c9a1752ba10 to your computer and use it in GitHub Desktop.
// Hello.java
public class Hello {
public String helloWorld() {
return "Hello World";
}
}
// HelloEndpoints.java
public class HelloEndpoints {
private Endpoint helloWorldEndpoint(Hello hello) {
return Endpoint
.withPath("/hello")
.method(GET)
.produces(MediaType.TEXT_PLAIN)
.invoke(hello::helloWorld);
}
public List<Endpoint> endpoints(Hello hello) {
return Arrays.asList(helloWorldEndpoint(hello), ...);
}
}
@adamw
Copy link
Author

adamw commented Dec 28, 2018

Thanks for the comments!

First of all I think it's worth noting that with or without annotations, the approach is basically the same:

  1. we describe the endpoints. In one approach, we might use the restricted language of annotations (@Path, @RequestMapping etc.). In another, we use a "normal" language, such as Scala, Java or Kotlin. From a high level, the result in both cases is the same: a data structure containing information about your endpoints
  2. we create an interpreter, which turns the description into an actual endpoint. With annotations, this is done by e.g. Spring, first using runtime reflection to read the description, and then exposing the endpoint based on that information. Without annotations, you have to explicitly pass the data structure you created to the interpreter.

There might be many interpreters, both when using annotations and when not. For example, you can expose endpoints based on annotations, or generate documentation. Without annotations, you can do the same; as someone pointed out, that's what Tapir is aspiring to do.

The big advantage of the "normal code" approach is that you have much more flexibility when creating the endpoint description, unlike being unnecessarily constrained with the limitation of annotations. However, annotations do have the advantage of being able to reference fields or methods - something that is not directly possible in Java (but is possible in Scala with shapeless/macros). But then again, you might not have to reference fields/methods in the first place - depend on how you define the API to create the descriptions.

Summing up: I wouldn't use annotations for creating endpoint descriptions. Normal code is much more flexible, allows operating on values in the base language (just like any other value) and can be interpreted to an actual endpoint in the same way as with the annotations approach. Annotations do have their usages, but mainly for compile time (for example, apart from things such as @SuppressWarnings, in Scala they are often useful for directing compile-time code generation via macros).

@adamw
Copy link
Author

adamw commented Dec 28, 2018

Btw. as for the specific example with extracting path parameters, I think a Java API could be sth like this:

return Endpoint
  .withPath("hello")
  .withPathSegment(Parsers.string)
  .withPathSegment(Parsers.integer)
  .withBody(Parsers.json)
  .method(PUT)
  .produces(MediaType.APPLICATION_JSON)
  .invoke(Logic::helloWorld);

Here, the first invocation of withPathSegment would be on Endpoint0, and yield an Endpoint1<String> (an endpoint, which requires a single string parameter - in this case, read from the path, but could be read from the query as well).

The second would be on Endpoint1<String> and yield an Endpoint2<String, Integer>, etc. Then, the invoke method can be properly typed to expect a method reference with the right signature.

That's of course just a sketch, and would require the library author to define a number of EndpointN classes (or maybe auto-generate them?). In Scala, you have have a single Endpoint class which can accumulate the input/output parameters with some combinators (again, see Tapir for details).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment