Firebase is a Google platform that has multiple features in it. Those are Analytics, Performance Tracking, Firestore DB, Crashlytics, Messaging etc. Most of them are very useful, but there is one that is always overestimated. We will be talking about Firestore DB. Don’t get me wrong, I’m not claiming it’s something terrible, as Google would never create that. We will check how useful it will be in comparison to custom backend.
First of all, we will determine all benefits mentioned by Firestore fans.
(1) One developer can write Front-end and Back-end
(2) It’s cheaper than writing a custom Back-end
(3) It’s faster than writing a custom Back-end
(4) It supports a real-time update
(5) Firestore support offline mode automatically from the box
To see all the benefits and pitfalls we will try to recreate the whole flow developing Startup with Firestore.
We will not divide the Firebase Database and Firestore, because most of the pitfalls are the same for both. Afterwards, we will only talk about the best one of this couple – Firestore.
First of all I would like to review a really powerful feature from the list written on top – offline mode support. That really works well and will give a good user experience to your application for free. When the app will not have internet connection – Firestore will return the latest cached data and all changes will be automatically synced after the internet will appear.
A first huge + to Firestore!!!
Now let’s try to create our startup with Firestore. To do that, we will need to learn what Firestore is and how it is working
Firestore is a not relational database.
A relational database can be seen as a group of different lists connected with each other with lines (relations). All lists are independent and can be connected by relations in the future when there will be a need to combine two or more lists.
A non-relational database is a tree-like structure that does not have relations by default. It can be seen as a folder structure where you always move deeper and deeper by hierarchy. When we need to have connections – we will be creating several small databases connected by unique identifiers.
Having all this in mind – our first task is to create a database architecture for non-relational Firestore.
Doing so requires a deep understanding of a product and its future as we will not have flexibility like with relational databases. So we will need not just a developer but a person who understands the product needs in detail. A CTO might be a good fit if there is one in a StartUp. Moreover, this person is supposed to have an excellent understanding of Back-end approaches and database understanding in general.
This contradicts with (1 – Front-end and Back-end can be written by one developer) & (2 – It’s cheaper than writing a custom Back-end) as most of the Front-end devs will not be able to architect a good database, so we will require a high-level Backend-dev to do that. In addition to this, Firestore does not have database migrations, so significant changes to the business model, which were not taken into account at the architecture stage, will become a massive pain in the ass and almost impossible to deliver. Startups need to have the flexibility and sometimes require 180 degrees turn around, so the situation described is the worst-case scenario for them.
After we successfully documented a database structure – we will start a client development. As an example, we will take an iOS messaging app. Messages require real-time updates, so it gives a huge + to (4 – It supports a real-time update).
We will take a look at the most straightforward implementation and then move to a harder one.
The first wrong assumption is that anybody can work with Firestore.
Most of the developers who did not have a chance to work with a Firestore had been working with a RESTful API that requires an entirely different approach. So, we will need a person who has already had experience in working with Firestore or we need to be ready that a newcomer will need some time to get into it, but this again contradicts (2) & (3). We will take the best scenario and imagine that we have a real Firestore expert who has already built a lot of projects on it and knows how it works.
The first thing that we need to know about a Firestore, is that most of the backend logic needs to be implemented on Front-end.
For example, in our app, we need to send a message to another user or a group of users. In addition to this, it is necessary to change unread messages count and send a push notification. While a custom backend requires us to perform one action and everything else is automated, in Firestore case we are backend and we need to complete all three operations at once.
If we have no experienced developer – he will run all three actions simultaneously. If one operation fails – we will get a corrupted database that does not match real state. A more experienced developer knows a solution and will be using bath updates that will automatically cancel all changes if at least one action fails. All this will increase code readability, maintainability as well as development time. Ok, we have dealt with this problem by hiring a Firestore expert and giving him additional time. Let’s move forward.
The second thing to be known about Firestore is that the database is a remote one.
It means that there is no direct access to all data inside. To get this data, it is necessary to perform requests. Most of the readers will point that custom server requires requests to be made from a client as well. That’s true, but… The custom server does not require requests by itself. Now, I will explain in detail. In the last paragraph, we understood that a client becomes a server, and all actions must be handled there. In our previous example, we increased the number of unread messages of the user who sent a message. To do that it is necessary to know the current count. While a custom backend can quickly get information from the database, in the case of Firestore, an additional request to Firestore is unavoidable.
What is wrong with additional requests?
- It increases delay while performing some actions.
- It creates an additional field where a request might fail and can cancel all-action, as described in the first paragraph.
It’s a significant benefit for a custom server. But is there a way to handle the current situation with Firestore?
Yes, there is a partial solution – Cloud functions.
Anyway, it looks like we get the same server implementation on Firebase! But no 🙁
At the moment of writing this article, Cloud functions and Firestore are located on different servers. It means that we are sending a request to Cloud function after which Cloud function will perform a request to Firestore.
For custom Back-end the database and implementation will be stored on the same server, so the additional request will not be required. Getting back to Cloud functions, you should understand it as an intermediate client between the iOS app and Firestore. It has almost the same implementation as it has on the iOS app. The same three requests for sending messages + one request for fetching unread messages count. Cloud functions are only useful when we have some specific operations not related to Firestore.
Such as sending push notifications or communicating with some REST API. So, developing on Firestore, it is necessary to find a balance between Cloud functions and client implementation. Most of the code should be anyway on the client-side if you do not want to have one additional request made to the Cloud function. Which means that the problem described in the previous paragraph will stay with us and we will need to learn how to live with that 🙂
Now our expert wrote an iOS app that somehow works with Firestore.
He was working alone, so he spent twice more time because he was writing client and server logic. Team with separate developers(for backend and a client) write everything twice faster as they were working simultaneously. In practice, the difference is even more significant because of all described pitfalls that require continual rewriting on an implementation when some bottleneck can be found. But we had an expert, so everything was correctly written during the first attempt. Let’s stick to this version again and continue discovering 🙂
After some time, our StartUp has become successful, and we would like to create an Android application as well. Let’s take a look at two scenarios here.
First, one – our expert does not know how to write Android applications. In this case, we will hire a new developer – Expert #2.
The third thing that we need to know about Firestore is that by default, every client can modify everything in the Firestore database.
Only implementations (iOS & Android) control how the database needs to be changed by performing some action. So, while on iOS unread messages count modifies the database in one way, on Android in another – we will end up having inconsistency, data corruption and a big list of issues. Custom implementation servers provide only one way of doing so, and there is no chance to do it differently. To have a better understanding – iOS and Android are separate server implementations connected to the same database while the custom server is the only for each.
To have an implementation similar to Firestore, we can get back and create a Cloud function, but you already know what will go wrong.
To save the situation, Google created security rules that will allow only one way of writing and reading data. It will require additional time again and in-depth knowledge of Firestore to set up these rules. But we have an expert who did that, so let’s continue writing Android apps.
Our newly hired developer will need to write exactly the same implementation as iOS has. So if sending a message requires iOS one request to get information and three to send – the same needs to be done on Android as well. It’s necessary to create a documentation of Firestore implementation.
Keep in mind that it is not just a description of Firestore architecture given at the beginning, this is also a description of which requests and in what order are supposed to be made to perform some actions. It will take extra time. I know that for a custom backend – documentation is also required, but in case the backend is written with some standards – there are a lot of tools that generate documentation based on a code structure.
It is more than enough for early-stage startups to avoid spendings. Anyway, our expert has written this documentation and our Expert2 can start working.
With written documentation, Expert2 will be able to recreate the same Firestore implementation which we have on iOS. Anyway, he will need to do that again, copy the same logic twice. So, writing an Android app will be still more time-consuming than if we had a custom server.
Now we have both platforms implemented, and we only need to maintain them. And here we will face problems as well. The most obvious that every change will be implemented twice – for iOS and Android. It means more time in total and as a result – more spendings.
Second, and not so obvious problem already was described at the beginning. It is changing a database structure. As we have a non-relational database, we can not just alter small pieces. We will need to look at architecture in general. Anyway, imagine that we’ve found a way to change the database structure. To do that and not to lose current users information, we will need to migrate data from one state to another.
For example, if messages were related to users, now we might want to make them related to channels, so we are moving existing messages from users to channels. With Firestore everything is not so easy. Remember that custom servers have databases and implementations in the same solace. In this case, we will only need a few minutes even for an extensive database to migrate data. In Firestore, we need to perform requests to do so, and that becomes very complicated and time-consuming while your app is in regular use. For instance, while you have migrated messages for one user, another created tons of new messages that need to be migrated again.
Regarding pricing, to avoid another boring text, I will just put this link:
It definitely will not be cheaper than AWS servers. By my approximate calculation, it’s around three times more.
Now, as we understand what to expect, let’s talk about the feature that looks like a benefit – real-time update.
You should agree that this one is cool, but there are a lot of existing solutions for sockets that make life easy with a custom backend as well: Pusher, SocketIO, Twilio, etc. For sure Firestore will be more comfortable in implementation than any of these solutions, but ask yourself if you need real-time updates and if so – is it worth working with Firestore?
So is Firestore completely useless?
I see Firestore as an excellent sandbox to implement your idea as POC while a database structure will stay very simple and will not require a lot of manipulations. Definitely, for some applications, Firestore will be the best solution. But only for some, not for all. A good example might be apps that have most of the work done on clients. It might be noted, TODO lists, some photo editing apps, local games, etc. They do not need logic on a server. They need more databases to store data. In this scenario, Firestore might be your way to go!
- One developer will be writing code on Firestore twice slower (in practice almost 2.5 times) than two developers for a client and server separately
- A system with Firestore will be massive and hardly supportable
- If you have more than one client, Firestore code needs to be copied
- Firestore performance will always be worse than the one from custom server
- Firestore maintenance will cost more in a long-term perspective
- Firestore implementation will cost more than custom server
- Firestore does not support migrations, that makes significant changes impossible
- You will need a rare expert who will be able to implement Firestore properly