I'm New to firestore. I need a little help.
I got a function, that updates the amount of "stock shares". its a "sell" function, so the amount can only go down.
The problem is. I don't wanna go below 0.
So I wanna get the PREVIOUS amount of shares. before I update to the new amount.
So I can make sure I don't go below 0.
There are 2 ways to do this. 1 is by using firestore rules, 2 is by getting the Prev amount like i said.
Can you guys help me get the Prev amount before the UPDATE stage?
code:
function sellStock(){
db.collection("Stocks")
.where("ticker", "==", props.name)
.get()
.then((querySnapshot) => {
if(!querySnapshot.empty){
querySnapshot.forEach(function(doc){
db.collection("myStocks")
.doc(doc.id)
.update({
shares: doc.data().shares - amount
})
"shares" will be the prev amount.
"amount" will be the amount of shares we wanna sell.
Try this way:
.update({
shares: (doc.data().shares - amount) >= 0 ? (doc.data().shares - amount) : 0
})
Updated after discussion in the comments.
There is an important aspect to consider with your business logic: Do you need to execute an atomic operation on several documents? Example: You are subtracting the value of amount to the value of shares, since it is a selling operation but I guess that somewhere else (in another document) you are also adding some value, for example in the bank account of the seller.
In such a case you should use a Transaction: "if a transaction reads documents and another client modifies any of those documents, Cloud Firestore retries the transaction". You need to include in the Transaction all the documents that need to be locked while the operation is ongoing (i.e. all the docs that are involved in the operation, the ones on which you subtract and the ones on which you add).
However, since you want to update several documents, returned by a query, you cannot use a Transaction of one of the mobile/web SDKs (e.g. iOS, Android, Web/JavaScript), because the mobile/web SDKs use optimistic concurrency controls to resolve data contention.
What you can do is to use one of the Admin SDKs, like the Node.js one, since it uses pessimistic concurrency controls and therefore offers the possibility to run a Transaction on a query (see that you can pass a query to the get() method). So you could do that in a Callable Cloud Function.
Here is an example of a Transaction that will atomically update all the docs on which you substract. Since you didn't share the entire business logic (we don't know which are the docs that you need to update by adding a value) it's a bit difficult to go deeper in the example.
exports.updateTickers = functions.https.onCall((data, context) => {
// get the value of the filter (i.e. props.name) via the data Object
const filter = data.filter;
const amount = data.amount;
const db = admin.firestore();
return db.runTransaction(transaction => {
let queryRef = db.collection("Stocks").where("ticker", "==", filter);
return transaction.get(queryRef)
.then((querySnapshot) => {
querySnapshot.forEach((doc) => {
const currentValue = doc.get('amount');
if (currentValue - amount > 0) (
transaction.update(doc.ref, { likes: currentValue - amount })
)
});
});
})
.then(() => {
return { result: "Amounts update successful" }
})
});
The only sure-fire way to prevent the shares count from going below 0 is to enforce that in security rules:
allow write: if request.resource.data.shares >= 0;
Any other method can be bypassed by a malicious user who uses your project configuration data with their code.
With this in place, the simplest (and fastest) way to subtract the amount from the shares count is with an atomic increment operation:
db.collection("myStocks")
.doc(doc.id)
.update({
shares: firebase.firestore.FieldValue.increment(-1 * amount)
})
Related
Suppose I want to make an e-commerce system. I have 2 aggregates here ProductAggregate and UserAggregate. Product aggregate contains productId, price. User aggregate contains userId and balance. Here's the problem, in event-sourcing we should not rely on the read model since there might be eventual consistency problem. Ok so we should rely on the command model right I guess?, but this two command model is different. I read from somewhere else they told me that aggregate should only rely on its state. Let's say the user want to buy a product I have to check if he has enough balance and in order to do that I need to know the price of product. So read model not allowed, aggregate query not allowed. what options do I have here?
const ProductAggregate = {
state: {
productId: "product-1",
price: 100
}
}
const UserAggregate = {
state: {
userId: "userId-1",
balance: 50
},
handlePurchase: ({ userId, productId }) => {
// todo I got productId from the client, but how can I retrieve its price ?
if (this.state.balance < price) {
throw "Insufficient balance bro."
}
}
}
So I though it must be my bad aggregate design which makes UserAggregate requires state from outside of its context. So in this situation how do I properly design an Aggregate for User and Product.
edited:
I have been thinking all day long for the solution and I came up with this approach. So instead of putting purchase command in the UserAggregate I put it in the ProductAggregate and call it OrderProductCommand which is a bit weird for me since the product itself can't create an order, but the user can (it seems to work anyway I don't even know?). So with this approach I can now retrieve the price and send another command DeductBalanceCommand which will deduct amount of money from the user.
const ProductAggregate = {
state: {
productId: "product-1",
price: 100
},
handleOrder: ({productId, userId}) => {
await commandBus.send({
command: "handleDeduct",
params: {
userId: userId,
amount: this.state.price
}
})
.then(r => eventBus.publish({
event: "OrderCreated",
params: {
productId: productId,
userId: userId
}
}))
.catch(e => {
throw "Unable to create order due to " + e.message
})
}
}
const UserAggregate = {
state: {
userId: "userId-1",
balance: 50
},
handleDeduct: ({ userId, amount }) => {
if (this.state.balance < amount) {
throw "Insufficient balance bro."
}
eventBus.publish({
event: "BalanceDeducted",
params: {
userId: userId,
amount: amount
}
})
}
}
Is it fine and correct to use this approach? it's a bit weird for me or maybe it's just a way of thinking in DDD world?
ps. I added javascript tag so my code can have colors and easy to read.
First of all, regarding your handle, you're not stupid :)
A few points:
In many situations you can query the read model even though there's eventual consistency. If you reject a command that would have been accepted had a pending update become visible in the read model, that can typically be retried. If you accept a command that would have been rejected, there's often a compensating action that can be applied after the fact (e.g. a delay between ordering a physical product and that product being delivered).
There are a couple of patterns that can be useful. One is the saga pattern where you would model the process of a purchase. Rather than "user A buys product X", you might have an aggregate corresponding to "user A's attempt to purchase product X", which validates and reserves that user A is able to buy X and that X is able to be purchased.
Every write model with an aggregate implies the existence of one sufficiently consistent read model for that aggregate. One can thus define queries or "read-only" commands against the write model. CQRS (IMO) shouldn't be interpreted as "don't query the write model" but "before trying to optimize the write model for reads (whether ease, performance, etc.), give strong consideration to handling that query with a read model": i.e. if you're querying the write model, you give up some of the right to complain about the queries being slow or difficult. Depending on how you're implementing aggregates this option may or may not be easy to do.
I have some code that looks like the following:
export const createTable = async (data) => {
const doc = db.collection("tables").doc();
const ref = db
.collection("tables")
.where("userId", "==", data.userId)
.orderBy("number", "desc").limit(1);
db.runTransaction(async transaction => {
const query = await transaction.get(ref);
let number = 1;
if (!query.empty) {
const snapshot = query.docs[0];
const data = snapshot.data();
const id = snapshot.id;
number = data.number + 1;
}
data = {number, ...data};
transaction.set(doc, data);
});
Basically I have a tables collection and each table has an auto generated number like #1, #2, #3
When creating new tables, I want to fetch the latest table number and create the new table with that number incremented by 1.
I wanted to wrap it in a transaction so that if a table created while running the transaction, it will restart so that I don't end up with duplicate numbers.
However, this errors out on the .get(), and from googling I've read that Firestore can't monitor a whole collection within transactions, but instead it requires a specific doc passed to it. Which I obviously can't do because I need to monitor for new docs created in that collection, not changes in a particular doc.
If so, what's the correct way to implement this?
The Firestore transaction API for client apps requires that you get() each individual document that you want to participate in the transaction. So, if you have a query whose results you want to transact with, you will need to:
Perform the query (outside of the transaction)
Collect document references for each document in the result set
In the transaction, get() them all individually.
You will be limited to 500 documents per transaction.
If you want to dynamically look for new documents to modify, you will probably much better off implementing that on the backend using a Firestore trigger in Cloud Functions to automatically handle each new document as they are created, without requiring any code on the client.
Because you're updating just one document, you probably don't need to use transactions for incrementing values.
You can use Firestore Increment to achieve this.
Here is an example taken from here:
const db = firebase.firestore();
const increment = firebase.firestore.FieldValue.increment(1);
// Document reference
const storyRef = db.collection('stories').doc('hello-world');
// Update read count
storyRef.update({ reads: increment });
This is the easiest way to increment values in Firestore.
I have a firestore collections named users, each users have a generated id with a field score :
users
0e8X3VFL56rHBxxgkYOW
score : 4
3SeDjrgAWMmh3ranh2u
score : 5
I use redux-firestore and i want to reset all my users score at 0, something like
firestore.update({ collection: 'users' }, { score : 0 }
I can't achieve this because update method need a document id
Do you know how to do this ?
You can get all the documents in the collection, get their id's and perform updates using those id's:
db.collection("cities").get().then(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
doc.ref.update({
capital: true
});
});
});
For some strange reason the accepted answer ( thehamzarocks ) wasn't working for me, none of the documents were updated. Maybe there's a bug in AngularFire2. Anyway, I decided to loop over the docs array of the QuerySnapshot instead of using its forEach method, and add each update to a batch queue. Batching bulk operations is also more efficient than sending a new update request for each update operation.
resetScore(): Promise<void> {
return this.usersCollectionRef.ref.get().then(resp => {
console.log(resp.docs)
let batch = this.afs.firestore.batch();
resp.docs.forEach(userDocRef => {
batch.update(userDocRef.ref, {'score': 0, 'leadsWithSalesWin': 0, 'leadsReported': 0});
})
batch.commit().catch(err => console.error(err));
}).catch(error => console.error(error))
}
Batch updates are nice but bare in mind that they are limited to 500 document updates per transaction.
If this reset isn't done often maybe simplest approach is:
async function resetScores() {
const collection = await db
.collection("users")
.get()
collection.forEach(doc=> {
doc.ref
.update({
score: 0
})
})
}
I came across this post while searching for similar solutions. Firestore now has batched writes, which will update all documents in one go. This could be an ideal solution for fewer documents.
Updating #thehamzarocks's answer:
const batch = db.batch()
db.collection('cities').get().then(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
const docRef = db.collection('cities').doc(doc.id)
batch.update(docRef, { capital: true })
});
batch.commit();
});
Firestore doesn't have the ability to bulk update documents without knowing their IDs. You will have to somehow know the document ID of each document to update (perform a query, or do batches of queries), and update each one individually.
Sorry if the question is old but I thought providing a new answer to this question might be useful to someone else too. I managed to bulk update the entries of a list using the following command:
this.db
.list<User[]>('users')
.set('/', users);
Edit: I'm using AngularFireDatabase.
What I need:
I want to save articles or notes in Firestore with their respective fields:
Title
Content (texts or paragraphs)
Creation date
Owners (to share that article with other
people and who can edit them like: https://firebase.google.com/docs/firestore/solutions/role-based-access)
But when I show the list of articles I don't need the "content" field (to save bandwidth). I've read that (maybe I'm wrong), it is not possible to make a query to get only specific fields from a document with Firestore.
If it were normal SQL to obtain specific columns from articles (without its content) It would be something like:
SELECT title, creation_date, ...
FROM table_name;
So I've opted to separate the content for two root-level collections (for flexibility and scalability)
My current structure:
Articles collection:
- `articles` [collection]
- `ARTICLE_ID` [document]
- `creatorId` [field]
- `title` [field]
- `date` [field]
- `owners` [obj field]
- {user1_id}: true
- {user2_id}: true
...
Contents collection:
- `contents` [collection]
- `{ARTICLE_ID}` [document]
- `content` [field]
To get articles list in realtime:
firebase.firestore().collection('articles')
.where(`owners.${user.uid}`, '==', true)
.onSnapshot(querySnapshot => {
const articles = []
querySnapshot.forEach((doc) => {
articles.push({
id: doc.id,
...doc.data()
})
})
// do something with articles array
})
To show in another view and get the entire article with its content:
const db = firebase.firestore()
const articleRef = db.collection('articles').doc(articleId)
const contentRef = db.collection('contents').doc(articleId) // same Id as article
articleRef.get().then(articleDoc => {
if (articleDoc.exists) {
contentRef.get().then(contentDoc => {
if (contentDoc.exists) {
const article = {
...articleDoc.data(),
...contentDoc.data()
}
// full article obj
}
})
}
})
My questions
Do you think it's better to do two queries (getArticle and getContent) at the same time and wait with Promise.all() instead of nesting the querys like I do?
Is there a better way to get the article and its content with one query or more efficiently? Some tips or ideas?
Thank you very much in advance!
According to the Firestore Query.select documentation you should be able to select the fields you want.
let collectionRef = firestore.collection('col');
let documentRef = collectionRef.doc('doc');
return documentRef.set({x:10, y:5}).then(() => {
return collectionRef.where('x', '>', 5).select('y').get();
}).then((res) => {
console.log(`y is ${res.docs[0].get('y')}.`);
});
Neither approach is pertinently better than the other. But there are a few key differences.
When you nest the reads, the second read only starts after the first read has completed. When you use Promise.all() both reads start at the same time, so can (partially) run in parallel.
On the other hand: when you use Promise.all() your completion handler (the code you run in then()) won't execute until both documents have loaded. If you nest the calls, you can update the UI after just the first document has loaded.
In the end, the differences are likely to be small. But since they may be significant to your use-case, measure the results and see what works best for you.
In order to output a single field from a Firestore document (version 9) - for example the 'title' in the articles collection you can use the following code snippet:
const q = query(collection(db, 'articles'))
let results = [];
await getDocs(q);
results = getLocation.docs.map((doc) => doc.data()['title']);
results.sort()
The results array will contain only the title field, sorted alphabetically
(Note you have to reference the Firestore db and import 'getDocs', 'query' and 'collection' modules from Firestore)
Firebase Hosting would be your best bet for static content such as articles. If you look at AMP-HTML for example, they strongly make the case for ultra-fast page loads and highlight benefits of edge caching. Firebase hosting is advertised to also support global edge caching.
Firestore and Firebase Realtime Database are database engines. These are not the proper tool for serving up articles.
Is it possible to update firestore using the previous state?
So for example I have an address document which has a users field which holds an array of users associated with the address.
whenever I want to add a new user to this array I need the previous array otherwise I will end up overwriting the current data with the new data.
So I end up with something like.
firestore()
.collection("addresses")
.doc(addressId)
.get()
.then(doc => {
this.db
.collection("addresses")
.doc(addressId)
.update({
users: [...doc.data().users, id]
})
});
Is there a way to access the previous data without having to nest calls?
if not
Is there a better way to manage relationships?
If you need the previous value to determine the new value, you should use a transaction. This is the only way to ensure that different clients aren't accidentally overwriting each other's actions.
Unfortunately transactions also need nested calls, since that is the only way to get the current value, and even have one extra wrapper (for the transaction.
var docRef = firestore()
.collection("addresses")
.doc(addressId);
return db.runTransaction(function(transaction) {
// This code may get re-run multiple times if there are conflicts.
return transaction.get(docRef).then(function(doc) {
transaction.update(docRef, { users: [...doc.data().users, id ]});
});
}).then(function() {
console.log("Transaction successfully committed!");
}).catch(function(error) {
console.log("Transaction failed: ", error);
});
The optimal solution is to use a data structure that doesn't require the current value to add new values. This is one of the reasons Firebase recommends against using arrays: they're inherently hard to scale when multiple users may be adding items to the array. If there is no need for maintaining order between the users, I'd recommend using a set-like structure for the users:
users: {
id1: true,
id2: true
}
This is a collection with two users (id1 and id2). The true values are just markers, since you can't have a field without a value.
With this structure, adding a user is as easy as:
firestore()
.collection("addresses")
.doc(addressId)
.update({ "users.id3": true })
Also see the Firestore documentation on
Working with Arrays, Lists, and Sets