You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@solr.apache.org by ge...@apache.org on 2024/01/29 17:03:52 UTC

(solr) branch main updated: SOLR-15781: Document v2 API syntax conventions (#2219)

This is an automated email from the ASF dual-hosted git repository.

gerlowskija pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr.git


The following commit(s) were added to refs/heads/main by this push:
     new 0830423afd7 SOLR-15781: Document v2 API syntax conventions (#2219)
0830423afd7 is described below

commit 0830423afd7d29ce2b8f18643b7fd660ce8dc08e
Author: Jason Gerlowski <ge...@apache.org>
AuthorDate: Mon Jan 29 12:03:46 2024 -0500

    SOLR-15781: Document v2 API syntax conventions (#2219)
    
    Currently, decisions around what our v2 API should look like are
    scattered across a large number of JIRA tickets, PR reviews, and
    spreadsheets.  There's no one place giving an overview of the desired
    syntax (beyond perhaps the general guidance in SIP-16 to move towards
    "REST" where possible).
    
    This commit attempts to address this by introducing docs describing
    some of the conventions used in our v2 API.
    
    Room for improvement remains: parameters conventions remain a big gap,
    as does sourcing to the original rationale behind many of these
    decisions.  But this commit provides a place to build off of, at the
    least.
---
 dev-docs/apis.adoc               | 13 +++----
 dev-docs/v2-api-conventions.adoc | 76 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 83 insertions(+), 6 deletions(-)

diff --git a/dev-docs/apis.adoc b/dev-docs/apis.adoc
index 49ff7df5a11..9e710f7e0a4 100644
--- a/dev-docs/apis.adoc
+++ b/dev-docs/apis.adoc
@@ -51,27 +51,28 @@ Separating the API "definition" and "implementation" in this way allows us to on
 
 Writing a new v2 API may appear daunting, but additions in reality are actually pretty simple:
 
-1. *Create POJO ("Plain Old Java Object") classes as needed to represent the API's request and response*:
+. *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 <<v2-api-conventions.adoc,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` and `PUT` 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 the `api` 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.
-2. *Find or create an interface to hold the v2 API definition*:
+. *Find or create an interface to hold the v2 API definition*:
 ** API interfaces live in the `org.apache.solr.client.api.endpoint` package of the `api` 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.
-3. *Add a method to the chosen interface representing the API*:
+. *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 a `PUT` or `POST` 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.
-4. *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.
+. *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` and `tags` 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 "res [...]
-5. *Create a class implementing the API interface*: Implementation classes live in the `core` gradle project, typically in the `org.apache.solr.handler` package or one of its descendants.
+. *Create a class implementing the API interface*: Implementation classes live in the `core` gradle project, typically in the `org.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 as `CoreContainer`, `SolrQueryRequest`, `SolrCore`, etc.  See the factory-bindings in `JerseyApplications` (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.
-6. *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 in `CoreContainer.load`.
+. *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 in `CoreContainer.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 https://github.com/apache/solr/blob/9426902acb7081a2e9a1fa29699c5286459e1365/solr/api/src/java/org/apache/solr/client/api/endpoint/AddReplicaPropertyApi.java[AddReplicaPropertyApi], an implementing class https://github.com/apache/solr/blob/9426902acb7081a2e9a1fa29699c5286459e1365/solr/core/src/java/org/apache/solr/handler/admin/api/AddReplicaProperty.java[AddReplicaP [...]
 
diff --git a/dev-docs/v2-api-conventions.adoc b/dev-docs/v2-api-conventions.adoc
new file mode 100644
index 00000000000..d2159239694
--- /dev/null
+++ b/dev-docs/v2-api-conventions.adoc
@@ -0,0 +1,76 @@
+== HTTP Paths
+
+Where possible, each v2 API is given an HTTP path that reflects the resource type and/or name most relevant to its functionality.
+Resource types are typically plural nouns such as "aliases", "collections", and "shards".
+Resource names are (typically user-provided) identifiers such as "myAlias", "techproducts", and "shard1".
+For example, `/api/collections` is the HTTP path used for all APIs concerned with collections generally, but that don't involve any one specific collection (e.g. listing all collections).
+APIs that concern themselves with a specific collection use the HTTP path `/api/collections/someCollectionName`.
+
+
+Resource types and names are arranged in the HTTP path such that each path segment is more specific, or "narrower", than the segment that came before.
+This "narrowing" also extends to resources that have an "is part of" or "contains" relationship to one another.
+In these cases all relevant resources and their types are included in the path, with the "contained" or "child" resource following its "parent".
+For example, since replicas always belong to a shard, and shards always belong to a collection, most v2 APIs pertaining to a specific replica use the HTTP path: `/api/collections/specificCollectionName/shards/specificShardName/replicas/specificReplicaName`.
+
+Following these guidelines has given us the following (non-exhaustive) list of v2 API paths, provided here to give a good sense of the paths currently in use and the logic underlying them.
+* `/api/aliases`
+* `/api/aliases/specificAliasName`
+* `/api/aliases/specificAliasName/properties`
+* `/api/aliases/specificAliasName/properties/specificPropertyName`
+* `/api/backups/specificBackupName`
+* `/api/backups/specificBackupName/versions`
+* `/api/backups/specificBackupName/versions/specificVersion`
+* `/api/cluster/nodes/specificNodeName/roles`
+* `/api/cluster/nodes/specificNodeName/roles/specificRoleName`
+* `/api/cluster/properties`
+* `/api/cluster/properties/specificPropertyName`
+* `/api/collections`
+* `/api/collections/specificCollName`
+* `/api/collections/specificCollName/properties`
+* `/api/collections/specificCollName/properties/specificPropertyName`
+* `/api/collections/specificcollName/shards`
+* `/api/collections/specificCollName/shards/specificShardName`
+* `/api/collections/specificCollName/shards/specificShardName/replicas`
+* `/api/collections/specificCollName/shards/specificShardName/replicas/specificReplicaName`
+* `/api/collections/specificCollName/shards/specificShardName/replicas/specificReplicaName/properties`
+* `/api/collections/specificCollName/shards/specificShardName/replicas/specificReplicaName/properties/specificPropertyName`
+* `/api/configsets`
+* `/api/configsets/specificConfigsetName`
+* `/api/cores`
+* `/api/cores/specificCoreName`
+* `/api/node`
+
+=== Unproxied APIs
+
+The last entry on the list above, `/api/node`, exhibits a bit of a special case.
+SolrCloud handles most requests as a distributed system, i.e. any request can be made to any node in the cluster and Solr will proxy or route the request internally in order to serve a response.
+But not all APIs work this way- some functionality is designed to only return data from the receiving node, such as `/api/node/key` which returns a cryptographic key specific to the receiving node.
+Solr will not proxy these requests.
+To represent this distinction the API design uses the idiosyncratic path `/api/node`, to help distinguish these from other node-related APIs.
+
+== HTTP Methods 
+
+Where possible, HTTP methods (colloquially called 'verbs') are used semantically to distinguish between APIs available at the same path.
+For example, the API to delete a collection uses the `DELETE` HTTP method, as in `DELETE /api/collections/specificCollectionName`.
+The API to modify the collection uses the `PUT` HTTP method, as in `PUT /api/collections/specificCollectionName`.
+
+While the best effort is made to use HTTP methods semantically, the v2 API currently restricts itself to the better known HTTP methods: `GET`, `POST`, `PUT`, and `DELETE`.
+In some situations this leads us to eschew a more semantically appropriate verb due to its relative obscurity.
+The most significant example of this is the HTTP method `PATCH`, which according to the HTTP spec is used to indicate a partial update (i.e. a resource modification request which only provides the part to-be-modified).
+Solr's "modify collection" functionality uses partial update semantics, but the v2 API uses `PUT` instead of `PATCH` due to the relative obscurity of the latter.
+
+For use within the v2 API, the four "popular" HTTP methods have the following semantics and implications:
+
+* `GET` - used for non-mutating (i.e. "read only") requests. Most often used to list elements of a particular resource type, or fetch information about about a specific named resource.
+* `POST` - used for non-idempotent resource modifications.
+* `PUT` - used for idempotent resource modifications.
+* `DELETE` - Used to delete or cleanup resource
+
+== Exceptional Cases - "Command" APIs
+
+The pairing of semantic HTTP verbs and "resource"-based paths gives Solr an intuitive pattern for representing many operations, but not all.
+Many Solr APIs cover complex operations that don't map cleanly to an HTTP verb.
+Often these operations were initially conceived of as procedural "commands" and as such are hard to fit into the v2 APIs resource-first model.
+
+Solr's v2 API currently accommodates these "command" APIs by appending the command name (often a verb like "unload", "reload", or "split") onto the otherwise "resource"-based path.
+For example: Solr's core "unload" command uses the API `POST /api/cores/specificCoreName/unload`.