Skip to content

Commit 58500a3

Browse files
Polishing
Finishing touches.
1 parent e356e9d commit 58500a3

File tree

18 files changed

+630166
-155
lines changed

18 files changed

+630166
-155
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+
```

mongodb/fragment-spi/atlas-api/pom.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,6 @@
99
</parent>
1010

1111
<artifactId>spring-data-mongodb-fragment-spi-atlas</artifactId>
12-
<name>Spring Data MongoDB - Fragment SPI</name>
12+
<name>Spring Data MongoDB - Reusable Fragments - Vector Search Fragment</name>
1313

1414
</project>
Original file line numberDiff line numberDiff line change
@@ -1,18 +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+
*/
116
package com.example.spi.mongodb.atlas;
217

318
import java.util.List;
419

5-
import org.springframework.beans.BeansException;
6-
import org.springframework.beans.factory.config.BeanPostProcessor;
7-
import org.springframework.context.annotation.Bean;
8-
import org.springframework.data.domain.Limit;
9-
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
10-
import org.springframework.stereotype.Component;
1120

1221
/**
1322
* @author Christoph Strobl
1423
*/
1524
public interface AtlasRepository<T> {
1625

17-
List<T> vectorSearch(String index, String path, List<Double> vector, Limit limit);
26+
List<T> vectorSearch(String index, String path, List<Double> vector);
1827
}

mongodb/fragment-spi/atlas-api/src/main/java/com/example/spi/mongodb/atlas/AtlasRepositoryAutoConfiguration.java

-33
This file was deleted.

mongodb/fragment-spi/atlas-api/src/main/java/com/example/spi/mongodb/atlas/AtlasRepositoryFragment.java

+24-26
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* you may not use this file except in compliance with the License.
66
* You may obtain a copy of the License at
77
*
8-
* http://www.apache.org/licenses/LICENSE-2.0
8+
* https://www.apache.org/licenses/LICENSE-2.0
99
*
1010
* Unless required by applicable law or agreed to in writing, software
1111
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -16,60 +16,58 @@
1616
package com.example.spi.mongodb.atlas;
1717

1818
import java.util.List;
19-
import java.util.Objects;
2019

2120
import org.bson.Document;
2221
import org.springframework.beans.factory.annotation.Autowired;
22+
import org.springframework.core.ResolvableType;
2323
import org.springframework.data.domain.Limit;
2424
import org.springframework.data.mongodb.core.MongoOperations;
2525
import org.springframework.data.mongodb.core.aggregation.Aggregation;
26-
import org.springframework.data.mongodb.core.query.CriteriaDefinition;
27-
import org.springframework.data.repository.core.support.RepositoryMethodContext;
28-
import org.springframework.lang.Nullable;
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;
2929

30-
class AtlasRepositoryFragment<T> implements AtlasRepository<T> {
30+
class AtlasRepositoryFragment<T> implements AtlasRepository<T>, RepositoryMetadataAccess {
3131

32-
private MongoOperations mongoOperations;
32+
private final MongoOperations mongoOperations;
3333

3434
public AtlasRepositoryFragment(@Autowired MongoOperations mongoOperations) {
3535
this.mongoOperations = mongoOperations;
3636
}
3737

3838
@Override
3939
@SuppressWarnings("unchecked")
40-
public List<T> vectorSearch(String index, String path, List<Double> vector, Limit limit) {
40+
public List<T> vectorSearch(String index, String path, List<Double> vector) {
4141

42-
RepositoryMethodContext metadata = RepositoryMethodContext.currentMethod();
42+
RepositoryMethodContext methodContext = RepositoryMethodContext.getContext();
4343

44-
Class<?> domainType = metadata.getRepository().getDomainType();
45-
System.out.println("domainType: " + domainType);
44+
Class<?> domainType = resolveDomainType(methodContext.getMetadata());
4645

47-
Document $vectorSearch = createDocument(index, path, vector, limit, null, null, null);
46+
Document $vectorSearch = createDocument(index, path, vector, Limit.of(10));
4847
Aggregation aggregation = Aggregation.newAggregation(ctx -> $vectorSearch);
4948

50-
return (List<T>) mongoOperations.aggregate(aggregation, mongoOperations.getCollectionName(domainType), domainType);
49+
return (List<T>) mongoOperations.aggregate(aggregation, mongoOperations.getCollectionName(domainType), domainType).getMappedResults();
5150
}
5251

53-
private static Document createDocument(String indexName, String path, List<Double> vector, Limit limit, @Nullable Boolean exact, @Nullable CriteriaDefinition filter, @Nullable Integer numCandidates) {
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) {
5463

5564
Document $vectorSearch = new Document();
5665

5766
$vectorSearch.append("index", indexName);
5867
$vectorSearch.append("path", path);
5968
$vectorSearch.append("queryVector", vector);
6069
$vectorSearch.append("limit", limit.max());
61-
62-
if (exact != null) {
63-
$vectorSearch.append("exact", exact);
64-
}
65-
66-
if (filter != null) {
67-
$vectorSearch.append("filter", filter.getCriteriaObject());
68-
}
69-
70-
if (numCandidates != null) {
71-
$vectorSearch.append("numCandidates", numCandidates);
72-
}
70+
$vectorSearch.append("numCandidates", 150);
7371

7472
return new Document("$vectorSearch", $vectorSearch);
7573
}

mongodb/fragment-spi/atlas-api/src/main/java/com/example/spi/mongodb/atlas/AtlasRepositoryPostProcessor.java

-38
This file was deleted.

mongodb/fragment-spi/atlas-api/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

-16
This file was deleted.

mongodb/fragment-spi/pom.xml

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
</parent>
1010

1111
<artifactId>spring-data-mongodb-fragment-spi</artifactId>
12+
<name>Spring Data MongoDB - Reusable Fragments</name>
1213
<packaging>pom</packaging>
1314

1415
<modules>

mongodb/fragment-spi/sample/pom.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
</parent>
1010

1111
<artifactId>spring-data-mongodb-fragment-spi-usage</artifactId>
12-
<name>Spring Data MongoDB - Fragment Usage</name>
12+
<name>Spring Data MongoDB - Reusable Fragments - Fragment Usage</name>
1313

1414
<dependencies>
1515
<dependency>

mongodb/fragment-spi/sample/src/main/java/com/example/data/mongodb/ApplicationConfiguration.java

+1-17
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* you may not use this file except in compliance with the License.
66
* You may obtain a copy of the License at
77
*
8-
* http://www.apache.org/licenses/LICENSE-2.0
8+
* https://www.apache.org/licenses/LICENSE-2.0
99
*
1010
* Unless required by applicable law or agreed to in writing, software
1111
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -23,20 +23,4 @@
2323
@SpringBootApplication
2424
public class ApplicationConfiguration {
2525

26-
// @Bean
27-
// BeanFactoryPostProcessor postProcessor() {
28-
// return new BeanFactoryPostProcessor() {
29-
// @Override
30-
// public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
31-
//
32-
// BeanDefinition movieRepository = beanFactory.getBeanDefinition("movieRepository");
33-
// movieRepository.getPropertyValues().add("exposeMetadata", true);
34-
// }
35-
// };
36-
// }
37-
38-
// @Bean
39-
// BeanPostProcessor postProcessor() {
40-
// return new AtlasRepositoryPostProcessor();
41-
// }
4226
}

mongodb/fragment-spi/sample/src/main/java/com/example/data/mongodb/Movie.java

+38-5
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* you may not use this file except in compliance with the License.
66
* You may obtain a copy of the License at
77
*
8-
* http://www.apache.org/licenses/LICENSE-2.0
8+
* https://www.apache.org/licenses/LICENSE-2.0
99
*
1010
* Unless required by applicable law or agreed to in writing, software
1111
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -19,11 +19,44 @@
1919

2020
/**
2121
* @author Christoph Strobl
22-
* @since 2024/08
2322
*/
24-
@Document
23+
@Document("movies")
2524
public class Movie {
2625

27-
String id;
28-
String title;
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+
}
2962
}

mongodb/fragment-spi/sample/src/main/java/com/example/data/mongodb/MovieRepository.java

+15
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,18 @@
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+
*/
116
package com.example.data.mongodb;
217

318
import com.example.spi.mongodb.atlas.AtlasRepository;

0 commit comments

Comments
 (0)