Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Possibility to put all related Microprofile-Annotations into Bundles #672

Open
funkrusher opened this issue Nov 3, 2024 · 5 comments
Open

Comments

@funkrusher
Copy link

funkrusher commented Nov 3, 2024

As seen in the following Stackoverflow-Question the Annotations could become very big and make it hard to see the REST-Endpoint-.Code:

For this reason (amon other reasons) it would be helpful to let the developer create "Bundles" where he can collect all relevant Annotations.

For many Annotations this is already possible. For example: The APIResponse and Parameter Annotation can be bundled together in a Meta-Annotation, that the developer creates himself for his project / his requirements. For example:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@APIResponses(value = {
    @APIResponse(responseCode = "200", description = "Request Successful"),
    @APIResponse(responseCode = "500", description = "Server unavailable")
})
public @interface GetApiResponses {
}

For following Annotations this is not possible though, which makes this approach not good enough to be fully usable (for all cases):

  • @Operation
  • @RequestBody
@Azquelt
Copy link
Member

Azquelt commented Nov 5, 2024

This would be similar to how you can compose Jakarta interceptor bindings: https://jakarta.ee/specifications/interceptors/2.2/jakarta-interceptors-spec-2.2#interceptor_binding_types_with_additional_interceptor_bindings

@funkrusher
Copy link
Author

funkrusher commented Nov 5, 2024

@Azquelt
yes exactly, that would be great.

For an example of such a "composed Jakarta interceptor binding" see the accepted answer in following Stackoverflow:

@Azquelt
Copy link
Member

Azquelt commented Nov 5, 2024

We discussed this issue on this week's call and did consider the following alternatives:

  • For many types, including request bodies and responses, it's possible to define it once as a component and then reference it wherever it's used. If there's a common error response or a common type of request, this would reduce repetition.
  • A filter is also an option if the same processing can be applied to all instances of a type. For example if you want to add a standard error response to all operations in your API, or add the same descriptions if an operation has a particular set of parameters.

We also had the following concerns:

  • Having "bundling" annotations allows there to be multiple annotations of the same type applied where previously this wasn't possible. How should these multiple annotations get merged?
    • If there's an @Operation on the method and an @Operation on a "bundling" annotation, is the @Operation from the bundling annotation ignored or do they get merged together somehow?
    • If they get merged, what are the rules for doing so? Remember that you can't rely on being able to tell the difference between an annotation field which is not set and one which is set to the default value.
    • What about if there are two bundling annotations and both have an @Operation annotation on them?
    • I use @Operation as an example here because you listed it as one of your use cases and it's not repeatable. Only allowing one @Operation to take effect would be valid, but I can't see a use-case for applying exactly the same @Operation annotation to multiple methods.

@funkrusher
Copy link
Author

funkrusher commented Nov 5, 2024

i quote you:

... it's possible to define it once as a component and then reference it wherever it's used...

is the "define it once as a component" possible in the java-world (via annotation and/or classes) ?
I try to avoid the hybrid-approach, and would like to keep all the openapi-specifications within the java-code itself (close to the source-code)

What i had in mind was:

  • Create a separate "Class" / "Annotation" for every "Operation" or Rest-Method

For example:

  • CreateProduct
  • GetProduct
  • PostProduct
  • PutProduct

Annotate each of the methods with the class.
Keep all openapi-specification relevant Annotations directly within this class.

I would like to profit from the Typesafety of the Java-World and i find the Syntax of the Open-API Specification File itself to be hard to read or manage.

But maybe i will try the hybrid approach sometimes, because with too many annotations the REST-Endpoints become hard to manage.

But i understand your concerns of merging annotations, and there are also non-openapi-specific annotations that are still relevant for the openapi-docs.

@Azquelt
Copy link
Member

Azquelt commented Nov 12, 2024

... it's possible to define it once as a component and then reference it wherever it's used...

is the "define it once as a component" possible in the java-world (via annotation and/or classes) ?

Yes, you would do it like this:

@OpenAPIDefinition(components = @Components(
    @APIResponse(
        name="getProductResponse"
        responseCode = "200",
        description = "Product information",
        @Content(
            mediaType="application/json",
            Schema = @Schema(implementation = Product.class),
        )
    )
))

and then reference it from your operation method:

@APIResponse(ref = "getProductResponse")
// ... any other annotations
public Product getProduct(@PathParam("id") String id) {
    // ...
}

What i had in mind was:

  • Create a separate "Class" / "Annotation" for every "Operation" or Rest-Method

For example:

  • CreateProduct
  • GetProduct
  • PostProduct
  • PutProduct

Annotate each of the methods with the class. Keep all openapi-specification relevant Annotations directly within this class.

Hmm, I guess it doesn't really work for that because it's designed for the finer-grained parts which are reused throughout your application (e.g. a common error response structure) and as such there's no support for storing operations within a component.

Wanting to use annotations but keep them separate from the code they relate to isn't really something I'd considered, though it's understandable as they can get quite long. In most cases, I'd expected that most of the detail for the operation could be inferred from the method itself and its the Jakarta REST annotations, or from other related objects, such as the return type or any registered exception handlers. I guess if you need to add longer descriptions and examples to each one then that will add up to quite a lot of space.

At the moment, I think the hybrid approach is likely to work best for you.

We need to think more about whether we should enable storing annotations away from the code they relate to. On the one hand, that is not generally how annotations work, they store a relatively small amount of data close to the code that it relates to. On the other hand, part of the problem here is that we're storing so much information in the annotation and they're not really suited to that task (we already run into issues expressing certain structures because an annotation cannot refer to another instance of itself, whereas OpenAPI Schemas can have other schemas within them).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants