Skip to content

Commit 8d0bde0

Browse files
christophstroblmp911de
authored andcommitted
Add sample for reusable repository extensions.
Closes #690
1 parent a4c5214 commit 8d0bde0

File tree

18 files changed

+630380
-8
lines changed

18 files changed

+630380
-8
lines changed

README.adoc

+2-2
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,8 @@ WARNING: If you're done using it, don't forget to shut it down!
112112

113113
== Miscellaneous
114114

115-
* `bom` - Example project how to use the Spring Data release train bom in non-Spring-Boot
116-
scenarios.
115+
* `mongodb/fragment-spi` - Example project how to use Spring Data Fragment SPI to provide reusable custom extensions.
116+
* `bom` - Example project how to use the Spring Data release train bom in non-Spring-Boot scenarios.
117117
* `map` - Example project to show how to use `Map`-backed repositories.
118118
* `multi-store` - Example project to use both Spring Data MongoDB and Spring Data JPA in
119119
one project.

mongodb/fragment-spi/README.md

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Spring Data - Fragment SPI Example
2+
3+
This project contains a sample using `spring.factories` to register implementation details for a repository extension for MongoDB Vector Search that lives outside of the project namespace.
4+
5+
The project is divided into the `atlas-api`, providing the extension, and the `sample` using it.
6+
7+
## atlas-api
8+
9+
The `AtlasRepository` is the base interface containing a `vectorSearch` method that is implemented in `AtlasRepositoryFragment`. The configuration in `src/main/resources/META-INF/spring.factories` makes sure it is picked up by the spring data infrastructure.
10+
11+
The implementation leverages `RepositoryMethodContext` to get hold of method invocation metadata to determine the collection name derived from the repositories domain type `<T>`.
12+
Since providing the metadata needs to be explicitly activated the `AtlasRepositoryFragment` uses the additional marker interface `RepositoryMetadataAccess` enabling the features for repositories extending the `AtlasRepository`.
13+
14+
## sample
15+
16+
The `MovieRepository` extends the `AtlasRepository` from the api project using a `Movie` type targeting the `movies` collection. No further configuration is needed to use the provided `vectorSearch` within the `MovieRepositoryTests`.
17+
18+
The `Movies` class in `src/main/test` takes care of setting up required test data and indexes.
19+
20+
## Running the sample
21+
22+
The is using a local MongoDB Atlas instance bootstrapped by Testcontainers.
23+
Running the `MovieRepositoryTests` the `test/movies` collection will be populated with about 400 entries from the `mflix.embedded_movies.json` file.
24+
Please be patient while data is loaded into the database and the index created afterwards.
25+
Progress information will be printed to the log.
26+
```log
27+
INFO - com.example.data.mongodb.Movies: 73 - Loading movies mflix.embedded_movies.json
28+
INFO - com.example.data.mongodb.Movies: 90 - Created 420 movies in test.movies
29+
INFO - com.example.data.mongodb.Movies: 65 - creating vector index
30+
INFO - com.example.data.mongodb.Movies: 68 - index 'plot_vector_index' created
31+
```
32+
Once data and index are available search result will be printed:
33+
```log
34+
INFO - ...mongodb.MovieRepositoryTests: 183 - Movie{id='66d6ee0937e07b74aa2939cc', ...
35+
```
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
3+
<modelVersion>4.0.0</modelVersion>
4+
5+
<parent>
6+
<groupId>org.springframework.data.examples</groupId>
7+
<artifactId>spring-data-mongodb-fragment-spi</artifactId>
8+
<version>2.0.0.BUILD-SNAPSHOT</version>
9+
</parent>
10+
11+
<artifactId>spring-data-mongodb-fragment-spi-atlas</artifactId>
12+
<name>Spring Data MongoDB - Reusable Fragments - Vector Search Fragment</name>
13+
14+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright 2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.example.spi.mongodb.atlas;
17+
18+
import java.util.List;
19+
20+
21+
/**
22+
* @author Christoph Strobl
23+
*/
24+
public interface AtlasRepository<T> {
25+
26+
List<T> vectorSearch(String index, String path, List<Double> vector);
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Copyright 2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.example.spi.mongodb.atlas;
17+
18+
import java.util.List;
19+
20+
import org.bson.Document;
21+
import org.springframework.beans.factory.annotation.Autowired;
22+
import org.springframework.core.ResolvableType;
23+
import org.springframework.data.domain.Limit;
24+
import org.springframework.data.mongodb.core.MongoOperations;
25+
import org.springframework.data.mongodb.core.aggregation.Aggregation;
26+
import org.springframework.data.repository.core.RepositoryMetadata;
27+
import org.springframework.data.repository.core.RepositoryMethodContext;
28+
import org.springframework.data.repository.core.support.RepositoryMetadataAccess;
29+
30+
class AtlasRepositoryFragment<T> implements AtlasRepository<T>, RepositoryMetadataAccess {
31+
32+
private final MongoOperations mongoOperations;
33+
34+
public AtlasRepositoryFragment(@Autowired MongoOperations mongoOperations) {
35+
this.mongoOperations = mongoOperations;
36+
}
37+
38+
@Override
39+
@SuppressWarnings("unchecked")
40+
public List<T> vectorSearch(String index, String path, List<Double> vector) {
41+
42+
RepositoryMethodContext methodContext = RepositoryMethodContext.getContext();
43+
44+
Class<?> domainType = resolveDomainType(methodContext.getMetadata());
45+
46+
Document $vectorSearch = createDocument(index, path, vector, Limit.of(10));
47+
Aggregation aggregation = Aggregation.newAggregation(ctx -> $vectorSearch);
48+
49+
return (List<T>) mongoOperations.aggregate(aggregation, mongoOperations.getCollectionName(domainType), domainType).getMappedResults();
50+
}
51+
52+
@SuppressWarnings("unchecked")
53+
private static <T> Class<T> resolveDomainType(RepositoryMetadata metadata) {
54+
55+
// resolve the actual generic type argument of the AtlasRepository<T>.
56+
return (Class<T>) ResolvableType.forClass(metadata.getRepositoryInterface())
57+
.as(AtlasRepository.class)
58+
.getGeneric(0)
59+
.resolve();
60+
}
61+
62+
private static Document createDocument(String indexName, String path, List<Double> vector, Limit limit) {
63+
64+
Document $vectorSearch = new Document();
65+
66+
$vectorSearch.append("index", indexName);
67+
$vectorSearch.append("path", path);
68+
$vectorSearch.append("queryVector", vector);
69+
$vectorSearch.append("limit", limit.max());
70+
$vectorSearch.append("numCandidates", 150);
71+
72+
return new Document("$vectorSearch", $vectorSearch);
73+
}
74+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
com.example.spi.mongodb.atlas.AtlasRepository=com.example.spi.mongodb.atlas.AtlasRepositoryFragment

mongodb/fragment-spi/pom.xml

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
3+
<modelVersion>4.0.0</modelVersion>
4+
5+
<parent>
6+
<groupId>org.springframework.data.examples</groupId>
7+
<artifactId>spring-data-mongodb-examples</artifactId>
8+
<version>2.0.0.BUILD-SNAPSHOT</version>
9+
</parent>
10+
11+
<artifactId>spring-data-mongodb-fragment-spi</artifactId>
12+
<name>Spring Data MongoDB - Reusable Fragments</name>
13+
<packaging>pom</packaging>
14+
15+
<modules>
16+
<module>atlas-api</module>
17+
<module>sample</module>
18+
</modules>
19+
</project>

mongodb/fragment-spi/sample/pom.xml

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
3+
<modelVersion>4.0.0</modelVersion>
4+
5+
<parent>
6+
<groupId>org.springframework.data.examples</groupId>
7+
<artifactId>spring-data-mongodb-fragment-spi</artifactId>
8+
<version>2.0.0.BUILD-SNAPSHOT</version>
9+
</parent>
10+
11+
<artifactId>spring-data-mongodb-fragment-spi-usage</artifactId>
12+
<name>Spring Data MongoDB - Reusable Fragments - Fragment Usage</name>
13+
14+
<dependencies>
15+
<dependency>
16+
<groupId>org.springframework.data.examples</groupId>
17+
<artifactId>spring-data-mongodb-fragment-spi-atlas</artifactId>
18+
<version>${project.version}</version>
19+
</dependency>
20+
<dependency>
21+
<groupId>org.springframework.data.examples</groupId>
22+
<artifactId>spring-data-mongodb-example-utils</artifactId>
23+
<scope>test</scope>
24+
</dependency>
25+
</dependencies>
26+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright 2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.example.data.mongodb;
17+
18+
import org.springframework.boot.autoconfigure.SpringBootApplication;
19+
20+
/**
21+
* @author Christoph Strobl
22+
*/
23+
@SpringBootApplication
24+
public class ApplicationConfiguration {
25+
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright 2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.example.data.mongodb;
17+
18+
import org.springframework.data.mongodb.core.mapping.Document;
19+
20+
/**
21+
* @author Christoph Strobl
22+
*/
23+
@Document("movies")
24+
public class Movie {
25+
26+
private String id;
27+
private String title;
28+
private String plot;
29+
30+
public String getPlot() {
31+
return plot;
32+
}
33+
34+
public void setPlot(String plot) {
35+
this.plot = plot;
36+
}
37+
38+
public String getId() {
39+
return id;
40+
}
41+
42+
public void setId(String id) {
43+
this.id = id;
44+
}
45+
46+
public String getTitle() {
47+
return title;
48+
}
49+
50+
public void setTitle(String title) {
51+
this.title = title;
52+
}
53+
54+
@Override
55+
public String toString() {
56+
return "Movie{" +
57+
"id='" + id + '\'' +
58+
", title='" + title + '\'' +
59+
", plot='" + plot + '\'' +
60+
'}';
61+
}
62+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright 2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.example.data.mongodb;
17+
18+
import com.example.spi.mongodb.atlas.AtlasRepository;
19+
import org.springframework.data.repository.CrudRepository;
20+
21+
/**
22+
* @author Christoph Strobl
23+
*/
24+
public interface MovieRepository extends CrudRepository<Movie, String>, AtlasRepository<Movie> {
25+
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

0 commit comments

Comments
 (0)