In this article we are going to integrate Firestore, a worth exploring database solution offered by GCP (Google Cloud Platform) in a spring boot application using spring cloud gcp.
For those who don’t know about firestore, In a nutshell it is a realtime No SQL document database, And its the defacto database of Firebase. I’m not going to explore firestore or firebase in detail rather take you through how to develop spring based java application to store your data in firestore. This integration will come in handy when you need to support your firebase application with a spring backend, or similaraly in any spring project to leverage GCP infrastructure.
Create Spring Boot Project
First let’s create a spring boot project using spring initializr. Create your own configurations or start with mine. Make sure you add GCP support by adding dependency if you choose your own configurations. My configurations are for maven + java 14 with jar packaging. So you should have java 14 ready in your PC to follow along. After open the downloaded project add following dependency to the pom or the build.gradle.
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-gcp-starter-firestore</artifactId> </dependency>
Create GCP Project or Firebase Project
First you need to create GCP project or Firebase project. To create GCP project go to google cloud console, login with your google account or else create one there, you can also get their free credit if you want. Then create a new project. Next we need to add firestore to the project. For that, on the menu goto storage then firestore. When you navigated to firestore page, select native mode. Then select location to deploy firestore. Make sure to choose it wisely. After few seconds you will be navigated to firestore data browser. You can add data manually. But who does do that?
To access Firestore (GCP API) you need to create service account. Go to IAM & Admin from the menu and select service accounts. There will be a service account created for Firestore you can use that, or else create new one from the above menu. If you decided to use existing one, click edit in the drop down menu. When creating new one you will have few options to try out, other wise in edit mode you can change name or description. I invite you to hack around a bit, as we developers usually do. Look for create key button and click, pick JSON from the popup then it will download a json file, keep it securely. Advice, don’t commit it to your public repo mistakenly. If you choose firebase, its rather easy, go to firebase console create a project then go to database section and create Firestore database. On screen information will you guide to download service account key. Either way now with your service account key you are ready to access Firestore in your code.
Setup Spring Boot Project with GCP Service Account
spring: cloud: gcp: project-id: your-project-id firestore: credentials: location: classpath:service-account-file.json
You can put your service account file in resources directory and use classpath: to point to the file otherwise you could use file: to point file in the file system. Make sure to add project id other wise you could end up with NullPointerException
comes out of no where, I got it and finally manage to identify that I’ve missed the project id in application.yml. After that you could test app to see if it is ready to communicate with Firestore by updating your application class with following source.
package com.aptkode.example.firestore; import com.google.cloud.firestore.Firestore; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; @SpringBootApplication public class FirestoreApplication { private final static Logger logger = LoggerFactory.getLogger(FirestoreApplication.class); @Autowired private Firestore firestore; public static void main(String[] args) { SpringApplication.run(FirestoreApplication.class, args); } @Bean public CommandLineRunner commandLineRunner() { return args -> { logger.info("{} app initialized.", firestore.getOptions().getProjectId()); }; } }
Firestore Data Model
Firestore is a Document oriented database, there is no tables or rows as in conventional SQL databases. In Firestore data is organized in to collections. Collections can have Documents, which is the unit of storage in Firestore. Document can contain key value pairs, nested objects (max deep level is 100) and sub collections. Collections and Documents are created implicitly in Firestore, in other words it creates if the collection or document when it is unavailable. You don’t have to create collections explicitly, when you enter first document to the collection it is created, if it doesn’t have any document left then there collection is no longer exists. To simplify collections document separation, you can use /
in the document method in java API as in the previous code snippet.
Save First Document
Let’s create User class and save it in Firestore. Make sure that your data class has a default constructor otherwise you won’t be able to deserialize Firestore document to java bean.
package com.aptkode.example.firestore; public class User { private String name; private int age; public User() { // required for firestore } public User(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
Following method creates or overwrites a user in users collection by its id (tom).
private void writeAndReadDocument() throws ExecutionException, InterruptedException { // Add document with id "tom" using a custom User class to users collection User tom = new User("Tom", 18); // set asynchronously create/update document reference users/tom // users is the collection and tom is the document id ApiFuture<WriteResult> apiFuture = this.firestore.document("users/tom").set(tom); // .get() blocks on response WriteResult writeResult = apiFuture.get(); logger.info("Update time: {}", writeResult.getUpdateTime()); }
Call it in command line runner.
Read Document as Java Object
private User readDocument() throws ExecutionException, InterruptedException { // this.firestore.collection("users").document("tom") works also ApiFuture<DocumentSnapshot> apiFuture = this.firestore.document("users/tom").get(); // .get() blocks on response DocumentSnapshot documentSnapshot = apiFuture.get(); return documentSnapshot.toObject(User.class); }
Update Document
private void updateDocument() throws ExecutionException, InterruptedException { ApiFuture<WriteResult> apiFuture = this.firestore.document("users/tom") .set(new User("tom", 19)); WriteResult writeResult = apiFuture.get(); logger.info("Update time: {}", writeResult.getUpdateTime()); }
Delete Document
private void deleteDocument() throws ExecutionException, InterruptedException { // document deletion does not delete its sub collections // see https://firebase.google.com/docs/firestore/manage-data/delete-data#collections ApiFuture<WriteResult> apiFuture = this.firestore.document("users/tom").delete(); WriteResult writeResult = apiFuture.get(); logger.info("Update time: {}", writeResult.getUpdateTime()); }
Wire it Together
Following is the example of executing all queries in command line runner block. But you may have different way of using these. We’ll explore them in future articles, for now let’s do it in following way.
@Bean public CommandLineRunner commandLineRunner() { return args -> { logger.info("{} app initialized.", firestore.getOptions().getProjectId()); writeAndReadDocument(); User user = readDocument(); logger.info("user name: {} age: {}", user.getName(), user.getAge()); updateDocument(); user = readDocument(); logger.info("user name: {} age: {}", user.getName(), user.getAge()); deleteDocument(); }; }
Firestore vs Firebase Realtime Database
Firestore is relatively new, earlier there was Realtime Database in the Firebase. Still its there. So what’s the difference? Not in the technical terms, from my experience Firestore is far better solution than Realtime database. Data modeling is much easier in Firestore, then all the CRUD operations, querying and listening to the realtime updates are easy. You can visit this page and get clear idea of what product should you use for your mobile backed either in firebase or firebase backed by custom backend.
What if I hate Spring
Trust me, there are people who hates spring. Doesn’t matter the reason, they can go ahead with GCP official java SDK for firestore. Setting up that require separate article. So I’ll show you how to use firebase admin sdk. You need to have following dependency.
<dependency> <groupId>com.google.firebase</groupId> <artifactId>firebase-admin</artifactId> <version>6.12.2</version> </dependency>
Then, as previously you need to have service account key file. By having setup similar to following you could get the access to Firestore instance. It exposes all the methods we used earlier. So you should be in same page. If you want to see how to setup firebase project for your next project, head over to one of our earlier video.
InputStream serviceAccount = Application.class.getResourceAsStream("/" + FIREBASE_ADMIN_SDK_CONFIG_FILE); FirebaseOptions options = new FirebaseOptions.Builder() .setCredentials(GoogleCredentials.fromStream(serviceAccount)) .build(); FirebaseApp.initializeApp(options); Firestore firestore = FirestoreClient.getFirestore(FirebaseApp.getInstance());
Final thoughts
Here we just touched the surface of Firestore in spring ecosystem and plain java approach, we could do more. Let’s keep digging interesting topics such as real time updates, transactions, collection manipulation, queries and more in future articles. Video version of this article is coming up on our youtube channel, subscribe for future updates and watch our existing video series on IntelliJ Plugin Development, GraphQL, spring boot, unit testing and android + Firebase. Read second part of this article here and If you have any question please put it in comments, or join our social network pages. Happy Coding.
If We Use spring-cloud-gcp-starter-firestore with
spring:
cloud:
gcp:
project-id: your-project-id
firestore:
credentials:
location: classpath:service-account-file.json
For Providing Service Account .
How Can We Access FireBaseAuth.getUser(“UserId”)
Any specific dependency that will Autowire FireBaseAuth Just like Firestore.??
Hi, nice article! Found an error though, the sprint-cloud-gcp-starter-data-firestore dependency belongs to groupId com.google.cloud not org.springframework.cloud.