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 <[email protected]>
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`.

Reply via email to