Skip to content

Break api into 2 parts #31

Description

@edeandrea

Is there a way to break this into 2 parts - the pure api and the implementation?

Downstream frameworks (like Spring & Quarkus) may want to use their own Jackson versions & their own http implementations. Using the client as-is does not allow for this customization.

I maintain the Docling Java client and we have successfully done that - separated into 2 artifacts, the api & the client. Downstream frameworks like Spring & Quarkus only depend on the API and provide their own implementations of it. See https://github.com/docling-project/docling-java/tree/main/docling-serve

The Docling api also supports both Jackson 2 & 3 simultaneously.

We use a ServiceLoader abstraction, so you never actually have to use the implementation imports directly. Everything is driven through the API.

Apps that use Jackson 3 are unable to use this api at all.

I'm happy to help contribute if there is interest.

Activity

  1. edeandrea commented on May 7, 2026

    @edeandrea
    Author

    Same with the async operations. They should return CompletionStage (the API) instead of CompletableFuture (the implementation)

  2. Steffen911 commented on May 19, 2026

    @Steffen911
    Member

    @edeandrea I'm not sure I fully understand what the advantage is. What would be unlocked by such a separation?
    Additionally, our APIs are currently auto-generated using fern, essentially an OpenAPI spec. If there is a stable way to do this, we'd be happy to explore.

  3. edeandrea commented on May 19, 2026

    @edeandrea
    Author

    The current approach has 3 limitations:

    1. It forces OkHttp on the user of the api
    2. It forces Jackson v2 on the user of the api
    3. The async operations force CompletableFuture (the concrete implementation) rather than CompletionStage (the definition).

    I wanted to use this API, but unfortunately I can't in its current state, as the dependency on OkHttp is non-starter. I've had to take the raw OpenAPI spec and use openapi-generator to generate my own implementation. Forcing CompletableFuture on me is also a non-starter.

    Other frameworks (like Spring & Quarkus) don't use OkHttp. Spring 4 is based on Jackson 3, not Jackson 2. They also have their own async frameworks (Project reactor for Spring, Mutiny for Quarkus).

    The separation would allow for the "models" and an API interface to be part of the API, and then an implementation module which could have a default implementation (OkHttp, Jackson, etc). Downstream frameworks (such as Spring/Quarkus) could use the api and define their own implementations using the relevant technologies from their ecosystems (in Spring - RestClient, in Quarkus - Jakarta Rest Client).

    I ran into this exact same problem when building the Docling Java API. We've done it in a way where the API (the API interface & corresponding model pojos) are defined in an API module, and the reference implementation (based on the built-in Java HttpClient) are in a client module. The API module supports both Jackson 2 & 3 simultaneously.

    Note

    If you aren't aware, Jackson 2 -> 3 has many breaking changes, including the Java package name structure.

    The separation I did in Docling is done using the Java ServiceLoader mechanism - the API module exposes a ServiceLoader spi which the client module implements. This mechanism is what allows downstream frameworks to provide their own implementations (see https://github.com/quarkiverse/quarkus-docling/blob/main/runtime/src/main/resources/META-INF/services/ai.docling.serve.api.spi.DoclingServeApiBuilderFactory for the Quarkus implementation).

    Because the api is just an interface, it can be re-implemented using whatever tech stack the client wants.

    Same with the async api. By forcing CompletableFuture you are forcing a concurrency implementation on the end user, rather than allowing the end user to adapt to something that implements CompletionStage (Mono/Flux for Spring, Uni/Multi for Quarkus).

    our APIs are currently auto-generated using fern

    I'm not familiar with fern honestly, so I can't offer any insight. I'm sure the separation will require some thought and proper design - something I went through late last year with Docling (docling-project/docling-java#2 & docling-project/docling-java#141).

    I'm open to helping/contributing if this is an initiative you'd like to take on.

  4. RyanDeleurme commented on May 19, 2026

    @RyanDeleurme

    We also ran into a similar issue with this. Our company's OkHttp version is behind the Langfuse package, so we had to use two different apis:

    • the current package implementation for all GETs
    • A separate Feign client implementation for any requests that require a body, since our OKHttp versions were different, there were breaking changes
  5. Steffen911 commented on May 20, 2026

    @Steffen911
    Member

    @edeandrea We would definitively appreciate any assistance in improving this package. For us the main requirement will be that most of the package can be auto-generated to keep maintenance effort on new API routes etc minimal.

    Thank you a lot for the added context! This really puts it into perspective and makes a lot of sense.

  6. pinned and unpinned a comment on May 20, 2026
  7. edeandrea commented on May 20, 2026

    @edeandrea
    Author

    We would definitively appreciate any assistance in improving this package

    Yay! I'd love to help!

    For us the main requirement will be that most of the package can be auto-generated to keep maintenance effort on new API routes etc minimal.

    As I mentioned, I'm not familiar with fern. I haven't looked at things in depth, but my initial thinking is that we could use something like OpenAPI generator to generate the models. We may need to build a custom template to generate the api interface (without implementation) and to be able to support both Jackson 2 and 3 simultaneously. I'd have to dig a little deeper.

    Then in the client module you could again use openapi generator to generate just the api implementation. The model classes would be in the api. Again, we may need to build a custom template to make the generated api implement the interface in the api.

    You'll probably have to hand-code the ServiceLoader spi, but thats not a big deal and is easily maintainable.

    With Docling we decided not to generate the models from the spec, but to hand code things. It was a deliberate decision, mostly based on the fact that docling-serve doesn't do true api-first development - they generate the OpenAPI spec from the FastAPI runtime. If you're doing "true" contract-first, then generating might work better. We were also under a time crunch and it was simply faster to hand-code the models rather than invest in custom openapi-generator templates since the api is relatively small.

  8. edeandrea commented on May 26, 2026

    @edeandrea
    Author

    @Steffen911 Would it be possible to describe the process you use to generate this api currently?

    I've read https://github.com/langfuse/langfuse-java#updating but it seems there are some manual steps in there. Would it be possible to provide a step-by-step set of instructions on how to generate the api?

  9. ThomasVitale commented on Jun 3, 2026

    @ThomasVitale

    I really like @edeandrea's proposal and PR, which I hope will be merged soon. It will enable more integration possibilities for Langfuse in the Java ecosystem.

  10. Steffen911 commented on Jun 6, 2026

    @Steffen911
    Member

    @edeandrea I'm fine if we skip the fern-based client generation and move to something that takes https://github.com/langfuse/langfuse/blob/main/web/public/generated/api/openapi.yml as a baseline as long as this is well documented and easy to execute based on the README in this repository. Will take a look at the PR!

    Thank you so much for all the effort in this 🙏

  11. edeandrea commented on Jun 6, 2026

    @edeandrea
    Author

    @edeandrea I'm fine if we skip the fern-based client generation and move to something that takes https://github.com/langfuse/langfuse/blob/main/web/public/generated/api/openapi.yml as a baseline as long as this is well documented and easy to execute based on the README in this repository. Will take a look at the PR!

    Thank you so much for all the effort in this 🙏

    That's exactly what I did. Everything here starts from the openapi document.

    I did dd a follow-on issue that addresses some gaps I found in the openapi document.

  12. edeandrea commented on Aug 13, 2026

    @edeandrea
    Author

    Hi @Steffen911 - any appetite for merging #36 ?

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions