title | perex | date | author | preferredLang |
---|---|---|---|---|
gRPC |
The gRPC API was designed to provide a very powerful and unified way to control the evitaDB database from different
programming languages, using the existing evitaDB Java API.
|
26.7.2023 |
Ing. Tomáš Pozler |
java |
evitaDB is manipulated using the contracts exposed in evita_api, specifically the evita_api/src/main/java/io/evitadb/api/EvitaContract.java and evita_api/src/main/java/io/evitadb/api/EvitaSessionContract.java interfaces. The proposed gRPC API aims to define its corresponding services with as little variation as possible, but in some cases it was necessary to adapt the model to the capabilities of gRPC. In the design, it was not possible to model generics, inheritance, or lambda functions directly, but these problems could have been avoided by adapting the defined models at the cost of redundant information.
It is also not possible to call methods on instances of objects located on the server, such as sessions, which are used to perform most data manipulation and retrieval operations. However, these problems can be solved by implementing a driver library with a rich API. The use of these methods on the session object has been replaced by the need to specify the identifier of the user-created session, which must then be passed in the query metadata for all methods based on the EvitaSessionContract interface.
One way to simplify this process is to store the session ID in a shared memory scope (example class
evita_external_api/evita_external_api_grpc/shared/src/main/java/io/evitadb/externalApi/grpc/interceptor/ClientSessionInterceptor.java)
and set it to the metadata with every method call using a evita_external_api/evita_external_api_grpc/shared/src/main/java/io/evitadb/externalApi/grpc/interceptor/ClientSessionInterceptor.java.
In addition to sessionId
, additional identifiers can be set in the metadata for observability.
Specifically, these are the clientId
and requestId
parameters, whose settings are used to add information about the
client that uses the database in a given instance (for example, it can be the name of the application using evitaDB) and
possibly an additional identifier for each query or method executed. Setting and using both of these parameters is
completely optional.
gRPC works on the principle of Remote Method Calling (RPC), i.e. the client uses methods defined in protobuf services in its code and their implementation is called on the server, where the request is processed and the response is sent back to the client. For the above-mentioned contracts evita_api/src/main/java/io/evitadb/api/EvitaContract.java and evita_api/src/main/java/io/evitadb/api/EvitaSessionContract.java there are corresponding services in the form of evita_external_api/evita_external_api_grpc/server/src/main/java/io/evitadb/externalApi/grpc/services/EvitaService.java and evita_external_api/evita_external_api_grpc/server/src/main/java/io/evitadb/externalApi/grpc/services/EvitaSessionService.java with the included methods supporting the same functionality as the base Java API, but in a less convenient way due to the limitations of the gRPC protocol.
Protocol buffers are a way to define the structure of data through messages using supported data types and structures. They are not a replacement for JSON or XML formats used for data serialization, but rather a language-neutral and platform-neutral alternative.
The main benefit of using protocol buffers is that they allow you to represent data in a structured way. Objects that match the defined message types can be serialized into a stream of bytes using the appropriate library.
When combined with the gRPC library, protocol buffers make the serialization process even more straightforward. You don't need to directly manipulate protobuf files, and the gRPC library takes care of compiling protobuf files into native classes and handling the serialization of objects automatically.
All the protobuf files that define the gRPC protocol can be found in a META-INF folder. Below is an example of the evita_external_api/evita_external_api_grpc/server/src/main/java/io/evitadb/externalApi/grpc/services/EvitaSessionService.java design and few selected procedures in the protobuf file evita_external_api/evita_external_api_grpc/shared/src/main/resources/META-INF/io/evitadb/externalApi/grpc/GrpcEvitaSessionAPI.proto:
service EvitaSessionService {
rpc GoLiveAndClose(google.protobuf.Empty) returns (GrpcGoLiveAndCloseResponse);
rpc Query(GrpcQueryRequest) returns (GrpcQueryResponse);
}
together with the definition of the used input/output messages (except for the Empty type, which comes from the collection of standard message types that are part of the protocol and are therefore included in the library):
message GrpcGoLiveAndCloseResponse {
bool success = 1;
}
message GrpcQueryRequest {
string query = 1;
repeated GrpcQueryParam positionalQueryParams = 2;
map<string, GrpcQueryParam> namedQueryParams = 3;
}
message GrpcQueryResponse {
GrpcDataChunk recordPage = 1;
GrpcExtraResults extraResults = 2;
}
All protobuf files that define the gRPC API protocol should always be synchronised between client and server. Although gRPC provides some mechanisms for backward compatibility, there is no guarantee that the client will be able to communicate with the server if the protocol is not the same. Unchanged RPCs should work even if the protocol is not the same, any changes or additions will not work.
Java client provided by us keeps the same versioning scheme as the server library. The server version can be easily
retrieved from the server by calling the ServerStatus
method from the GrpcEvitaManagementAPI
service. You should
try to keep the major/minor version of the client and server libraries the same, but the patch version can differ.
To connect to the server, you need to create a channel and a stub for the service you want to use. You can use a simple gPRC java client or a more powerful client implementation like Armeria, which provides additional features like connection pooling, load balancing, and more. Since we also use Armeria on the server side, we recommend using their client as well.
First, you need to create an evita service from which you can create a session. Second, you need to create a session service, which is a key for all data manipulation operations and sending queries. The session is always bound to a specific catalog and you need to decorate it with evita_external_api/evita_external_api_grpc/client/src/main/java/io/evitadb/driver/interceptor/ClientSessionInterceptor.java which will propagate the session id to the server with each call through the session service in gRPC metadata.
Example of creating Armeria gRPC client and connecting the server
One of the design challenges was to find a way to represent database queries using protobuf capabilities. While it is
possible to define a message type that represents a query and pass the serialized query in a binary representation, we
decided to use the parameterized string form, where the parameters can be replaced by the ?
symbol, and a list of
parameters can be passed with such a query. An alternative is to use named parameters with the @
prefix, which are
used with a unique name in the query and are also embedded in the map as keys, where each named parameter has
a corresponding value. This form is well known in the developer community and is used by many database drivers - namely
in the form of JDBC statements, or named queries
introduced by Spring framework.
In this form, the user submits the query to the server, which parses the string form and creates an object representing the requested query, ready for execution by the database.
Example of creating gRPC channel and a service operating upon it and executing a query
The example uses the convertQueryParam
method from the evita_external_api/evita_external_api_grpc/shared/src/main/java/io/evitadb/externalApi/grpc/query/QueryConverter.java class. A similar method must be implemented in the gRPC client language to register
input parameters of a specific gRPC data type for the query.
The primary purpose of evitaDB is to execute queries. However, as shown in the example above, working with the database in this way is not the most user-friendly experience. Unfortunately, there is currently no support for intellisense or query validation, and we haven't developed any IDE tools to address this limitation.
Yes, there is, and you will not see this kind of usage in our integration tests. Instead, we work with query in its original type-safe form:
Example of alternative gRPC query invocation
However, this approach requires that the query language model be implemented in the target gRPC language, and this requires a significant amount of effort. We always recommend using appropriate client drivers, if they are available for the target language, as this shifts the developer experience to a higher level.
To make the most of the gRPC API, we highly recommend using one of our implemented drivers or building your own tool to facilitate querying. This will significantly improve your experience with evitaDB. Direct use of the gRPC API by developers was not intended, and was designed to be more of a "machine-to-machine" interface.
When you start developing a driver for your preferred language, we recommend that you study the evita_external_api/evita_external_api_grpc/client/src/main/java/io/evitadb/driver/EvitaClient.java driver, which has many things to inspire you for better performance, such as how to handle channel pooling or how to create and use schema caches.
It is also important not to forget about TLS certificates, which need to be set up correctly on both the server and the client side. gRPC communicates over HTTP/2 and data is sent over a TCP connection, and it is generally recommended to use TLS to encrypt bidirectional communication. Our API enforces this, so it is recommended that you read the instructions regarding TLS and set it up on the server if necessary.
If you are considering developing a custom driver, we invite you to reach out to us for support and guidance throughout the process. We are more than willing to provide the necessary feedback, recommendations, and assistance to ensure the seamless functioning of your driver. Your success is important to us, and we are committed to working closely with you to make your driver integration with our API a successful and rewarding experience. Don't hesitate to contact us. We're here to help!
It's important to note that gRPC is designed to be used from any of the supported programming languages using protobuf-based generated classes, optionally with a friendly IDE that provides benefits such as intellisense, so using it is pretty straightforward and there are a lot of resources and guides available on the web. For testing purposes without writing code, i.e. trying out the available services and calling the procedures they provide, it is possible to use classic API testing tools such as Postman or Insomnia, which already support communication with the gRPC API. However, if you're new to gRPC, we recommend the BloomRPC tool, even though it's deprecated, because it's more intuitive and dedicated to this technology. Alternatively, there are many great alternatives for testing gRPC APIs, which can be found here.
You can find all the officially maintained libraries on the gRPC website, from which you can choose a library for your own programming language. Along with a library, you also need to download a suitable protocol compiler, which is not directly included in any of the libraries. It comes in the form of plugins, tools or completely separate libraries - it just depends on the language you are using.