Skip to content

Commit

Permalink
Add sample for reusable repository extensions.
Browse files Browse the repository at this point in the history
Closes #690
  • Loading branch information
christophstrobl authored and mp911de committed Nov 22, 2024
1 parent a4c5214 commit 8d0bde0
Show file tree
Hide file tree
Showing 18 changed files with 630,380 additions and 8 deletions.
4 changes: 2 additions & 2 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,8 @@ WARNING: If you're done using it, don't forget to shut it down!

== Miscellaneous

* `bom` - Example project how to use the Spring Data release train bom in non-Spring-Boot
scenarios.
* `mongodb/fragment-spi` - Example project how to use Spring Data Fragment SPI to provide reusable custom extensions.
* `bom` - Example project how to use the Spring Data release train bom in non-Spring-Boot scenarios.
* `map` - Example project to show how to use `Map`-backed repositories.
* `multi-store` - Example project to use both Spring Data MongoDB and Spring Data JPA in
one project.
Expand Down
35 changes: 35 additions & 0 deletions mongodb/fragment-spi/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Spring Data - Fragment SPI Example

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.

The project is divided into the `atlas-api`, providing the extension, and the `sample` using it.

## atlas-api

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.

The implementation leverages `RepositoryMethodContext` to get hold of method invocation metadata to determine the collection name derived from the repositories domain type `<T>`.
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`.

## sample

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`.

The `Movies` class in `src/main/test` takes care of setting up required test data and indexes.

## Running the sample

The is using a local MongoDB Atlas instance bootstrapped by Testcontainers.
Running the `MovieRepositoryTests` the `test/movies` collection will be populated with about 400 entries from the `mflix.embedded_movies.json` file.
Please be patient while data is loaded into the database and the index created afterwards.
Progress information will be printed to the log.
```log
INFO - com.example.data.mongodb.Movies: 73 - Loading movies mflix.embedded_movies.json
INFO - com.example.data.mongodb.Movies: 90 - Created 420 movies in test.movies
INFO - com.example.data.mongodb.Movies: 65 - creating vector index
INFO - com.example.data.mongodb.Movies: 68 - index 'plot_vector_index' created
```
Once data and index are available search result will be printed:
```log
INFO - ...mongodb.MovieRepositoryTests: 183 - Movie{id='66d6ee0937e07b74aa2939cc', ...
```
14 changes: 14 additions & 0 deletions mongodb/fragment-spi/atlas-api/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.springframework.data.examples</groupId>
<artifactId>spring-data-mongodb-fragment-spi</artifactId>
<version>2.0.0.BUILD-SNAPSHOT</version>
</parent>

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

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright 2024 the original author or authors.
*
* 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
*
* https://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.
*/
package com.example.spi.mongodb.atlas;

import java.util.List;


/**
* @author Christoph Strobl
*/
public interface AtlasRepository<T> {

List<T> vectorSearch(String index, String path, List<Double> vector);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Copyright 2024 the original author or authors.
*
* 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
*
* https://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.
*/
package com.example.spi.mongodb.atlas;

import java.util.List;

import org.bson.Document;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ResolvableType;
import org.springframework.data.domain.Limit;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.RepositoryMethodContext;
import org.springframework.data.repository.core.support.RepositoryMetadataAccess;

class AtlasRepositoryFragment<T> implements AtlasRepository<T>, RepositoryMetadataAccess {

private final MongoOperations mongoOperations;

public AtlasRepositoryFragment(@Autowired MongoOperations mongoOperations) {
this.mongoOperations = mongoOperations;
}

@Override
@SuppressWarnings("unchecked")
public List<T> vectorSearch(String index, String path, List<Double> vector) {

RepositoryMethodContext methodContext = RepositoryMethodContext.getContext();

Class<?> domainType = resolveDomainType(methodContext.getMetadata());

Document $vectorSearch = createDocument(index, path, vector, Limit.of(10));
Aggregation aggregation = Aggregation.newAggregation(ctx -> $vectorSearch);

return (List<T>) mongoOperations.aggregate(aggregation, mongoOperations.getCollectionName(domainType), domainType).getMappedResults();
}

@SuppressWarnings("unchecked")
private static <T> Class<T> resolveDomainType(RepositoryMetadata metadata) {

// resolve the actual generic type argument of the AtlasRepository<T>.
return (Class<T>) ResolvableType.forClass(metadata.getRepositoryInterface())
.as(AtlasRepository.class)
.getGeneric(0)
.resolve();
}

private static Document createDocument(String indexName, String path, List<Double> vector, Limit limit) {

Document $vectorSearch = new Document();

$vectorSearch.append("index", indexName);
$vectorSearch.append("path", path);
$vectorSearch.append("queryVector", vector);
$vectorSearch.append("limit", limit.max());
$vectorSearch.append("numCandidates", 150);

return new Document("$vectorSearch", $vectorSearch);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
com.example.spi.mongodb.atlas.AtlasRepository=com.example.spi.mongodb.atlas.AtlasRepositoryFragment
19 changes: 19 additions & 0 deletions mongodb/fragment-spi/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.springframework.data.examples</groupId>
<artifactId>spring-data-mongodb-examples</artifactId>
<version>2.0.0.BUILD-SNAPSHOT</version>
</parent>

<artifactId>spring-data-mongodb-fragment-spi</artifactId>
<name>Spring Data MongoDB - Reusable Fragments</name>
<packaging>pom</packaging>

<modules>
<module>atlas-api</module>
<module>sample</module>
</modules>
</project>
26 changes: 26 additions & 0 deletions mongodb/fragment-spi/sample/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.springframework.data.examples</groupId>
<artifactId>spring-data-mongodb-fragment-spi</artifactId>
<version>2.0.0.BUILD-SNAPSHOT</version>
</parent>

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

<dependencies>
<dependency>
<groupId>org.springframework.data.examples</groupId>
<artifactId>spring-data-mongodb-fragment-spi-atlas</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.data.examples</groupId>
<artifactId>spring-data-mongodb-example-utils</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright 2024 the original author or authors.
*
* 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
*
* https://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.
*/
package com.example.data.mongodb;

import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
* @author Christoph Strobl
*/
@SpringBootApplication
public class ApplicationConfiguration {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright 2024 the original author or authors.
*
* 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
*
* https://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.
*/
package com.example.data.mongodb;

import org.springframework.data.mongodb.core.mapping.Document;

/**
* @author Christoph Strobl
*/
@Document("movies")
public class Movie {

private String id;
private String title;
private String plot;

public String getPlot() {
return plot;
}

public void setPlot(String plot) {
this.plot = plot;
}

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

public String getTitle() {
return title;
}

public void setTitle(String title) {
this.title = title;
}

@Override
public String toString() {
return "Movie{" +
"id='" + id + '\'' +
", title='" + title + '\'' +
", plot='" + plot + '\'' +
'}';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright 2024 the original author or authors.
*
* 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
*
* https://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.
*/
package com.example.data.mongodb;

import com.example.spi.mongodb.atlas.AtlasRepository;
import org.springframework.data.repository.CrudRepository;

/**
* @author Christoph Strobl
*/
public interface MovieRepository extends CrudRepository<Movie, String>, AtlasRepository<Movie> {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Loading

0 comments on commit 8d0bde0

Please sign in to comment.