Spring Data Reactive Repositories for Cloud Firestore
| Currently some features are not supported: transactions, sorting, query by example, projections, auditing. |
Spring Data is an abstraction for storing and retrieving POJOs in numerous storage technologies.
Spring Cloud GCP adds Spring Data support for Google Cloud Firestore in native mode, providing reactive template and repositories support.
To begin using this library, add the spring-cloud-gcp-data-firestore artifact to your project.
Maven coordinates for this module only, using Spring Cloud GCP BOM:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gcp-data-firestore</artifactId>
</dependency>
Gradle coordinates:
dependencies {
compile group: 'org.springframework.cloud', name: 'spring-cloud-gcp-data-firestore'
}
We provide a Spring Boot Starter for Spring Data Firestore, with which you can use our recommended auto-configuration setup. To use the starter, see the coordinates below.
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gcp-starter-data-firestore</artifactId>
</dependency>
Gradle coordinates:
dependencies {
compile group: 'org.springframework.cloud', name: 'spring-cloud-gcp-starter-data-firestore'
}
Configuration
Properties
The Spring Boot starter for Google Cloud Firestore provides the following configuration options:
Name |
Description |
Required |
Default value |
|
Enables or disables Pub/Sub auto-configuration |
No |
|
|
GCP project ID where the Google Cloud Firestore API is hosted, if different from the one in the Spring Cloud GCP Core Module |
No |
|
|
OAuth2 credentials for authenticating with the Google Cloud Firestore API, if different from the ones in the Spring Cloud GCP Core Module |
No |
|
|
Base64-encoded OAuth2 credentials for authenticating with the Google Cloud Firestore API, if different from the ones in the Spring Cloud GCP Core Module |
No |
|
|
OAuth2 scope for Spring Cloud GCP Cloud Firestore credentials |
No |
Reactive Repository settings
Spring Data Repositories can be configured via the @EnableReactiveFirestoreRepositories annotation on your main @Configuration class.
With our Spring Boot Starter for Spring Data Cloud Firestore, @EnableReactiveFirestoreRepositories is automatically added.
It is not required to add it to any other class, unless there is a need to override finer grain configuration parameters provided by @EnableReactiveFirestoreRepositories.
Autoconfiguration
Our Spring Boot autoconfiguration creates the following beans available in the Spring application context:
-
an instance of
FirestoreTemplate -
instances of all user defined repositories extending
FirestoreReactiveRepository(an extension ofReactiveCrudRepositorywith additional Cloud Firestore features) when repositories are enabled -
an instance of
Firestorefrom the Google Cloud Java Client for Firestore, for convenience and lower level API access
Object Mapping
Spring Data Cloud Firestore allows you to map domain POJOs to Cloud Firestore collections and documents via annotations:
import org.springframework.data.annotation.Id;
import org.springframework.cloud.gcp.data.firestore.Document;
@Document(collectionName = "usersCollection")
public class User {
@DocumentId
private String name;
private Integer age;
}
@Document(collectionName = "usersCollection") annotation configures the collection name for the documents of this type.
This annotation is optional, by default the collection name is derived from the class name.
@DocumentId annotation marks a field to be used as document id. This annotation is required.
| Internally we use Firestore client library object mapping. See the documentation for supported annotations. |
Reactive Repositories
Spring Data Repositories is an abstraction that can reduce boilerplate code.
For example:
public interface UserRepository extends FirestoreReactiveRepository<User> {
Flux<User> findByAge(Integer age);
Flux<User> findByAgeGreaterThanAndAgeLessThan(Integer age1, Integer age2);
Flux<User> findByAgeGreaterThan(Integer age);
Flux<User> findByAgeGreaterThan(Integer age, Pageable pageable);
Mono<Long> countByAgeIsGreaterThan(Integer age);
}
Spring Data generates a working implementation of the specified interface, which can be autowired into an application.
The User type parameter to FirestoreReactiveRepository refers to the underlying domain type.
public class MyApplication {
@Autowired
UserRepository userRepository;
public void writeReadDeleteTest() {
User alice = new User("Alice", 29);
User bob = new User("Bob", 60);
this.userRepository.save(alice).block();
this.userRepository.save(bob).block();
assertThat(this.userRepository.count().block()).isEqualTo(2);
assertThat(this.userRepository.findAll().map(User::getName).collectList().block())
.containsExactlyInAnyOrder("Alice", "Bob");
}
}
Repositories allow you to define custom Query Methods (detailed in the following sections) for retrieving and counting based on filtering and paging parameters.
Custom queries with @Query annotation are not supported since there is no query language in Cloud Firestore
|
Query methods by convention
public class MyApplication {
public void partTreeRepositoryMethodTest() {
User u1 = new User("Cloud", 22);
User u2 = new User("Squall", 17);
Flux<User> users = Flux.fromArray(new User[] { u1, u2 });
this.userRepository.saveAll(users).blockLast();
assertThat(this.userRepository.count().block()).isEqualTo(2);
assertThat(this.userRepository.findByAge(22).collectList().block()).containsExactly(u1);
assertThat(this.userRepository.findByAgeGreaterThanAndAgeLessThan(20, 30).collectList().block())
.containsExactly(u1);
assertThat(this.userRepository.findByAgeGreaterThan(10).collectList().block()).containsExactlyInAnyOrder(u1,
u2);
}
}
In the example above the query method implementations in UserRepository are generated based on the name of the methods using the Spring Data Query creation naming convention.
Cloud Firestore only supports filter components joined by AND, and the following operations:
-
equals -
greater than or equals -
greater than -
less than or equals -
less than -
is null
After writing a custom repository interface specifying just the signatures of these methods, implementations are generated for you and can be used with an auto-wired instance of the repository.
Transactions
Read-only and read-write transactions are provided by TransactionalOperator (see this blog post on reactive transactions for details).
In order to use it, you would need to autowire ReactiveFirestoreTransactionManager like this:
public class MyApplication {
@Autowired
ReactiveFirestoreTransactionManager txManager;
}
After that you will be able to use it to create an instance of TransactionalOperator.
Note that you can switch between read-only and read-write transactions using TransactionDefinition object:
DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
transactionDefinition.setReadOnly(false);
TransactionalOperator operator = TransactionalOperator.create(this.txManager, transactionDefinition);
When you have an instance of TransactionalOperator, you can execute a sequence of Firestore operations in a transaction using operator::transactional:
User alice = new User("Alice", 29);
User bob = new User("Bob", 60);
this.userRepository.save(alice)
.then(this.userRepository.save(bob))
.as(operator::transactional)
.block();
this.userRepository.findAll()
.flatMap(a -> {
a.setAge(a.getAge() - 1);
return this.userRepository.save(a);
})
.as(operator::transactional).collectList().block();
assertThat(this.userRepository.findAll().map(User::getAge).collectList().block())
.containsExactlyInAnyOrder(28, 59);
Read operations in a transaction can only happen before write operations.
All write operations are applied atomically.
Read documents are locked until the transaction finishes with a commit or a rollback, which are handled by Spring Data.
If an Exception is thrown within a transaction, the rollback operation is executed.
Otherwise, the commit operation is executed.
|
Reactive Repository Sample
A sample application is available.
Cloud Firestore Spring Boot Starter
If you prefer using Firestore client only, Spring Cloud GCP provides a convenience starter which automatically configures authentication settings and client objects needed to begin using Google Cloud Firestore in native mode.
See documentation to learn more about Cloud Firestore.
To begin using this library, add the spring-cloud-gcp-starter-firestore artifact to your project.
Maven coordinates, using Spring Cloud GCP BOM:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gcp-starter-firestore</artifactId>
</dependency>
Gradle coordinates:
dependencies {
compile group: 'org.springframework.cloud', name: 'spring-cloud-gcp-starter-firestore'
}
Using Cloud Firestore
The starter automatically configures and registers a Firestore bean in the Spring application context. To start using it, simply use the @Autowired annotation.
@Autowired
Firestore firestore;
void writeDocumentFromObject() throws ExecutionException, InterruptedException {
// Add document data with id "joe" using a custom User class
User data = new User("Joe",
Arrays.asList(
new Phone(12345, PhoneType.CELL),
new Phone(54321, PhoneType.WORK)));
// .get() blocks on response
WriteResult writeResult = this.firestore.document("users/joe").set(data).get();
LOGGER.info("Update time: " + writeResult.getUpdateTime());
}
User readDocumentToObject() throws ExecutionException, InterruptedException {
ApiFuture<DocumentSnapshot> documentFuture =
this.firestore.document("users/joe").get();
User user = documentFuture.get().toObject(User.class);
LOGGER.info("read: " + user);
return user;
}
Sample
A sample application is available.