Solr’s codebase currently has a handful of ways of defining APIs. This complexity stems largely from two ongoing transitions: 1. Away from our "v1" APIs and towards the new "v2" API. 2. Away from a legacy API framework towards the off-the-shelf JAX-RS library for implementing our v2 APIs
As we finish these transitions, this complexity should simplify considerably. But in the interim, this document can help guide developers who need to understand or modify APIs in Solr.
APIs in Solr (regardless of whether v1 or v2) can typically be classified as either "per-core" or "cluster" APIs.
Per-core APIs, as the name suggests, typically affect only a single core or collection, usually used to search or analyze that core’s contents in some way.
Implementation-wise, they’re registered on the SolrCore
object itself.
They are configured in solrconfig.xml, which also means they can differ from one core to another based on the configset being used.
Alternatively "cluster" APIs potentially affect the entire Solr instance or cluster.
They’re registered on the CoreContainer
object itself.
It’s much less common to provide configuration for these APIs, but it is possible to do so using solr.xml
.
v1 APIs are the primary way that users consume Solr, as our v2 APIs remain "experimental". Many new APIs are added as "v2 only", however updates to existing v1 APIs still happen frequently.
v1 APIs exist in Solr as implementations of the SolrRequestHandler
interface, usually making use of the RequestHandlerBase
base class.
RequestHandlers have two methods of primary interest:
1. init
, often used to parse per-API configuration or otherwise setup the class
2. handleRequest
(or handleRequestBody
if using RequestHandlerBase
), the main entrypoint for the API, containing the business logic and constructing the response.
While they do define many aspects of the endpoint’s interface (e.g. query parameters, request body format, response format, etc.), RequestHandler’s don’t actually specify the URL path that they’re located at.
These paths are instead either hardcoded at registration time (see CoreContainer.load
and ImplicitPlugins.json
), or specified by users in configuration files (typically solrconfig.xml
).
v2 APIs are currently still "experimental", and not necessarily recommended yet for users. But they’re approaching parity with v1 and will eventually replace Solr’s "RequestHandler"-based APIs.
New v2 APIs in Solr are written in compliance with "JAX-RS", a library and specification that uses annotations to define APIs. Many libraries implement the JAX-RS spec: Solr currently uses the implementation provided by the "Jersey" project.
These v2 API definitions consist of two parts: a JAX-RS annotated interface in the api
module "defining" the API, and a class in core
"implementing" the interface.
Separating the API "definition" and "implementation" in this way allows us to only define each API in a single place, and use code generators to produce other API-related bits such as SolrJ code and ref-guide documentation.
Writing a new v2 API may appear daunting, but additions in reality are actually pretty simple:
-
Agree on Endpoint Syntax: Before implementing, developers should generate consensus around what the new API will look like. General guidelines and conventions for the v2 API syntax are described here.
-
Create POJO ("Plain Old Java Object") classes as needed to represent the API’s request and response:
-
POJOs are used to represent both the body of the API request (for some
POST
andPUT
endpoints), as well as the response from the API. -
Re-use of existing classes here is preferred where possible. A library of available POJOs can be found in the
org.apache.solr.client.api.model
package of theapi
gradle project. -
POJO class fields are typically "public" and annotated with the Jackson
@JsonProperty
annotations to allow serialization/deserialization. -
POJO class fields should also have a Swagger
@Schema
annotation where possible, describing the purpose of the field. These descriptions are technically non-functional, but add lots of value to our OpenAPI spec and any artifacts generated downstream.
-
-
Find or create an interface to hold the v2 API definition:
-
API interfaces live in the
org.apache.solr.client.api.endpoint
package of theapi
gradle project. Interfaces are usually given an "-Api" suffix to indicate their role. -
If a new API is similar enough to existing APIs, it may make sense to add the new API definition into an existing interface instead of creating a wholly new one. Use your best judgement.
-
-
Add a method to the chosen interface representing the API:
-
The method should take an argument representing each path and query parameter (annotated with
@PathParam
or@QueryParam
as appropriate). If the API is aPUT
orPOST
that expects a request body, the method should take the request body POJO as its final argument, annotated with@RequestBody
. -
Each method parameter should also be annotated with the Swagger
@Parameter
annotation. Like the@Schema
annotation mentioned above,@Parameter
isn’t strictly required for correct operation, but they add lots of value to our OpenAPI spec and generated artifacts. -
As a return value, the method should return the response-body POJO.
-
-
Futher JAX-RS Annotation: The interface method in step (3) has specified its inputs and outputs, but several additional annotations are needed to define how users access the API, and to make it play nice with the code-generation done by Solr’s build.
-
Each interface must have a
@Path
annotation describing the path that the API is accessed from. Specific interface methods can also be given@Path
annotations, making the "effective path" a concatenation of the interface and method-level values.@Path
supports a limited regex syntax, and curly-brackets can be used to create named placeholders for path-parameters. -
Each interface method should be given an HTTP-method annotation (e.g.
@GET
,@POST
, etc.) -
Each interface method must be marked with a Swagger
@Operation
annotation. This annotation is used to provide metadata about the API that appears in the OpenAPI specification and in any artifacts generated from that downstream. At a minimum,summary
andtags
values should be specified on the annotation. (tags
is used by our SolrJ code generation to group similar APIs together. Typically APIs are only given a single tag representing the plural name of the most relevant "resource" (e.g.tags = {"aliases"}
,tags = {"replica-properties"}
)
-
-
Create a class implementing the API interface: Implementation classes live in the
core
gradle project, typically in theorg.apache.solr.handler
package or one of its descendants.-
Implementing classes must extent
JerseyResource
, and are typically named similarly to the API interface created in (2) above without the "-Api" suffix. e.g.class AddReplicaProperty extends JerseyResource implements AddReplicaPropertyApi
) -
Solr’s use of Jersey offers us some limited dependency-injection ("DI") capabilities. Class constructors annotated with
@Inject
can depend on a selection of types made available through DI, such asCoreContainer
,SolrQueryRequest
,SolrCore
, etc. See the factory-bindings inJerseyApplications
(or other API classes) for a sense of which types are available for constructor injection. -
Add a body to your classes method(s). For the most part this is "normal" Java development.
-
-
Register your API: APIs must be registered to be available at runtime. If the v2 API is associated with an existing v1 RequestHandler, the API class name can be added to the handler’s
getJerseyResources
method. If there is no associated RequestHandler, the API should be registered similar to other APIs inCoreContainer.load
.
A good example for each of these steps can be seen in Solr’s v2 "add-replica-property" API, which has a defining interface AddReplicaPropertyApi, an implementing class AddReplicaProperty, and the two POJOs AddReplicaPropertyRequestBody and SolrJerseyResponse.
While we’ve settled on JAX-RS as our framework for defining v2 APIs going forward, Solr still retains many v2 APIs that were written using an older homegrown framework.
This framework defines APIs using annotations (e.g. @EndPoint
) similar to those used by JAX-RS, but lacks the full range of features and 3rd-party tooling.
We’re in the process of migrating these API definitions to JAX-RS and hope to remove all support for this legacy framework in a future release.