Skip to content

Commit

Permalink
Update Elide JPA docs (#157)
Browse files Browse the repository at this point in the history
  • Loading branch information
QubitPi authored Jun 24, 2024
1 parent 84fa373 commit d641d59
Show file tree
Hide file tree
Showing 46 changed files with 660 additions and 88 deletions.
16 changes: 1 addition & 15 deletions docs/docs/configuration.md → docs/docs/configuration.mdx
Original file line number Diff line number Diff line change
@@ -1,22 +1,8 @@
---
sidebar_position: 5
sidebar_position: 4
title: Configuration
---

[//]: # (Copyright Jiaqi Liu)

[//]: # (Licensed under the Apache License, Version 2.0 (the "License");)
[//]: # (you may not use this file except in compliance with the License.)
[//]: # (You may obtain a copy of the License at)

[//]: # ( http://www.apache.org/licenses/LICENSE-2.0)

[//]: # (Unless required by applicable law or agreed to in writing, software)
[//]: # (distributed under the License is distributed on an "AS IS" BASIS,)
[//]: # (WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.)
[//]: # (See the License for the specific language governing permissions and)
[//]: # (limitations under the License.)

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

Expand Down
8 changes: 8 additions & 0 deletions docs/docs/crud/_category_.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"label": "CRUD Webservice",
"position": 6,
"link": {
"type": "generated-index",
"description": "Spinning Up CRUD Jersey Webservice in a Minute"
}
}
8 changes: 8 additions & 0 deletions docs/docs/crud/elide/_category_.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"label": "Elide Library Documentation",
"position": 2,
"link": {
"type": "generated-index",
"description": "Spinning Up CRUD Jersey Webservice in a Minute"
}
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -792,7 +792,7 @@ backend. This can be customized by providing our own implementation. Elide provi
Internals
---------

![Error loading elide-async-design.png](./img/elide-async-design.png)
![Error loading elide-async-design.png](img/elide-async-design.png)

[demo-schema]: asyncapi#enable-the-async-api
[file-result-storage-engine]: https://github.com/paion-data/elide/blob/master/elide-async/src/main/java/com/paiondata/elide/async/service/storageengine/FileResultStorageEngine.java
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ A client makes a request to the single entry point of the federated graph called
orchestrates and distributes the request across your APIs and returns a unified response. For a client, the request and
response cycle of querying the router looks the same as querying any GraphQL server.

![Error loading graphql-federation.png](./img/graphql-federation.png)
![Error loading graphql-federation.png](img/graphql-federation.png)

### Benefits of Federation

Expand All @@ -32,12 +32,12 @@ when an organization adopting GraphQL has multiple teams developing APIs indepen
that provides the data used by that team. For example, a travel app may have separate GraphQL APIs for users, flights,
and hotels:

![Error loading multiple-graphql-apis.png](./img/multiple-graphql-apis.png)
![Error loading multiple-graphql-apis.png](img/multiple-graphql-apis.png)

With a single federated graph, we preserve a powerful advantage of GraphQL over traditional REST APIs: the ability to
fetch all the data we need in a single request.

![Error loading multiple-graphql-apis-federated.png](./img/multiple-graphql-apis-federated.png)
![Error loading multiple-graphql-apis-federated.png](img/multiple-graphql-apis-federated.png)

The router intelligently calls all the APIs it needs to complete requests rather than simply forwarding them. For
performance and security reasons, clients should only query the router, and only the router should query the constituent
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -521,4 +521,4 @@ Running websockets as a standalone service is as simple as disabling JSON-API an
</TabItem>
</Tabs>

[example-project]: https://github.com/QubitPi/elide-spring-boot-example
[example-project]: https://github.com/paion-data/elide-spring-boot-example
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
7 changes: 7 additions & 0 deletions docs/docs/crud/elide/datastores/_category_.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"label": "Data Stores",
"position": 8,
"link": {
"type": "generated-index"
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
sidebar_position: 7
sidebar_position: 1
title: Data Stores
description: A boundary between Webservice and Database
---
Expand Down
167 changes: 167 additions & 0 deletions docs/docs/crud/elide/datastores/search-datastore.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
---
sidebar_position: 2
title: Search Data Store
---

Overview
--------

Search Data Store provides full text search for Elide.

### Requirements

This store leverages [Hibernate Search](https://hibernate.org/search/) which requires Hibernate 6+.

### Usage

`SearchDataStore` wraps another fully featured store and supports full text search on fields that are indexed using
Hibernate Search. If the query cannot be answered by the `SearchDataStore`, it delegates the query to the underlying
(wrapped) data store.

#### Annotating Entity

Use Hibernate Search annotations to describe how your entities are indexed and stored in Lucene or Elasticsearch. Some
of the annotations (like `AnalyzerDef`) can be defined once at the package level if desired.

```java
@Entity
@Include
@Indexed
@Data // Lombok
public class Item {

@Id
private long id;

@FullTextField(
name = "name",
searchable = Searchable.YES,
projectable = Projectable.NO,
analyzer = "case_insensitive"
)
@KeywordField(name = "sortName", sortable = Sortable.YES, projectable = Projectable.NO, searchable = Searchable.YES)
private String name;

@FullTextField(searchable = Searchable.YES, projectable = Projectable.NO, analyzer = "case_insensitive")
private String description;

@GenericField(searchable = Searchable.YES, projectable = Projectable.NO, sortable = Sortable.YES)
private Date modifiedDate;

private BigDecimal price;
}
```

#### (Optional) Defining a Custom Analyzer

The `Item` entity above references a non-standard analyzer - `case_insensitive`. This analyzer needs to be
programmatically created:

```java
public class MyLuceneAnalysisConfigurer implements LuceneAnalysisConfigurer {

@Override
public void configure(LuceneAnalysisConfigurationContext ctx) {
ctx.analyzer("case_insensitive")
.custom()
.tokenizer(NGramTokenizerFactory.class)
.param("minGramSize", "3")
.param("maxGramSize", "50")
.tokenFilter(LowerCaseFilterFactory.class);
}
}
```

and then configured by setting the property `hibernate.search.backend.analysis.configurer` to the new analyzer.

```xml
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
version="2.0">
<persistence-unit name="searchDataStoreTest">
<class>com.paiondata.elide.datastores.search.models.Item</class>

<properties>
<property name="hibernate.search.backend.analysis.configurer" value="class:com.paiondata.elide.datastores.search.MyLuceneAnalysisConfigurer"/>
<property name="hibernate.search.backend.directory.type" value="local-heap"/>
...
</properties>
</persistence-unit>
</persistence>
```

#### Wrapping DataStore

```java
/* Create your JPA data store */
DataStore store = ...

/* Wrap it with a SearchDataStore */
EntityManagerFactory emf = ...

boolean indexOnStartup = true; //Create a fresh index when the server starts
searchStore = new SearchDataStore(store, emf, indexOnStartup);

/* Configure Elide with your store */
ElideSettings = new ElideSettingsBuidler(searchStore).build();
```

#### Indexing Data

We can index data either by:

1. When the `SearchDataStore` is initialized, indicate (by setting `indexOnStartup` to `true`) that the search store
should build a complete index.
2. Issuing created, updated, and delete requests against our Elide service.
3. Using an out of band process using Hibernate Search APIs.

### Caveats

#### Data Type Support

Only text fields (String) are supported/tested. Other data types (dates, numbers, etc) have not been tested. Embedded
index support has not been implemented.

#### Filter Operators

Only INFIX, and PREFIX filter operators (and their case insensitive equivalents) are supported. Note that hibernate
search only indexes and analyzes fields as either case sensitive or not case-sensitive - so a given field will only
support the INFIX/PREFIX filter operator that matches how the field was indexed.

All other filter operators are passed to the underlying wrapped JPA store.

#### Analyzer Assumptions

##### Index Analysis

To implement correct behavior for Elide's INFIX and PREFIX operators, the search store assumes an ngram (non-edge)
tokenizer is used. This allows white spaces and punctuation to be included in the index.

If the client provides a filter predicate with a term which is smaller or larger than the min/max ngram sizes
respectively, it will not be found in the index.

The search store can be configured to return a 400 error to the client in those scenarios by passing the minimum and
maximum ngram size to the constructor of the `SearchDataStore`. The sizes are global and apply to all Elide entities
managed by the store instance:

```java
new SearchDataStore(jpaStore, emf, true, 3, 50);
```

##### Search Term Analysis

Elide creates a Hibernate Search `SimpleQueryString` for each predicate. It first escapes white space and punctuation
in any user provided input (to match Elide's default behavior when not using the `SearchDataStore`). The resulting
single token is used to construct a prefix query.

##### Sorting and Pagination

When using the INFIX operator, sorting and pagination are pushed to down Lucene/ElasticSearch. When using the PREFIX
operator, they are performed in-memory in the Elide service.

Elide constructs a Prefix query, which together with an ngram index fully implements the INFIX operator. However, the
ngram analyzer adds ngrams to the index that do not start on word boundaries. For the prefix operator, the search
store first performs the lucene filter and then filters again in-memory to return the correct set of matching terms.

In this instance, because filtering is performed partially in memory, Elide also sorts and paginates in memory as well.
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ The following diagram represents a high level component breakout of Elide. Names
whereas other names represent functional blocks (made up of many classes). Gray arrows represent client request and
response flow through the system.

![High Level Design](./img/high-level-design.png)
![High Level Design](img/high-level-design.png)

Elide can be broken down into the following layers:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,4 @@ to:

The following screenshot demonstrates configuring these two settings for the 'FilterIT' tests in IntelliJ:

![Configuring IT Tests In Intellij](./img/intellij-config.png)
![Configuring IT Tests In Intellij](img/intellij-config.png)
Loading

0 comments on commit d641d59

Please sign in to comment.