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

Compose "api-*" artifacts contents (in terms of backend choice) more practical #30

Closed
Jeffset opened this issue Mar 20, 2023 · 1 comment · Fixed by #45
Closed

Compose "api-*" artifacts contents (in terms of backend choice) more practical #30

Jeffset opened this issue Mar 20, 2023 · 1 comment · Fixed by #45
Labels
api Related to introducing new API or changing existing one enhancement New feature or request
Milestone

Comments

@Jeffset
Copy link
Contributor

Jeffset commented Mar 20, 2023

As of now, there are the following "public api" artifacts:

  1. api-public - just the annotations (API), no entry-point (EP - Yatagan object).
  2. api-compiled - depends on api-public, adds Yatagan which loads compiled implementations.
  3. api-dynamic - depends on api-public, adds Yatagan which loads dynamic implementations (can load compiled if configured) and comes with extra API for configuring reflection-specific things.

Now this "layout" seems to work, but apparently has some drawbacks. There's no real problem for the application developers, as they can just use the correct backend-specific artifact and be done with it. But when it comes to library development, some unpleasant things begin to surface: turns out it's not okay for library authors to depend on any backend-specific artifact, as they would actually force the backend usage on all the clients this way. One can't have multiple backends on the runtime classpath, as they would conflict. And the only backend-agnostic artifact is api-public which doesn't contain any Yatagan EPs, so there's no way for a library to create its own components in its code. If the library is not published and serves only as a way to organize code in the multi-module project, developers can get away with twiddling with tools like compileOnly/runtimeOnly and so on, but it is in no way a general solution.

So here I propose to rearrange the api/EP/loader code so its more practical to use and general integration logic can be formulated, maybe even with Gradle plugin.

New structure (draft)

  1. api:public - contains both API and EP*. EP has the following behavior - they try to locate a dynamic loader class (once, the result is cached - very fast). If the loader is present, EP delegates to it. If not - EP loads the compiled implementations.
  2. rt:loader - the dynamic loader class and the reflection engine - loads and serves reflection-based implementations.

*It's not yet clear how to handle reflection-specific API - should they be included into api:public and be no-op by default and delegate to the rt:loader?

This way every project module can depend on api:public and its code will compile regardless. And then, depending on the usage and settings, additional configuration can be done - adding code generators or rt:loader as runtimeOnly. This way published libraries can depend on api:public and ship with generated implementations of their internal components. While clients can still include rt:loader and use reflection backend for themselves.

If the project depends on a library, that uses yatagan internally and the project itself chooses to use rt:loader, then we probably don't want for this to affect the library behavior. In other words, how exactly should api:public's loader logic operate in regards to compiled implementations? Should it load them unconditionally if detected and try to use dynamic loader only if no compiled implementation is present? This works well in theory and preserves library behavior (if it comes with generated impls). But practice shows that build systems can leave stale generated code in the runtime classpath after a developer switched to reflection, e.g. via a build flag, then the loader would still use compiled impl instead of reflection. Need to give this some more thought.

@Jeffset Jeffset added enhancement New feature or request api Related to introducing new API or changing existing one labels Mar 20, 2023
@Jeffset Jeffset added this to the 2.0.0 milestone Mar 20, 2023
@Jeffset
Copy link
Contributor Author

Jeffset commented Mar 20, 2023

There's another, "less breaking" option:

  1. Yatagan EP is moved into api-public.
  2. api-compiled is an empty artifact that just depends on api-public and is, essentially, deprecated.
  3. api-dynamic depends on rt:engine and provides loader class implementation.
    The crucial thing here is that api-compiled would be compatible with api-dynamic in a way, that the latter would take precedence if no compiled implementations are available.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api Related to introducing new API or changing existing one enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant